pg_eventstore 1.0.4 → 1.1.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/README.md +2 -1
  4. data/db/migrations/6_add_commands_data.sql +2 -0
  5. data/docs/configuration.md +0 -5
  6. data/lib/pg_eventstore/queries.rb +0 -4
  7. data/lib/pg_eventstore/stream.rb +2 -0
  8. data/lib/pg_eventstore/subscriptions/basic_runner.rb +10 -0
  9. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +2 -27
  10. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +4 -37
  11. data/lib/pg_eventstore/subscriptions/commands_handler.rb +1 -1
  12. data/lib/pg_eventstore/subscriptions/events_processor.rb +11 -3
  13. data/lib/pg_eventstore/subscriptions/extensions/base_command_extension.rb +47 -0
  14. data/lib/pg_eventstore/subscriptions/extensions/command_class_lookup_extension.rb +15 -0
  15. data/lib/pg_eventstore/{queries → subscriptions/queries}/subscription_command_queries.rb +19 -13
  16. data/lib/pg_eventstore/{queries → subscriptions/queries}/subscriptions_set_command_queries.rb +18 -12
  17. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/base.rb +33 -0
  18. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/restore.rb +14 -0
  19. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/start_all.rb +14 -0
  20. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/stop.rb +14 -0
  21. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/stop_all.rb +14 -0
  22. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands.rb +14 -0
  23. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +2 -1
  24. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/base.rb +36 -0
  25. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/reset_position.rb +25 -0
  26. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/restore.rb +14 -0
  27. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/start.rb +14 -0
  28. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/stop.rb +14 -0
  29. data/lib/pg_eventstore/subscriptions/subscription_runner_commands.rb +14 -0
  30. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +9 -1
  31. data/lib/pg_eventstore/version.rb +1 -1
  32. data/lib/pg_eventstore/web/application.rb +9 -3
  33. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +18 -0
  34. data/lib/pg_eventstore/web/subscriptions/helpers.rb +28 -6
  35. data/lib/pg_eventstore/web/views/subscriptions/index.erb +39 -7
  36. metadata +21 -6
  37. /data/lib/pg_eventstore/{queries → subscriptions/queries}/subscription_queries.rb +0 -0
  38. /data/lib/pg_eventstore/{queries → subscriptions/queries}/subscriptions_set_queries.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5a69951bc18bd74869eb843b9e017a1c1f238bfc17750400e30b99f7d7b9f2d
4
- data.tar.gz: 56ce94941096227944e8d5f11b9c7f9ca2f1fd816ad477cb02abe58604945c47
3
+ metadata.gz: b824856ac1174e2aa2431388e12cd2997256ad4a5638c319d22ee1863c24f4ea
4
+ data.tar.gz: 64f73d163abf7a80e9384483857ab1d2cfd38da6d98f2d8f7552a24ac8a7fb3c
5
5
  SHA512:
6
- metadata.gz: f2f682990d1450b874e3d1ad9cf68e3e1cf7dcd546c78bb101982c5a03c5f355f31101442b2dd9a2c59531535e02eff01b12b119c79beca331f7262e927ebe5c
7
- data.tar.gz: 2b2624a388eba5772c6a3bdd601ffe6fbcdb3c08c0d3c4585c1dfcf03862cf94f19e39db453950294f1a5a01a35e5ee33c975632c7ba6a2c966fcb85ff6dbb5d
6
+ metadata.gz: c37a3572d224ed2706bf31d416da5bdfb91f37323225fd1f60f060eb76cd96ff33907055f32924cc6d155aa8abae44056e1f982eff560dcca27b70f0c43afa3e
7
+ data.tar.gz: 891b7a1e7697b31f53964aeacfe9ae516294421c63e9a7d7095b0bd9489b5cf56ef9101639317cd5894d2f4e7f72b51be6ea14c9b7e5c238aaee2e25379d7a7b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.1.0]
4
+ - Add "Reset position" button on Subscriptions Admin UI page
5
+
6
+ **Note** This release includes a migration to support new functional. Please don't forget to run `rake pg_eventstore:migrate` to apply latest db changes.
7
+
3
8
  ## [1.0.4]
4
9
  - Fix bug which caused slow Subscriptions to stop processing new events
5
10
  - Optimize Subscriptions update queries
data/README.md CHANGED
@@ -5,7 +5,8 @@ Implements database and API to store and read events in event sourced systems.
5
5
  ## Requirements
6
6
 
