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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +20 -3
  4. data/Dockerfile +3 -0
  5. data/README.md +20 -7
  6. data/db/migrations/10_setup_pg_cron.rb +23 -0
  7. data/db/migrations/11_add_events_link_global_position.sql +1 -0
  8. data/db/migrations/12_migrate_legacy_links.rb +83 -0
  9. data/db/migrations/13_remove_events_link_id.sql +6 -0
  10. data/db/migrations/14_remove_ids_events_id_index.sql +1 -0
  11. data/db/migrations/9_create_events_horizon.sql +21 -0
  12. data/docs/appending_events.md +1 -1
  13. data/docs/events_and_streams.md +1 -1
  14. data/docs/multiple_commands.md +16 -1
  15. data/lib/pg_eventstore/callbacks.rb +7 -5
  16. data/lib/pg_eventstore/cli/try_to_delete_subscriptions_set.rb +2 -2
  17. data/lib/pg_eventstore/client.rb +7 -5
  18. data/lib/pg_eventstore/commands/all_stream_read_grouped.rb +3 -3
  19. data/lib/pg_eventstore/commands/append.rb +3 -3
  20. data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +5 -2
  21. data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +1 -1
  22. data/lib/pg_eventstore/commands/link_to.rb +6 -6
  23. data/lib/pg_eventstore/commands/multiple.rb +2 -2
  24. data/lib/pg_eventstore/commands/regular_stream_read_grouped.rb +1 -1
  25. data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +1 -1
  26. data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +1 -1
  27. data/lib/pg_eventstore/connection.rb +1 -35
  28. data/lib/pg_eventstore/errors.rb +1 -1
  29. data/lib/pg_eventstore/event.rb +7 -5
  30. data/lib/pg_eventstore/extensions/options_extension.rb +40 -11
  31. data/lib/pg_eventstore/maintenance.rb +1 -1
  32. data/lib/pg_eventstore/queries/event_queries.rb +10 -9
  33. data/lib/pg_eventstore/queries/links_resolver.rb +6 -3
  34. data/lib/pg_eventstore/queries/partition_queries.rb +72 -7
  35. data/lib/pg_eventstore/queries/transaction_queries.rb +10 -4
  36. data/lib/pg_eventstore/query_builders/events_filtering.rb +3 -7
  37. data/lib/pg_eventstore/query_builders/partitions_filtering.rb +28 -18
  38. data/lib/pg_eventstore/sql_builder.rb +30 -12
  39. data/lib/pg_eventstore/stream.rb +1 -1
  40. data/lib/pg_eventstore/subscriptions/basic_runner.rb +4 -4
  41. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +1 -1
  42. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +2 -2
  43. data/lib/pg_eventstore/subscriptions/events_processor.rb +1 -1
  44. data/lib/pg_eventstore/subscriptions/queries/subscription_command_queries.rb +5 -5
  45. data/lib/pg_eventstore/subscriptions/queries/subscription_queries.rb +3 -2
  46. data/lib/pg_eventstore/subscriptions/queries/subscription_service_queries.rb +78 -0
  47. data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rb +2 -2
  48. data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_queries.rb +1 -1
  49. data/lib/pg_eventstore/subscriptions/subscription.rb +18 -7
  50. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +8 -2
  51. data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +1 -3
  52. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +5 -2
  53. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +9 -1
  54. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +16 -10
  55. data/lib/pg_eventstore/tasks/setup.rake +30 -31
  56. data/lib/pg_eventstore/utils.rb +8 -0
  57. data/lib/pg_eventstore/version.rb +1 -1
  58. data/lib/pg_eventstore/web/application.rb +5 -5
  59. data/lib/pg_eventstore/web/paginator/events_collection.rb +4 -4
  60. data/lib/pg_eventstore/web/paginator/helpers.rb +3 -3
  61. data/lib/pg_eventstore/web/paginator/stream_ids_collection.rb +2 -2
  62. data/lib/pg_eventstore/web/subscriptions/helpers.rb +2 -2
  63. data/lib/pg_eventstore.rb +4 -4
  64. data/pg_eventstore.gemspec +1 -1
  65. data/sig/pg_eventstore/client.rbs +1 -1
  66. data/sig/pg_eventstore/commands/multiple.rbs +1 -1
  67. data/sig/pg_eventstore/event.rbs +7 -5
  68. data/sig/pg_eventstore/extensions/options_extension.rbs +9 -1
  69. data/sig/pg_eventstore/queries/event_queries.rbs +11 -11
  70. data/sig/pg_eventstore/queries/links_resolver.rbs +5 -5
  71. data/sig/pg_eventstore/queries/partition_queries.rbs +5 -1
  72. data/sig/pg_eventstore/queries/transaction_queries.rbs +2 -2
  73. data/sig/pg_eventstore/query_builders/partitions_filtering.rbs +9 -5
  74. data/sig/pg_eventstore/sql_builder.rbs +8 -2
  75. data/sig/pg_eventstore/subscriptions/queries/subscription_queries.rbs +13 -13
  76. data/sig/pg_eventstore/subscriptions/queries/subscription_service_queries.rbs +19 -0
  77. data/sig/pg_eventstore/subscriptions/subscription.rbs +2 -0
  78. data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +2 -0
  79. data/sig/pg_eventstore/subscriptions/subscription_runner.rbs +0 -2
  80. data/sig/pg_eventstore/subscriptions/subscription_runners_feeder.rbs +10 -3
  81. data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +2 -0
  82. data/sig/pg_eventstore/utils.rbs +10 -2
  83. 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: id) || raise(RecordNotFound.new('subscriptions', 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: subscriptions_set_id, command_name: command_name) ||
23
- create(subscriptions_set_id: subscriptions_set_id, command_name: command_name, data: data)
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: id) || raise(RecordNotFound.new('subscriptions_set', 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: attrs, locked_by: locked_by))
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: set, name: name)[:id]
123
- self.locked_by = subscription_queries.lock!(id, lock_id, force: 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: options,
176
+ options:,
167
177
  restart_count: 0,
168
178
  last_restarted_at: nil,
169
- max_restarts_number: 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: 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: 0.2,
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: 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 = Benchmark.realtime { result = yield }
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(from_position: next_chunk_global_position, max_count: estimate_events_number)
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
- runners_query_options = runners.to_h { |runner| [runner.id, runner.next_chunk_query_opts] }
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: force_lock
63
+ config_name, @subscriptions_set_lifecycle, force_lock:
63
64
  )