7
7
  - `pg_eventstore` requires a PostgreSQL database with jsonb data type support (which means you need to have v9.2+). However it is recommended to use a non [EOL](https://www.postgresql.org/support/versioning/) PostgreSQL version, because the development of this gem is targeted at current PostgreSQL versions.
8
- - It is recommend you to have the default value set for `default_transaction_isolation` PostgreSQL config setting(`"read committed"`) as the implementation relies on it. All other transaction isolation levels(`"repeatable read"` and `"serializable"`) may cause unexpected serialization errors which you will have to handle by yourself(e.g. by always wrapping your code using [`#multiple`](docs/multiple_commands.md)).
8
+ - It is recommended you to have the default value set for `default_transaction_isolation` PostgreSQL config setting(`"read committed"`) as the implementation relies on it. All other transaction isolation levels(`"repeatable read"` and `"serializable"`) may cause unexpected serialization errors.
9
+ - It is recommended to use a connection pooler (for example [PgBouncer](https://www.pgbouncer.org/)) in `transaction` pool mode to lower the load on a database.
9
10
  - `pg_eventstore` requires ruby v3+. The development of this gem is targeted at [current](https://endoflife.date/ruby) ruby versions.
10
11
 
11
12
  ## Installation
@@ -0,0 +1,2 @@
1
+ ALTER TABLE subscription_commands ADD COLUMN data jsonb DEFAULT '{}'::jsonb;
2
+ ALTER TABLE subscriptions_set_commands ADD COLUMN data jsonb DEFAULT '{}'::jsonb;
@@ -116,8 +116,3 @@ end
116
116
 
117
117
  Taking this into account you may want to increase `connection_pool_size` up to the number of your application's threads(
118
118
  or subscriptions).
119
-
120
- ### Usage of external connection pooler
121
-
122
- `pg_eventstore` does not use any session-specific features of PostgreSQL. You can use any PostgreSQL connection pooler
123
- you like, such as [PgBouncer](https://www.pgbouncer.org/) for example.
@@ -5,10 +5,6 @@ require_relative 'query_builders/events_filtering_query'
5
5
  require_relative 'queries/transaction_queries'
6
6
  require_relative 'queries/event_queries'
7
7
  require_relative 'queries/partition_queries'
8
- require_relative 'queries/subscription_queries'
9
- require_relative 'queries/subscriptions_set_queries'
10
- require_relative 'queries/subscription_command_queries'
11
- require_relative 'queries/subscriptions_set_command_queries'
12
8
  require_relative 'queries/links_resolver'
13
9
 
14
10
  module PgEventstore
@@ -72,6 +72,8 @@ module PgEventstore
72
72
  hash == another.hash
73
73
  end
74
74
 
75
+ # @param other_stream [Stream]
76
+ # @return [Boolean]
75
77
  def ==(other_stream)
76
78
  return false unless other_stream.is_a?(Stream)
77
79
 
@@ -182,6 +182,16 @@ module PgEventstore
182
182
  @state.to_s
183
183
  end
184
184
 
185
+ # @param state [Symbol]
186
+ # @return [Object] a result of evaluating of passed block
187
+ def within_state(state, &blk)
188
+ synchronize do
189
+ return unless @state.public_send("#{RunnerState::STATES.fetch(state)}?")
190
+
191
+ yield
192
+ end
193
+ end
194
+
185
195
  private
186
196
 
187
197
  def synchronize
@@ -3,8 +3,6 @@
3
3
  module PgEventstore
4
4
  module CommandHandlers
5
5
  class SubscriptionFeederCommands
6
- AVAILABLE_COMMANDS = %i[StopAll StartAll Restore Stop].to_h { [_1, _1.to_s] }.freeze
7
-
8
6
  # @param config_name [Symbol]
9
7
  # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
10
8
  def initialize(config_name, subscription_feeder)
@@ -16,15 +14,8 @@ module PgEventstore
16
14
  # @return [void]
17
15
  def process
18
16
  queries.find_commands(@subscription_feeder.id).each do |command|
19
- unless AVAILABLE_COMMANDS.values.include?(command[:name])
20
- PgEventstore.logger&.warn(
21
- "#{self.class.name}: Don't know how to handle #{command[:name].inspect}. Details: #{command.inspect}."
22
- )
23
- next
24
- end
25
- send(Utils.underscore_str(command[:name]))
26
- ensure
27
- queries.delete(command[:id])
17
+ command.exec_cmd(@subscription_feeder)
18
+ queries.delete(command.id)
28
19
  end
29
20
  end
30
21
 
@@ -39,22 +30,6 @@ module PgEventstore
39
30
  def connection
40
31
  PgEventstore.connection(@config_name)
41
32
  end
42
-
43
- def stop_all
44
- @subscription_feeder.stop_all
45
- end
46
-
47
- def start_all
48
- @subscription_feeder.start_all
49
- end
50
-
51
- def restore
52
- @subscription_feeder.restore
53
- end
54
-
55
- def stop
56
- @subscription_feeder.stop
57
- end
58
33
  end
59
34
  end
60
35
  end
@@ -3,30 +3,21 @@
3
3
  module PgEventstore
4
4
  module CommandHandlers
5
5
  class SubscriptionRunnersCommands
6
- AVAILABLE_COMMANDS = %i[Start Stop Restore].to_h { [_1, _1.to_s] }.freeze
7
-
8
6
  # @param config_name [Symbol]
9
7
  # @param runners [Array<PgEventstore::SubscriptionRunner>]
10
8
  # @param subscriptions_set_id [Integer]
11
9
  def initialize(config_name, runners, subscriptions_set_id)
12
10
  @config_name = config_name
13
- @runners = runners
11
+ @runners = runners.to_h { |runner| [runner.id, runner] }
14
12
  @subscriptions_set_id = subscriptions_set_id
15
13
  end
16
14
 
17
15
  # Look up commands for all given SubscriptionRunner-s and execute them
18
16
  # @return [void]
19
17
  def process
20
- queries.find_commands(@runners.map(&:id), subscriptions_set_id: @subscriptions_set_id).each do |command|
21
- unless AVAILABLE_COMMANDS.values.include?(command[:name])
22
- PgEventstore.logger&.warn(
23
- "#{self.class.name}: Don't know how to handle #{command[:name].inspect}. Details: #{command.inspect}."
24
- )
25
- next
26
- end
27
- send(Utils.underscore_str(command[:name]), command[:subscription_id])
28
- ensure
29
- queries.delete(command[:id])
18
+ queries.find_commands(@runners.keys, subscriptions_set_id: @subscriptions_set_id).each do |command|
19
+ command.exec_cmd(@runners[command.subscription_id]) if @runners[command.subscription_id]
20
+ queries.delete(command.id)
30
21
  end
31
22
  end
32
23
 
@@ -41,30 +32,6 @@ module PgEventstore
41
32
  def connection
42
33
  PgEventstore.connection(@config_name)
43
34
  end
44
-
45
- # @param subscription_id [Integer]
46
- # @return [PgEventstore::SubscriptionRunner, nil]
47
- def find_subscription_runner(subscription_id)
48
- @runners.find { |runner| runner.id == subscription_id }
49
- end
50
-
51
- # @param subscription_id [Integer]
52
- # @return [void]
53
- def start(subscription_id)
54
- find_subscription_runner(subscription_id)&.start
55
- end
56
-
57
- # @param subscription_id [Integer]
58
- # @return [void]
59
- def restore(subscription_id)
60
- find_subscription_runner(subscription_id)&.restore
61
- end
62
-
63
- # @param subscription_id [Integer]
64
- # @return [void]
65
- def stop(subscription_id)
66
- find_subscription_runner(subscription_id)&.stop_async
67
- end
68
35
  end
69
36
  end
70
37
  end
@@ -11,7 +11,7 @@ module PgEventstore
11
11
  extend Forwardable
12
12
 
13
13
  RESTART_DELAY = 5 # seconds
14
- PULL_INTERVAL = 1
14
+ PULL_INTERVAL = 1 # seconds
15
15
 
16
16
  def_delegators :@basic_runner, :start, :stop, :state, :stop_async, :wait_for_finish
17
17
 
@@ -7,7 +7,8 @@ module PgEventstore
7
7
  include Extensions::CallbacksExtension
8
8
  extend Forwardable
9
9
 
10
- def_delegators :@basic_runner, :state, :start, :stop, :wait_for_finish, :stop_async, :restore, :running?
10
+ def_delegators :@basic_runner, :state, :start, :stop, :wait_for_finish, :stop_async, :restore, :running?,
11
+ :within_state
11
12
 
12
13
  # @param handler [#call]
13
14
  def initialize(handler)
@@ -22,8 +23,10 @@ module PgEventstore
22
23
  def feed(raw_events)
23
24
  raise EmptyChunkFedError.new("Empty chunk was fed!") if raw_events.empty?
24
25
 
25
- callbacks.run_callbacks(:feed, global_position(raw_events.last))
26
- @raw_events.push(*raw_events)
26
+ within_state(:running) do
27
+ callbacks.run_callbacks(:feed, global_position(raw_events.last))
28
+ @raw_events.push(*raw_events)
29
+ end
27
30
  end
28
31
 
29
32
  # Number of unprocessed events which are currently in a queue
@@ -32,6 +35,11 @@ module PgEventstore
32
35
  @raw_events.size
33
36
  end
34
37
 
38
+ # @return [void]
39
+ def clear_chunk
40
+ @raw_events.clear
41
+ end
42
+
35
43
  private
36
44
 
37
45
  # @param raw_event [Hash]
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module Extensions
5
+ module BaseCommandExtension
6
+ def self.included(klass)
7
+ super
8
+ klass.extend(ClassMethods)
9
+
10
+ # Detects whether command is a known command. Known commands are commands, inherited from Base class.
11
+ # @return [Boolean]
12
+ klass.define_singleton_method(:known_command?) do
13
+ self < klass
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ # @param data [Hash]
19
+ # @return [Hash]
20
+ def parse_data(data)
21
+ {}
22
+ end
23
+ end
24
+
25
+ # @return [Integer]
26
+ def hash
27
+ options_hash.hash
28
+ end
29
+
30
+ # @param another [Object]
31
+ # @return [Boolean]
32
+ def eql?(another)
33
+ return false unless another.is_a?(self.class)
34
+
35
+ hash == another.hash
36
+ end
37
+
38
+ # @param another [Object]
39
+ # @return [Boolean]
40
+ def ==(another)
41
+ return false unless another.is_a?(self.class)
42
+
43
+ options_hash == another.options_hash
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module Extensions
5
+ module CommandClassLookupExtension
6
+ # @param cmd_name [String, Symbol]
7
+ # @return [Class]
8
+ def command_class(cmd_name)
9
+ const_get(cmd_name, false)
10
+ rescue NameError
11
+ self::Base
12
+ end
13
+ end
14
+ end
15
+ end
@@ -11,18 +11,22 @@ module PgEventstore
11
11
  @connection = connection
12
12
  end
13
13
 
14
- # @see #find_by or #create for available arguments
15
- # @return [Hash]
16
- def find_or_create_by(...)
14
+ # @param subscription_id [Integer]
15
+ # @param subscriptions_set_id [Integer]
16
+ # @param command_name [String]
17
+ # @param data [Hash]
18
+ # @return [PgEventstore::SubscriptionRunnerCommands::Abstract]
19
+ def find_or_create_by(subscription_id:, subscriptions_set_id:, command_name:, data:)
17
20
  transaction_queries.transaction do
18
- find_by(...) || create(...)
21
+ find_by(subscription_id: subscription_id, subscriptions_set_id: subscriptions_set_id, command_name: command_name) ||
22
+ create(subscription_id: subscription_id, subscriptions_set_id: subscriptions_set_id, command_name: command_name, data: data)
19
23
  end
20
24
  end
21
25
 
22
26
  # @param subscription_id [Integer]
23
27
  # @param subscriptions_set_id [Integer]
24
28
  # @param command_name [String]
25
- # @return [Hash, nil]
29
+ # @return [PgEventstore::SubscriptionRunnerCommands::Abstract, nil]
26
30
  def find_by(subscription_id:, subscriptions_set_id:, command_name:)
27
31
  sql_builder =
28
32
  SQLBuilder.new.
@@ -43,22 +47,23 @@ module PgEventstore
43
47
  # @param subscription_id [Integer]
44
48
  # @param subscriptions_set_id [Integer]
45
49
  # @param command_name [String]
46
- # @return [Hash]
47
- def create(subscription_id:, subscriptions_set_id:, command_name:)
50
+ # @param data [Hash]
51
+ # @return [PgEventstore::SubscriptionRunnerCommands::Abstract]
52
+ def create(subscription_id:, subscriptions_set_id:, command_name:, data:)
48
53
  sql = <<~SQL
49
- INSERT INTO subscription_commands (name, subscription_id, subscriptions_set_id)
50
- VALUES ($1, $2, $3)
54
+ INSERT INTO subscription_commands (name, subscription_id, subscriptions_set_id, data)
55
+ VALUES ($1, $2, $3, $4)
51
56
  RETURNING *
52
57
  SQL
53
58
  pg_result = connection.with do |conn|
54
- conn.exec_params(sql, [command_name, subscription_id, subscriptions_set_id])
59
+ conn.exec_params(sql, [command_name, subscription_id, subscriptions_set_id, data])
55
60
  end
56
61
  deserialize(pg_result.to_a.first)
57
62
  end
58
63
 
59
64
  # @param subscription_ids [Array<Integer>]
60
65
  # @param subscriptions_set_id [Integer]
61
- # @return [Array<Hash>]
66
+ # @return [Array<PgEventstore::SubscriptionRunnerCommands::Abstract>]
62
67
  def find_commands(subscription_ids, subscriptions_set_id:)
63
68
  return [] if subscription_ids.empty?
64
69
 
@@ -93,9 +98,10 @@ module PgEventstore
93
98
  end
94
99
 
95
100
  # @param hash [Hash]
96
- # @return [Hash]
101
+ # @return [PgEventstore::SubscriptionRunnerCommands::Base]
97
102
  def deserialize(hash)
98
- hash.transform_keys(&:to_sym)
103
+ attrs = hash.transform_keys(&:to_sym)
104
+ SubscriptionRunnerCommands.command_class(attrs[:name]).new(**attrs)
99
105
  end
100
106
  end
101
107
  end
@@ -11,16 +11,20 @@ module PgEventstore
11
11
  @connection = connection
12
12
  end
13
13
 
14
- # @return [Hash]
15
- def find_or_create_by(...)
14
+ # @param subscriptions_set_id [Integer]
15
+ # @param command_name [String]
16
+ # @param data [Hash]
17
+ # @return [PgEventstore::SubscriptionFeederCommands::Abstract]
18
+ def find_or_create_by(subscriptions_set_id:, command_name:, data:)
16
19
  transaction_queries.transaction do
17
- find_by(...) || create(...)
20
+ find_by(subscriptions_set_id: subscriptions_set_id, command_name: command_name) ||
21
+ create(subscriptions_set_id: subscriptions_set_id, command_name: command_name, data: data)
18
22
  end
19
23
  end
20
24
 
21
25
  # @param subscriptions_set_id [Integer]
22
26
  # @param command_name [String]
23
- # @return [Hash, nil]
27
+ # @return [PgEventstore::SubscriptionFeederCommands::Abstract, nil]
24
28
  def find_by(subscriptions_set_id:, command_name:)
25
29
  sql_builder =
26
30
  SQLBuilder.new.
@@ -37,21 +41,22 @@ module PgEventstore
37
41
 
38
42
  # @param subscriptions_set_id [Integer]
39
43
  # @param command_name [String]
40
- # @return [Hash]
41
- def create(subscriptions_set_id:, command_name:)
44
+ # @param data [Hash]
45
+ # @return [PgEventstore::SubscriptionFeederCommands::Abstract]
46
+ def create(subscriptions_set_id:, command_name:, data:)
42
47
  sql = <<~SQL
43
- INSERT INTO subscriptions_set_commands (name, subscriptions_set_id)
44
- VALUES ($1, $2)
48
+ INSERT INTO subscriptions_set_commands (name, subscriptions_set_id, data)
49
+ VALUES ($1, $2, $3)
45
50
  RETURNING *
46
51
  SQL
47
52
  pg_result = connection.with do |conn|
48
- conn.exec_params(sql, [command_name, subscriptions_set_id])
53
+ conn.exec_params(sql, [command_name, subscriptions_set_id, data])
49
54
  end
50
55
  deserialize(pg_result.to_a.first)
51
56
  end
52
57
 
53
58
  # @param subscriptions_set_id [Integer]
54
- # @return [Array<Hash>]
59
+ # @return [Array<PgEventstore::SubscriptionFeederCommands::Abstract>]
55
60
  def find_commands(subscriptions_set_id)
56
61
  sql_builder =
57
62
  SQLBuilder.new.select('*').
@@ -75,9 +80,10 @@ module PgEventstore
75
80
  private
76
81
 
77
82
  # @param hash [Hash]
78
- # @return [Hash]
83
+ # @return [PgEventstore::SubscriptionFeederCommands::Base]
79
84
  def deserialize(hash)
80
- hash.transform_keys(&:to_sym)
85
+ attrs = hash.transform_keys(&:to_sym)
86
+ SubscriptionFeederCommands.command_class(attrs[:name]).new(**attrs)
81
87
  end
82
88
 
83
89
  # @return [PgEventstore::TransactionQueries]
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionFeederCommands
5
+ # @!visibility private
6
+ class Base
7
+ include Extensions::OptionsExtension
8
+ include Extensions::BaseCommandExtension
9
+
10
+ # @!attribute id
11
+ # @return [Integer]
12
+ attribute(:id)
13
+ # @!attribute name
14
+ # @return [String]
15
+ attribute(:name) { self.class.name.split('::').last }
16
+ # @!attribute subscriptions_set_id
17
+ # @return [Integer]
18
+ attribute(:subscriptions_set_id)
19
+ # @!attribute data
20
+ # @return [Hash]
21
+ attribute(:data) { {} }
22
+ # @!attribute created_at
23
+ # @return [Time]
24
+ attribute(:created_at)
25
+
26
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
27
+ # @return [void]
28
+ def exec_cmd(subscription_feeder)
29
+ # Implement it in the subclass
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionFeederCommands
5
+ # @!visibility private
6
+ class Restore < Base
7
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
8
+ # @return [void]
9
+ def exec_cmd(subscription_feeder)
10
+ subscription_feeder.restore
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionFeederCommands
5
+ # @!visibility private
6
+ class StartAll < Base
7
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
8
+ # @return [void]
9
+ def exec_cmd(subscription_feeder)
10
+ subscription_feeder.start_all
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionFeederCommands
5
+ # @!visibility private
6
+ class Stop < Base
7
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
8
+ # @return [void]
9
+ def exec_cmd(subscription_feeder)
10
+ subscription_feeder.stop
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionFeederCommands
5
+ # @!visibility private
6
+ class StopAll < Base
7
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
8
+ # @return [void]
9
+ def exec_cmd(subscription_feeder)
10
+ subscription_feeder.stop_all
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'subscription_feeder_commands/base'
4
+ require_relative 'subscription_feeder_commands/restore'
5
+ require_relative 'subscription_feeder_commands/start_all'
6
+ require_relative 'subscription_feeder_commands/stop'
7
+ require_relative 'subscription_feeder_commands/stop_all'
8
+
9
+ module PgEventstore
10
+ # @!visibility private
11
+ module SubscriptionFeederCommands
12
+ extend Extensions::CommandClassLookupExtension
13
+ end
14
+ end
@@ -16,7 +16,8 @@ module PgEventstore
16
16
 
17
17
  attr_reader :subscription
18
18
 
19
- def_delegators :@events_processor, :start, :stop, :stop_async, :feed, :wait_for_finish, :restore, :state, :running?
19
+ def_delegators :@events_processor, :start, :stop, :stop_async, :feed, :wait_for_finish, :restore, :state, :running?,
20
+ :clear_chunk, :within_state
20
21
  def_delegators :@subscription, :lock!, :id
21
22
 
22
23
  # @param stats [PgEventstore::SubscriptionHandlerPerformance]
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionRunnerCommands
5
+ # @!visibility private
6
+ class Base
7
+ include Extensions::OptionsExtension
8
+ include Extensions::BaseCommandExtension
9
+
10
+ # @!attribute id
11
+ # @return [Integer]
12
+ attribute(:id)
13
+ # @!attribute name
14
+ # @return [String]
15
+ attribute(:name) { self.class.name.split('::').last }
16
+ # @!attribute subscription_id
17
+ # @return [Integer]
18
+ attribute(:subscription_id)
19
+ # @!attribute subscriptions_set_id
20
+ # @return [Integer]
21
+ attribute(:subscriptions_set_id)
22
+ # @!attribute data
23
+ # @return [Hash]
24
+ attribute(:data) { {} }
25
+ # @!attribute created_at
26
+ # @return [Time]
27
+ attribute(:created_at)
28
+
29
+ # @param subscription_runner [PgEventstore::SubscriptionRunner]
30
+ # @return [void]
31
+ def exec_cmd(subscription_runner)
32
+ # Implement it in the subclass
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionRunnerCommands
5
+ # @!visibility private
6
+ class ResetPosition < Base
7
+ class << self
8
+ # @param data [Hash]
9
+ # @return [Hash]
10
+ def parse_data(data)
11
+ { 'position' => Integer(data['position']) }
12
+ end
13
+ end
14
+
15
+ # @param subscription_runner [PgEventstore::SubscriptionRunner]
16
+ # @return [void]
17
+ def exec_cmd(subscription_runner)
18
+ subscription_runner.within_state(:stopped) do
19
+ subscription_runner.clear_chunk
20
+ subscription_runner.subscription.update(last_chunk_greatest_position: nil, current_position: data['position'])
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionRunnerCommands
5
+ # @!visibility private
6
+ class Restore < Base
7
+ # @param subscription_runner [PgEventstore::SubscriptionRunner]
8
+ # @return [void]
9
+ def exec_cmd(subscription_runner)
10
+ subscription_runner.restore
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionRunnerCommands
5
+ # @!visibility private
6
+ class Start < Base
7
+ # @param subscription_runner [PgEventstore::SubscriptionRunner]
8
+ # @return [void]
9
+ def exec_cmd(subscription_runner)
10
+ subscription_runner.start
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionRunnerCommands
5
+ # @!visibility private
6
+ class Stop < Base
7
+ # @param subscription_runner [PgEventstore::SubscriptionRunner]
8
+ # @return [void]
9
+ def exec_cmd(subscription_runner)
10
+ subscription_runner.stop_async
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'subscription_runner_commands/base'
4
+ require_relative 'subscription_runner_commands/reset_position'
5
+ require_relative 'subscription_runner_commands/restore'
6
+ require_relative 'subscription_runner_commands/start'
7
+ require_relative 'subscription_runner_commands/stop'
8
+
9
+ module PgEventstore
10
+ # @!visibility private
11
+ module SubscriptionRunnerCommands
12
+ extend Extensions::CommandClassLookupExtension
13
+ end
14
+ end
@@ -1,15 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'forwardable'
4
+ require_relative 'runner_state'
4
5
  require_relative 'basic_runner'
5
6
  require_relative 'subscription'
6
7
  require_relative 'events_processor'
7
8
  require_relative 'subscription_handler_performance'
8
9
  require_relative 'subscription_runner'
9
- require_relative 'runner_state'
10
10
  require_relative 'subscriptions_set'
11
11
  require_relative 'subscription_runners_feeder'
12
12
  require_relative 'subscription_feeder'
13
+ require_relative 'extensions/command_class_lookup_extension'
14
+ require_relative 'extensions/base_command_extension'
15
+ require_relative 'subscription_feeder_commands'
16
+ require_relative 'subscription_runner_commands'
17
+ require_relative 'queries/subscription_command_queries'
18
+ require_relative 'queries/subscription_queries'
19
+ require_relative 'queries/subscriptions_set_command_queries'
20
+ require_relative 'queries/subscriptions_set_queries'
13
21
  require_relative 'commands_handler'
14
22
 
15
23
  module PgEventstore
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgEventstore
4
- VERSION = "1.0.4"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -145,19 +145,25 @@ module PgEventstore
145
145
  end
146
146
 
147
147
  post '/subscription_cmd/:set_id/:id/:cmd' do
148
- puts SubscriptionCommandQueries.new(connection).find_or_create_by(
148
+ validate_subscription_cmd(params[:cmd])
149
+ cmd_class = SubscriptionRunnerCommands.command_class(params[:cmd])
150
+ SubscriptionCommandQueries.new(connection).find_or_create_by(
149
151
  subscriptions_set_id: params[:set_id],
150
152
  subscription_id: params[:id],
151
- command_name: CommandHandlers::SubscriptionRunnersCommands::AVAILABLE_COMMANDS.fetch(params[:cmd].to_sym)
153
+ command_name: cmd_class.new.name,
154
+ data: cmd_class.parse_data(Hash(params[:data]))
152
155
  )
153
156
 
154
157
  redirect redirect_back_url(fallback_url: url('/subscriptions'))
155
158
  end
156
159
 
157
160
  post '/subscriptions_set_cmd/:id/:cmd' do
161
+ validate_subscriptions_set_cmd(params[:cmd])
162
+ cmd_class = SubscriptionFeederCommands.command_class(params[:cmd])
158
163
  SubscriptionsSetCommandQueries.new(connection).find_or_create_by(
159
164
  subscriptions_set_id: params[:id],
160
- command_name: CommandHandlers::SubscriptionFeederCommands::AVAILABLE_COMMANDS.fetch(params[:cmd].to_sym)
165
+ command_name: cmd_class.new.name,
166
+ data: cmd_class.parse_data(Hash(params[:data]))
161
167
  )
162
168
 
163
169
  redirect redirect_back_url(fallback_url: url('/subscriptions'))
@@ -197,3 +197,21 @@ $(function(){
197
197
  handleMethod(e.target);
198
198
  });
199
199
  });
200
+
201
+ // Reset position action handling
202
+ $(function(){
203
+ "use strict";
204
+
205
+ let $resetPositionModal = $('#reset-position-modal');
206
+
207
+ $resetPositionModal.on('hide.bs.modal', function(){
208
+ $(this).find('.subscription-name').html('');
209
+ $(this).find('form').removeAttr('action');
210
+ });
211
+
212
+ $resetPositionModal.on('show.bs.modal', function(e){
213
+ let $clickedLink = $(e.relatedTarget);
214
+ $(this).find('.subscription-name').html($clickedLink.data('subscription-name'));
215
+ $(this).find('form').attr('action', $clickedLink.data('url'));
216
+ });
217
+ });
@@ -28,14 +28,36 @@ module PgEventstore
28
28
  url("/subscriptions_set_cmd/#{id}/#{cmd}")
29
29
  end
30
30
 
31
- # @return [Hash{Symbol => String}]
32
- def subscriptions_set_cmds
33
- CommandHandlers::SubscriptionFeederCommands::AVAILABLE_COMMANDS
31
+ # @param cmd_name [String] command name
32
+ # @return [String] command name
33
+ def subscriptions_set_cmd(cmd_name)
34
+ validate_subscriptions_set_cmd(cmd_name)
35
+
36
+ cmd_name
37
+ end
38
+
39
+ # @param cmd_name [String]
40
+ # @return [void]
41
+ # @raise [RuntimeError] in case if command class is not found
42
+ def validate_subscriptions_set_cmd(cmd_name)
43
+ cmd_class = SubscriptionFeederCommands.command_class(cmd_name)
44
+ raise "SubscriptionsSet command #{cmd_name.inspect} does not exist" unless cmd_class.known_command?
45
+ end
46
+
47
+ # @param cmd_name [String] command name
48
+ # @return [String] command name
49
+ def subscription_cmd(cmd_name)
50
+ validate_subscription_cmd(cmd_name)
51
+
52
+ cmd_name
34
53
  end
35
54
 
36
- # @return [Hash{Symbol => String}]
37
- def subscriptions_cmds
38
- CommandHandlers::SubscriptionRunnersCommands::AVAILABLE_COMMANDS
55
+ # @param cmd_name [String]
56
+ # @return [void]
57
+ # @raise [RuntimeError] in case if command class is not found
58
+ def validate_subscription_cmd(cmd_name)
59
+ cmd_class = SubscriptionRunnerCommands.command_class(cmd_name)
60
+ raise "Subscription command #{cmd_name.inspect} does not exist" unless cmd_class.known_command?
39
61
  end
40
62
 
41
63
  # @param state [String]
@@ -69,12 +69,12 @@
69
69
  <td><%= subscriptions_set.last_error_occurred_at %></td>
70
70
  <td>
71
71
  <% if subscriptions_set.state == PgEventstore::RunnerState::STATES[:dead] %>
72
- <a class="btn btn-success" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:Restore]) %>">
72
+ <a class="btn btn-success" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmd('Restore')) %>">
73
73
  Restore
74
74
  </a>
75
75
  <% end %>
76
76
  <% if PgEventstore::RunnerState::STATES.values_at(:running, :dead).include?(subscriptions_set.state) %>
77
- <a class="btn btn-warning" data-confirm="You are about to stop SubscriptionsSet#<%= subscriptions_set.id %>. This will also delete it and will result in stopping all related Subscriptions. Continue?" data-confirm-title="Stop SubscriptionsSet" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:Stop]) %>" data-toggle="tooltip" title="This action will delete Subscriptions Set and release all related Subscriptions.">
77
+ <a class="btn btn-warning" data-confirm="You are about to stop SubscriptionsSet#<%= subscriptions_set.id %>. This will also delete it and will result in stopping all related Subscriptions. Continue?" data-confirm-title="Stop SubscriptionsSet" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmd('Stop')) %>" data-toggle="tooltip" title="This action will delete Subscriptions Set and release all related Subscriptions.">
78
78
  Stop
79
79
  </a>
80
80
  <% end %>
@@ -101,10 +101,10 @@
101
101
  </div>
102
102
  <div class="float-right">
103
103
  <% if subscriptions_set.id %>
104
- <a class="btn btn-success" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:StartAll]) %>">
104
+ <a class="btn btn-success" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmd('StartAll')) %>">
105
105
  Start All