64
65
  @subscription_feeder = SubscriptionFeeder.new(
65
- config_name: 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: options, chunk_query_interval: pull_interval,
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: graceful_shutdown_timeout,
110
+ graceful_shutdown_timeout:,
110
111
  recovery_strategies: recovery_strategies(subscription, restart_terminator, failed_subscription_notifier)
111
112
  ),
112
- subscription: 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(&blk)
154
+ def run_cli_callbacks(&)
154
155
  return yield unless defined?(::PgEventstore::CLI)
155
156
 
156
- PgEventstore::CLI.callbacks.run_callbacks(:start_manager, self, &blk)
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: subscription,
184
- restart_terminator: restart_terminator,
185
- failed_subscription_notifier: 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
- helpers = Class.new do
6
- class << self
7
- def postgres_uri
8
- @postgres_uri ||=
9
- begin
10
- uri = URI.parse(ENV.fetch('PG_EVENTSTORE_URI'))
11
- uri.path = '/postgres'
12
- uri.to_s
13
- end
14
- end
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
- def db_name
17
- @db_name ||= URI.parse(ENV.fetch('PG_EVENTSTORE_URI')).path&.delete('/')
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.configure do |config|
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', [helpers.db_name]).
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 "#{helpers.db_name} already exists. Skipping."
40
+ puts "#{PgEventstore::MigrationHelpers.db_name} already exists. Skipping."
35
41
  else
36
- conn.exec("CREATE DATABASE #{conn.escape_string(helpers.db_name)} WITH OWNER #{conn.escape_string(conn.user)}")
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.configure do |config|
72
- config.pg_uri = helpers.postgres_uri
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
@@ -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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module PgEventstore
4
4
  # @return [String]
5
- VERSION = '1.13.3'
5
+ VERSION = '2.0.0'
6
6
  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: 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: 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: subscriptions_set,
192
- subscriptions: 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: 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: starting_id, per_page: per_page, order: order, options: options)
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: from_position, max_count: 1, direction: order)
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: from_position, max_count: per_page, direction: order == :asc ? :desc : :asc)
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}) events ORDER BY global_position #{order} LIMIT 1"
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: 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: 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: 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('events')
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('events')
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: 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: 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: name, **@config[name].options_hash) : Config.new(name: 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: max_retries,
96
- retries_interval: retries_interval,
97
- force_lock: force_lock
95
+ max_retries:,
96
+ retries_interval:,
97
+ force_lock:
98
98
  )
99
99
  end
100
100
 
@@ -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.0'
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
  #
@@ -1,7 +1,7 @@
1
1
  module PgEventstore
2
2
  module Commands
3
3
  class Multiple < PgEventstore::AbstractCommand
4
- def call: () { () -> untyped } -> untyped
4
+ def call: (read_only: bool) { () -> untyped } -> untyped
5
5
  end
6
6
  end
7
7
  end