106
106
  </a>
107
- <a class="btn btn-danger" data-confirm="You are about to stop all Subscriptions. Continue?" data-confirm-title="Stop all Subscriptions" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:StopAll]) %>">
107
+ <a class="btn btn-danger" data-confirm="You are about to stop all Subscriptions. Continue?" data-confirm-title="Stop all Subscriptions" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmd('StopAll')) %>">
108
108
  Stop All
109
109
  </a>
110
110
  <% else %>
@@ -170,17 +170,20 @@
170
170
  <td>
171
171
  <% if subscriptions_set.id %>
172
172
  <% if subscription.state == PgEventstore::RunnerState::STATES[:stopped] %>
173
- <a class="btn btn-success" data-method="post" href="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscriptions_cmds[:Start]) %>">
173
+ <a class="btn btn-success" data-method="post" href="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscription_cmd('Start')) %>">
174
174
  Start
175
175
  </a>
176
+ <a class="btn btn-warning" href="#" data-toggle="modal" data-target="#reset-position-modal" data-subscription-name="<%= h subscription.name %>" data-url="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscription_cmd('ResetPosition')) %>">
177
+ Reset position
178
+ </a>
176
179
  <% end %>
177
180
  <% if PgEventstore::RunnerState::STATES.values_at(:running, :dead).include?(subscription.state) %>
178
- <a class="btn btn-warning" data-method="post" href="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscriptions_cmds[:Stop]) %>">
181
+ <a class="btn btn-warning" data-method="post" href="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscription_cmd('Stop')) %>">
179
182
  Stop
180
183
  </a>
181
184
  <% end %>
182
185
  <% if subscription.state == PgEventstore::RunnerState::STATES[:dead] %>
183
- <a class="btn btn-success" data-method="post" href="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscriptions_cmds[:Restore]) %>">
186
+ <a class="btn btn-success" data-method="post" href="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscription_cmd('Restore')) %>">
184
187
  Restore
185
188
  </a>
186
189
  <% end %>
@@ -218,3 +221,32 @@
218
221
  </div>
219
222
  </div>
220
223
  </div>
224
+
225
+ <div class="modal fade" id="reset-position-modal" tabindex="-1" role="dialog" aria-labelledby="reset-position-modal" aria-hidden="true">
226
+ <div class="modal-dialog modal-dialog-centered" role="document">
227
+ <div class="modal-content">
228
+ <div class="modal-header">
229
+ <h5 class="modal-title"> Reset Subscription's position</h5>
230
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
231
+ <span aria-hidden="true">&times;</span>
232
+ </button>
233
+ </div>
234
+ <div class="modal-body font-weight-bold text-break">
235
+ <span>Reset position of</span><pre class="subscription-name"></pre><span>Subscription?</span>
236
+ <form id="reset-position-form" data-parsley-validate="" class="form-horizontal form-label-left" novalidate="" method="POST">
237
+ <div class="item form-group">
238
+ <label class="col-form-label col-md-3 col-sm-3 label-align" for="subscription-position">Position <span class="required">*</span>
239
+ </label>
240
+ <div class="col-md-6 col-sm-6 ">
241
+ <input type="number" id="subscription-position" name="data[position]" required="required" autocomplete="off" value="0" class="form-control">
242
+ </div>
243
+ </div>
244
+ </form>
245
+ </div>
246
+ <div class="modal-footer">
247
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
248
+ <button type="submit" class="btn btn-primary" form="reset-position-form">Reset</button>
249
+ </div>
250
+ </div>
251
+ </div>
252
+ </div>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_eventstore
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Dzyzenko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-06-10 00:00:00.000000000 Z
11
+ date: 2024-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -69,6 +69,7 @@ files:
69
69
  - db/migrations/3_create_subscription_commands.sql
70
70
  - db/migrations/4_create_subscriptions_set_commands.sql
71
71
  - db/migrations/5_partitions.sql
72
+ - db/migrations/6_add_commands_data.sql
72
73
  - docs/appending_events.md
73
74
  - docs/configuration.md
74
75
  - docs/events_and_streams.md
@@ -107,10 +108,6 @@ files:
107
108
  - lib/pg_eventstore/queries/event_queries.rb
108
109
  - lib/pg_eventstore/queries/links_resolver.rb
109
110
  - lib/pg_eventstore/queries/partition_queries.rb
110
- - lib/pg_eventstore/queries/subscription_command_queries.rb
111
- - lib/pg_eventstore/queries/subscription_queries.rb
112
- - lib/pg_eventstore/queries/subscriptions_set_command_queries.rb
113
- - lib/pg_eventstore/queries/subscriptions_set_queries.rb
114
111
  - lib/pg_eventstore/queries/transaction_queries.rb
115
112
  - lib/pg_eventstore/query_builders/events_filtering_query.rb
116
113
  - lib/pg_eventstore/rspec/has_option_matcher.rb
@@ -122,11 +119,29 @@ files:
122
119
  - lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb
123
120
  - lib/pg_eventstore/subscriptions/commands_handler.rb
124
121
  - lib/pg_eventstore/subscriptions/events_processor.rb
122
+ - lib/pg_eventstore/subscriptions/extensions/base_command_extension.rb
123
+ - lib/pg_eventstore/subscriptions/extensions/command_class_lookup_extension.rb
124
+ - lib/pg_eventstore/subscriptions/queries/subscription_command_queries.rb
125
+ - lib/pg_eventstore/subscriptions/queries/subscription_queries.rb
126
+ - lib/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rb
127
+ - lib/pg_eventstore/subscriptions/queries/subscriptions_set_queries.rb
125
128
  - lib/pg_eventstore/subscriptions/runner_state.rb
126
129
  - lib/pg_eventstore/subscriptions/subscription.rb
127
130
  - lib/pg_eventstore/subscriptions/subscription_feeder.rb
131
+ - lib/pg_eventstore/subscriptions/subscription_feeder_commands.rb
132
+ - lib/pg_eventstore/subscriptions/subscription_feeder_commands/base.rb
133
+ - lib/pg_eventstore/subscriptions/subscription_feeder_commands/restore.rb
134
+ - lib/pg_eventstore/subscriptions/subscription_feeder_commands/start_all.rb
135
+ - lib/pg_eventstore/subscriptions/subscription_feeder_commands/stop.rb
136
+ - lib/pg_eventstore/subscriptions/subscription_feeder_commands/stop_all.rb
128
137
  - lib/pg_eventstore/subscriptions/subscription_handler_performance.rb
129
138
  - lib/pg_eventstore/subscriptions/subscription_runner.rb
139
+ - lib/pg_eventstore/subscriptions/subscription_runner_commands.rb
140
+ - lib/pg_eventstore/subscriptions/subscription_runner_commands/base.rb
141
+ - lib/pg_eventstore/subscriptions/subscription_runner_commands/reset_position.rb
142
+ - lib/pg_eventstore/subscriptions/subscription_runner_commands/restore.rb
143
+ - lib/pg_eventstore/subscriptions/subscription_runner_commands/start.rb
144
+ - lib/pg_eventstore/subscriptions/subscription_runner_commands/stop.rb
130
145
  - lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb
131
146
  - lib/pg_eventstore/subscriptions/subscriptions_manager.rb
132
147
  - lib/pg_eventstore/subscriptions/subscriptions_set.rb