pg_eventstore 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +49 -0
- data/db/migrations/7_support_reading_streams_system_stream.sql +2 -0
- data/docs/reading_events.md +16 -1
- data/docs/subscriptions.md +14 -56
- data/exe/pg-eventstore +16 -0
- data/lib/pg_eventstore/callbacks.rb +16 -0
- data/lib/pg_eventstore/cli/commands/base_command.rb +45 -0
- data/lib/pg_eventstore/cli/commands/callback_handlers/start_cmd_handlers.rb +38 -0
- data/lib/pg_eventstore/cli/commands/help_command.rb +22 -0
- data/lib/pg_eventstore/cli/commands/start_subscriptions_command.rb +96 -0
- data/lib/pg_eventstore/cli/commands/stop_subscriptions_command.rb +22 -0
- data/lib/pg_eventstore/cli/commands.rb +6 -0
- data/lib/pg_eventstore/cli/exit_codes.rb +12 -0
- data/lib/pg_eventstore/cli/parser_options/base_options.rb +46 -0
- data/lib/pg_eventstore/cli/parser_options/default_options.rb +10 -0
- data/lib/pg_eventstore/cli/parser_options/metadata.rb +34 -0
- data/lib/pg_eventstore/cli/parser_options/subscription_options.rb +31 -0
- data/lib/pg_eventstore/cli/parser_options.rb +6 -0
- data/lib/pg_eventstore/cli/parsers/base_parser.rb +33 -0
- data/lib/pg_eventstore/cli/parsers/default_parser.rb +24 -0
- data/lib/pg_eventstore/cli/parsers/subscription_parser.rb +24 -0
- data/lib/pg_eventstore/cli/parsers.rb +5 -0
- data/lib/pg_eventstore/cli/try_to_delete_subscriptions_set.rb +75 -0
- data/lib/pg_eventstore/cli/try_unlock_subscriptions_set.rb +16 -0
- data/lib/pg_eventstore/cli/wait_for_subscriptions_set_shutdown.rb +67 -0
- data/lib/pg_eventstore/cli.rb +42 -0
- data/lib/pg_eventstore/commands/read.rb +1 -1
- data/lib/pg_eventstore/extensions/options_extension.rb +53 -8
- data/lib/pg_eventstore/queries/event_queries.rb +1 -10
- data/lib/pg_eventstore/query_builders/events_filtering.rb +27 -0
- data/lib/pg_eventstore/rspec/has_option_matcher.rb +42 -41
- data/lib/pg_eventstore/stream.rb +8 -0
- data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +13 -1
- data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +1 -1
- data/lib/pg_eventstore/subscriptions/queries/subscription_command_queries.rb +1 -1
- data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rb +3 -1
- data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +10 -50
- data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/ping.rb +22 -0
- data/lib/pg_eventstore/subscriptions/subscription_feeder_commands.rb +1 -0
- data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +7 -2
- data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +44 -10
- data/lib/pg_eventstore/subscriptions/subscriptions_set_lifecycle.rb +1 -1
- data/lib/pg_eventstore/utils.rb +32 -0
- data/lib/pg_eventstore/version.rb +1 -1
- data/lib/pg_eventstore/web/application.rb +12 -2
- data/lib/pg_eventstore/web/paginator/events_collection.rb +18 -5
- data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +24 -2
- data/lib/pg_eventstore/web/views/home/dashboard.erb +9 -0
- data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +1 -1
- data/lib/pg_eventstore/web/views/home/partials/events.erb +9 -6
- data/lib/pg_eventstore/web/views/home/partials/stream_filter.erb +3 -3
- data/lib/pg_eventstore/web/views/home/partials/system_stream_filter.erb +15 -0
- data/lib/pg_eventstore/web/views/layouts/application.erb +3 -3
- data/lib/pg_eventstore/web/views/subscriptions/index.erb +6 -6
- data/lib/pg_eventstore.rb +5 -2
- data/sig/pg_eventstore/callbacks.rbs +4 -0
- data/sig/pg_eventstore/cli/commands/base_command.rbs +13 -0
- data/sig/pg_eventstore/cli/commands/callback_handlers/start_cmd_handlers.rbs +14 -0
- data/sig/pg_eventstore/cli/commands/help_command.rbs +13 -0
- data/sig/pg_eventstore/cli/commands/start_subscriptions_command.rbs +28 -0
- data/sig/pg_eventstore/cli/commands/stop_subscriptions_command.rbs +11 -0
- data/sig/pg_eventstore/cli/exit_codes.rbs +8 -0
- data/sig/pg_eventstore/cli/parser_options/base_options.rbs +15 -0
- data/sig/pg_eventstore/cli/parser_options/default_options.rbs +8 -0
- data/sig/pg_eventstore/cli/parser_options/metadata.rbs +11 -0
- data/sig/pg_eventstore/cli/parser_options/subscription_options.rbs +9 -0
- data/sig/pg_eventstore/cli/parsers/base_parser.rbs +18 -0
- data/sig/pg_eventstore/cli/parsers/default_parser.rbs +9 -0
- data/sig/pg_eventstore/cli/parsers/subscription_parser.rbs +9 -0
- data/sig/pg_eventstore/cli/try_to_delete_subscriptions_set.rbs +24 -0
- data/sig/pg_eventstore/cli/try_unlock_subscriptions_set.rbs +7 -0
- data/sig/pg_eventstore/cli/wait_for_subscriptions_set_shutdown.rbs +26 -0
- data/sig/pg_eventstore/cli.rbs +10 -0
- data/sig/pg_eventstore/extensions/options_extension.rbs +18 -1
- data/sig/pg_eventstore/queries/event_queries.rbs +0 -5
- data/sig/pg_eventstore/query_builders/events_filtering_query.rbs +6 -0
- data/sig/pg_eventstore/stream.rbs +3 -0
- data/sig/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rbs +8 -0
- data/sig/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rbs +7 -1
- data/sig/pg_eventstore/subscriptions/queries/subscription_command_queries.rbs +1 -1
- data/sig/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rbs +1 -1
- data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +10 -11
- data/sig/pg_eventstore/subscriptions/subscription_feeder_commands/ping.rbs +11 -0
- data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +1 -1
- data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +13 -1
- data/sig/pg_eventstore/utils.rbs +2 -0
- data/sig/pg_eventstore/web/application.rbs +27 -0
- data/sig/pg_eventstore/web/paginator/base_collection.rbs +0 -9
- data/sig/pg_eventstore/web/paginator/event_types_collection.rbs +9 -0
- data/sig/pg_eventstore/web/paginator/events_collection.rbs +3 -1
- data/sig/pg_eventstore.rbs +2 -8
- metadata +47 -3
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module CLI
|
5
|
+
module Parsers
|
6
|
+
class BaseParser
|
7
|
+
class << self
|
8
|
+
# @return [String]
|
9
|
+
def banner
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :args, :options
|
15
|
+
|
16
|
+
# @param args [Array<String>]
|
17
|
+
# @param options [PgEventstore::CLI::ParserOptions::BaseOptions]
|
18
|
+
def initialize(args, options)
|
19
|
+
@args = args
|
20
|
+
@options = options
|
21
|
+
@parser = ::OptionParser.new(self.class.banner)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Array<Array<String>, PgEventstore::CLI::ParserOptions::BaseOptions>] list of commands and parsed
|
25
|
+
# options
|
26
|
+
def parse
|
27
|
+
@options.attach_parser_handlers(@parser)
|
28
|
+
[@parser.parse(args), @options]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module CLI
|
5
|
+
module Parsers
|
6
|
+
class DefaultParser < BaseParser
|
7
|
+
class << self
|
8
|
+
# @return [String]
|
9
|
+
def banner
|
10
|
+
<<~TEXT
|
11
|
+
Usage: pg-eventstore [options]
|
12
|
+
pg-eventstore [command]
|
13
|
+
|
14
|
+
Commands:
|
15
|
+
subscriptions Start, stop subscriptions
|
16
|
+
|
17
|
+
Options:
|
18
|
+
TEXT
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module CLI
|
5
|
+
module Parsers
|
6
|
+
class SubscriptionParser < BaseParser
|
7
|
+
class << self
|
8
|
+
# @return [String]
|
9
|
+
def banner
|
10
|
+
<<~TEXT
|
11
|
+
Usage: pg-eventstore subscriptions [command] [options]
|
12
|
+
|
13
|
+
Commands:
|
14
|
+
start Start subscriptions. Example: pg-eventstore subscriptions start -r lib/my_subscriptions.rb
|
15
|
+
stop Stop subscriptions. Example: pg-eventstore subscriptions stop
|
16
|
+
|
17
|
+
Options:
|
18
|
+
TEXT
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module CLI
|
5
|
+
class TryToDeleteSubscriptionsSet
|
6
|
+
class << self
|
7
|
+
def try_to_delete(...)
|
8
|
+
new(...).try_to_delete
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :config_name, :subscriptions_set_id
|
13
|
+
|
14
|
+
# @param config_name [Symbol]
|
15
|
+
# @param subscriptions_set_id [Integer]
|
16
|
+
def initialize(config_name, subscriptions_set_id)
|
17
|
+
@config_name = config_name
|
18
|
+
@subscriptions_set_id = subscriptions_set_id
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Boolean] whether subscription was deleted
|
22
|
+
def try_to_delete
|
23
|
+
PgEventstore.logger&.info(
|
24
|
+
"Trying to delete SubscriptionsSet##{subscriptions_set_id}."
|
25
|
+
)
|
26
|
+
cmd_name = SubscriptionFeederCommands.command_class('Ping').new.name
|
27
|
+
subscriptions_set_commands_queries.find_or_create_by(
|
28
|
+
subscriptions_set_id: subscriptions_set_id, command_name: cmd_name, data: {}
|
29
|
+
)
|
30
|
+
# Potentially CommandsHandler can be dead exactly at the same moment we expect it to process "Ping" command.
|
31
|
+
# Wait for potential recover plus run interval and plus another second to allow potential processing of
|
32
|
+
# "Ping" command. "Ping" command comes in prio, so it is guaranteed it will be processed as a first command.
|
33
|
+
sleep CommandsHandler::RESTART_DELAY + CommandsHandler::PULL_INTERVAL + 1
|
34
|
+
if subscriptions_set_commands_queries.find_by(subscriptions_set_id: subscriptions_set_id, command_name: cmd_name)
|
35
|
+
# "Ping" command wasn't consumed. Related process must be dead.
|
36
|
+
subscriptions_set_queries.delete(subscriptions_set_id)
|
37
|
+
PgEventstore.logger&.info(
|
38
|
+
"SubscriptionsSet##{subscriptions_set_id} was deleted successfully. Proceeding with startup process."
|
39
|
+
)
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
|
43
|
+
PgEventstore.logger&.warn(
|
44
|
+
"Failed to delete SubscriptionsSet##{subscriptions_set_id}. It looks alive."
|
45
|
+
)
|
46
|
+
false
|
47
|
+
rescue RecordNotFound
|
48
|
+
# Failed to create "Ping" command because related SubscriptionsSet does not exist.
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# @return [PgEventstore::SubscriptionsSetQueries]
|
55
|
+
def subscriptions_set_queries
|
56
|
+
SubscriptionsSetQueries.new(connection)
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [PgEventstore::SubscriptionsSetCommandQueries]
|
60
|
+
def subscriptions_set_commands_queries
|
61
|
+
SubscriptionsSetCommandQueries.new(connection)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [PgEventstore::Connection]
|
65
|
+
def connection
|
66
|
+
PgEventstore.connection(config_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [PgEventstore::Config]
|
70
|
+
def config
|
71
|
+
PgEventstore.config(config_name)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'try_to_delete_subscriptions_set'
|
4
|
+
require_relative 'wait_for_subscriptions_set_shutdown'
|
5
|
+
|
6
|
+
module PgEventstore
|
7
|
+
module CLI
|
8
|
+
class TryUnlockSubscriptionsSet
|
9
|
+
class << self
|
10
|
+
def try_unlock(...)
|
11
|
+
TryToDeleteSubscriptionsSet.try_to_delete(...) || WaitForSubscriptionsSetShutdown.wait_for_shutdown(...)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module CLI
|
5
|
+
class WaitForSubscriptionsSetShutdown
|
6
|
+
class << self
|
7
|
+
def wait_for_shutdown(...)
|
8
|
+
new(...).wait_for_shutdown
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Float] seconds
|
13
|
+
SHUTDOWN_CHECK_INTERVAL = 2.0
|
14
|
+
|
15
|
+
attr_reader :config_name, :subscriptions_set_id
|
16
|
+
|
17
|
+
# @param config_name [Symbol]
|
18
|
+
# @param subscriptions_set_id [Integer]
|
19
|
+
def initialize(config_name, subscriptions_set_id)
|
20
|
+
@config_name = config_name
|
21
|
+
@subscriptions_set_id = subscriptions_set_id
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Boolean]
|
25
|
+
def wait_for_shutdown
|
26
|
+
PgEventstore.logger&.info(
|
27
|
+
"Trying to wait for shutdown of SubscriptionsSet##{subscriptions_set_id}."
|
28
|
+
)
|
29
|
+
deadline = Time.now.utc + config.subscription_graceful_shutdown_timeout
|
30
|
+
loop do
|
31
|
+
find_set!
|
32
|
+
return false if Time.now.utc > deadline
|
33
|
+
|
34
|
+
sleep SHUTDOWN_CHECK_INTERVAL
|
35
|
+
end
|
36
|
+
rescue RecordNotFound
|
37
|
+
PgEventstore.logger&.warn(
|
38
|
+
"SubscriptionsSet##{subscriptions_set_id} no longer exists. Proceeding with startup process."
|
39
|
+
)
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# @return [PgEventstore::SubscriptionsSetQueries]
|
46
|
+
def subscriptions_set_queries
|
47
|
+
SubscriptionsSetQueries.new(connection)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [PgEventstore::SubscriptionsSet]
|
51
|
+
# @raise [PgEventstore::RecordNotFound]
|
52
|
+
def find_set!
|
53
|
+
SubscriptionsSet.using_connection(config_name).new(**subscriptions_set_queries.find!(subscriptions_set_id))
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [PgEventstore::Connection]
|
57
|
+
def connection
|
58
|
+
PgEventstore.connection(config_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [PgEventstore::Config]
|
62
|
+
def config
|
63
|
+
PgEventstore.config(config_name)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require_relative 'cli/parsers'
|
5
|
+
require_relative 'cli/parser_options'
|
6
|
+
require_relative 'cli/try_unlock_subscriptions_set'
|
7
|
+
require_relative 'cli/exit_codes'
|
8
|
+
require_relative 'cli/commands'
|
9
|
+
|
10
|
+
module PgEventstore
|
11
|
+
module CLI
|
12
|
+
OPTIONS_PARSER = {
|
13
|
+
"subscriptions" => [Parsers::SubscriptionParser, ParserOptions::SubscriptionOptions].freeze
|
14
|
+
}.tap do |directions|
|
15
|
+
directions.default = [Parsers::DefaultParser, ParserOptions::DefaultOptions].freeze
|
16
|
+
end.freeze
|
17
|
+
|
18
|
+
COMMANDS = {
|
19
|
+
["subscriptions", "start"].freeze => Commands::StartSubscriptionsCommand,
|
20
|
+
["subscriptions", "stop"].freeze => Commands::StopSubscriptionsCommand
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
class << self
|
24
|
+
# @return [PgEventstore::Callbacks]
|
25
|
+
def callbacks
|
26
|
+
@callbacks ||= Callbacks.new
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param args [Array<String>]
|
30
|
+
# @return [Integer] exit code
|
31
|
+
def execute(args)
|
32
|
+
options_parser_class, options_class = OPTIONS_PARSER[args[0]]
|
33
|
+
command, parsed_options = options_parser_class.new(ARGV, options_class.new).parse
|
34
|
+
return Commands::HelpCommand.new(parsed_options).call if parsed_options.help
|
35
|
+
return COMMANDS[command].new(parsed_options).call if COMMANDS[command]
|
36
|
+
|
37
|
+
_, parsed_options = options_parser_class.new(['-h'], options_class.new).parse
|
38
|
+
Commands::HelpCommand.new(parsed_options).call
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -15,7 +15,7 @@ module PgEventstore
|
|
15
15
|
# @return [Array<PgEventstore::Event>]
|
16
16
|
# @raise [PgEventstore::StreamNotFoundError]
|
17
17
|
def call(stream, options: {})
|
18
|
-
queries.events.stream_revision(stream) || raise(StreamNotFoundError, stream) unless stream.
|
18
|
+
queries.events.stream_revision(stream) || raise(StreamNotFoundError, stream) unless stream.system?
|
19
19
|
|
20
20
|
queries.events.stream_events(stream, options)
|
21
21
|
end
|
@@ -40,14 +40,59 @@ module PgEventstore
|
|
40
40
|
#
|
41
41
|
# SomeClass.new(attr1: 'hihi', attr4: 'byebye')
|
42
42
|
module OptionsExtension
|
43
|
+
class Option
|
44
|
+
attr_reader :name, :metadata
|
45
|
+
|
46
|
+
# @param name [Symbol]
|
47
|
+
# @param metadata [Object, nil]
|
48
|
+
def initialize(name, metadata: nil)
|
49
|
+
@name = name
|
50
|
+
@metadata = metadata
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param other_option [Object]
|
54
|
+
# @return [Boolean]
|
55
|
+
def ==(other_option)
|
56
|
+
return false unless other_option.is_a?(Option)
|
57
|
+
|
58
|
+
name == other_option.name
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param other_option [Object]
|
62
|
+
# @return [Boolean]
|
63
|
+
def eql?(other_option)
|
64
|
+
return false unless other_option.is_a?(Option)
|
65
|
+
|
66
|
+
name.eql?(other_option.name)
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [Integer]
|
70
|
+
def hash
|
71
|
+
name.hash
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Options < Set
|
76
|
+
def add(o)
|
77
|
+
@hash[o] = o
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
# @param option [Symbol]
|
82
|
+
# @return [PgEventstore::Extensions::OptionsExtension::Option, nil]
|
83
|
+
def [](option)
|
84
|
+
@hash[Option.new(option)]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
43
88
|
# @!visibility private
|
44
89
|
module ClassMethods
|
45
90
|
# @param opt_name [Symbol] option name
|
46
91
|
# @param blk [Proc] provide define value using block. It will be later evaluated in the
|
47
92
|
# context of your object to determine the default value of the option
|
48
93
|
# @return [Symbol]
|
49
|
-
def option(opt_name, &blk)
|
50
|
-
self.options = (options +
|
94
|
+
def option(opt_name, metadata: nil, &blk)
|
95
|
+
self.options = (options + Options.new([Option.new(opt_name, metadata: metadata)])).freeze
|
51
96
|
warn_already_defined(opt_name)
|
52
97
|
warn_already_defined(:"#{opt_name}=")
|
53
98
|
define_method "#{opt_name}=" do |value|
|
@@ -67,7 +112,7 @@ module PgEventstore
|
|
67
112
|
|
68
113
|
def inherited(klass)
|
69
114
|
super
|
70
|
-
klass.options =
|
115
|
+
klass.options = Options.new(options).freeze
|
71
116
|
end
|
72
117
|
|
73
118
|
private
|
@@ -86,7 +131,7 @@ module PgEventstore
|
|
86
131
|
|
87
132
|
def self.included(klass)
|
88
133
|
klass.singleton_class.attr_accessor(:options)
|
89
|
-
klass.options =
|
134
|
+
klass.options = Options.new.freeze
|
90
135
|
klass.extend(ClassMethods)
|
91
136
|
end
|
92
137
|
|
@@ -100,7 +145,7 @@ module PgEventstore
|
|
100
145
|
# @return [Hash]
|
101
146
|
def options_hash
|
102
147
|
self.class.options.each_with_object({}) do |option, res|
|
103
|
-
res[option] = public_send(option)
|
148
|
+
res[option.name] = public_send(option.name)
|
104
149
|
end
|
105
150
|
end
|
106
151
|
alias attributes_hash options_hash
|
@@ -108,7 +153,7 @@ module PgEventstore
|
|
108
153
|
# @param opt_name [Symbol]
|
109
154
|
# @return [Boolean]
|
110
155
|
def readonly!(opt_name)
|
111
|
-
return false unless self.class.options.include?(opt_name)
|
156
|
+
return false unless self.class.options.include?(Option.new(opt_name))
|
112
157
|
|
113
158
|
@readonly.add(opt_name)
|
114
159
|
true
|
@@ -136,8 +181,8 @@ module PgEventstore
|
|
136
181
|
def init_default_values(options)
|
137
182
|
self.class.options.each do |option|
|
138
183
|
# init default values of options
|
139
|
-
value = options.key?(option) ? options[option] : public_send(option)
|
140
|
-
public_send("#{option}=", value)
|
184
|
+
value = options.key?(option.name) ? options[option.name] : public_send(option.name)
|
185
|
+
public_send("#{option.name}=", value)
|
141
186
|
end
|
142
187
|
end
|
143
188
|
end
|
@@ -70,7 +70,7 @@ module PgEventstore
|
|
70
70
|
# @param options [Hash]
|
71
71
|
# @return [Array<PgEventstore::Event>]
|
72
72
|
def stream_events(stream, options)
|
73
|
-
exec_params = events_filtering(stream, options).to_exec_params
|
73
|
+
exec_params = QueryBuilders::EventsFiltering.events_filtering(stream, options).to_exec_params
|
74
74
|
raw_events = connection.with do |conn|
|
75
75
|
conn.exec_params(*exec_params)
|
76
76
|
end.to_a
|
@@ -128,15 +128,6 @@ module PgEventstore
|
|
128
128
|
[sql_rows_for_insert, values]
|
129
129
|
end
|
130
130
|
|
131
|
-
# @param stream [PgEventstore::Stream]
|
132
|
-
# @param options [Hash]
|
133
|
-
# @return [PgEventstore::EventsFiltering]
|
134
|
-
def events_filtering(stream, options)
|
135
|
-
return QueryBuilders::EventsFiltering.all_stream_filtering(options) if stream.all_stream?
|
136
|
-
|
137
|
-
QueryBuilders::EventsFiltering.specific_stream_filtering(stream, options)
|
138
|
-
end
|
139
|
-
|
140
131
|
# @return [PgEventstore::LinksResolver]
|
141
132
|
def links_resolver
|
142
133
|
LinksResolver.new(connection)
|
@@ -21,6 +21,18 @@ module PgEventstore
|
|
21
21
|
SUBSCRIPTIONS_OPTIONS = %i[from_position resolve_link_tos filter max_count].freeze
|
22
22
|
|
23
23
|
class << self
|
24
|
+
# @param stream [PgEventstore::Stream]
|
25
|
+
# @param options [Hash]
|
26
|
+
# @return [PgEventstore::EventsFiltering]
|
27
|
+
def events_filtering(stream, options)
|
28
|
+
return all_stream_filtering(options) if stream.all_stream?
|
29
|
+
if stream.system? && Stream::KNOWN_SYSTEM_STREAMS.include?(stream.context)
|
30
|
+
return system_stream_filtering(stream, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
specific_stream_filtering(stream, options)
|
34
|
+
end
|
35
|
+
|
24
36
|
# @param options [Hash]
|
25
37
|
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
26
38
|
def subscriptions_events_filtering(options)
|
@@ -54,6 +66,15 @@ module PgEventstore
|
|
54
66
|
event_filter.add_stream_direction(options[:direction])
|
55
67
|
event_filter
|
56
68
|
end
|
69
|
+
|
70
|
+
# @param stream [PgEventstore::Stream] system stream
|
71
|
+
# @param options [Hash]
|
72
|
+
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
73
|
+
def system_stream_filtering(stream, options)
|
74
|
+
all_stream_filtering(options).tap do |event_filter|
|
75
|
+
event_filter.set_source(stream.context)
|
76
|
+
end
|
77
|
+
end
|
57
78
|
end
|
58
79
|
|
59
80
|
def initialize
|
@@ -139,6 +160,12 @@ module PgEventstore
|
|
139
160
|
@sql_builder.to_exec_params
|
140
161
|
end
|
141
162
|
|
163
|
+
# @param table_name [String] system stream view name
|
164
|
+
# @return [void]
|
165
|
+
def set_source(table_name)
|
166
|
+
@sql_builder.from(%{ "#{PG::Connection.escape(table_name)}" events })
|
167
|
+
end
|
168
|
+
|
142
169
|
private
|
143
170
|
|
144
171
|
# @param stream_attrs [Hash]
|
@@ -1,15 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# This matcher is defined to test options which are defined by using
|
4
|
-
#
|
4
|
+
# PgEventstore::Extensions::OptionsExtension option. Example:
|
5
5
|
# Let's say you have next class
|
6
6
|
# class SomeClass
|
7
7
|
# include PgEventstore::Extensions::OptionsExtension
|
8
8
|
#
|
9
|
-
# option(:some_opt) { '1' }
|
9
|
+
# option(:some_opt, metadata: { foo: :bar }) { '1' }
|
10
10
|
# end
|
11
11
|
#
|
12
|
-
# To test that its instance has the proper option with the proper default value you can use this
|
12
|
+
# To test that its instance has the proper option with the proper default value and proper metadata you can use this
|
13
13
|
# matcher:
|
14
14
|
# RSpec.describe SomeClass do
|
15
15
|
# subject { described_class.new }
|
@@ -17,53 +17,50 @@
|
|
17
17
|
# # Check that :some_opt is present
|
18
18
|
# it { is_expected.to have_option(:some_opt) }
|
19
19
|
# # Check that :some_opt is present and has the correct default value
|
20
|
-
# it { is_expected.to have_option(:some_opt).with_default_value('1') }
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# If you have more complex implementation of default value of your option - you should handle it
|
24
|
-
# customly. For example:
|
25
|
-
# class SomeClass
|
26
|
-
# include PgEventstore::Extensions::OptionsExtension
|
27
|
-
#
|
28
|
-
# option(:some_opt) { calc_value }
|
29
|
-
# end
|
30
|
-
# You could test it like so:
|
31
|
-
# RSpec.described SomeClass do
|
32
|
-
# let(:instance) { described_class.new }
|
33
|
-
#
|
34
|
-
# describe ':some_opt default value' do
|
35
|
-
# subject { instance.some_opt }
|
36
|
-
#
|
37
|
-
# let(:value) { 'some val' }
|
38
|
-
#
|
39
|
-
# before do
|
40
|
-
# allow(instance).to receive(:calc_value).and_return(value)
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# it { is_expected.to eq(value) }
|
44
|
-
# end
|
20
|
+
# it { is_expected.to have_option(:some_opt).with_default_value('1').with_metadata(foo: :bar) }
|
45
21
|
# end
|
46
22
|
RSpec::Matchers.define :has_option do |option_name|
|
47
23
|
match do |obj|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
24
|
+
option = obj.class.options[option_name]
|
25
|
+
is_correct = obj.class.respond_to?(:options) && option
|
26
|
+
if defined?(@default_value)
|
27
|
+
is_correct &&=
|
28
|
+
RSpec::Matchers::BuiltIn::Match.new(@default_value).matches?(obj.class.allocate.public_send(option_name))
|
53
29
|
end
|
30
|
+
if defined?(@metadata)
|
31
|
+
is_correct &&= RSpec::Matchers::BuiltIn::Match.new(@metadata).matches?(option.metadata)
|
32
|
+
end
|
33
|
+
is_correct
|
54
34
|
end
|
55
35
|
|
56
36
|
failure_message do |obj|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
37
|
+
option = obj.class.options[option_name]
|
38
|
+
option_presence = obj.class.respond_to?(:options) && option
|
39
|
+
|
40
|
+
default_value_message = "with default value #{@default_value.inspect}"
|
41
|
+
metadata_message = "with metadata #{@metadata.inspect}"
|
42
|
+
message = "Expected #{obj.class} to have `#{option_name.inspect}' option"
|
43
|
+
message += " #{default_value_message}," if defined?(@default_value)
|
44
|
+
message += " #{metadata_message}," if defined?(@metadata)
|
45
|
+
message += "," unless defined?(@metadata) || defined?(@default_value)
|
46
|
+
if option_presence
|
47
|
+
actual_default_value = obj.class.allocate.public_send(option_name)
|
48
|
+
actual_metadata = option.metadata
|
49
|
+
default_value_matches = RSpec::Matchers::BuiltIn::Match.new(@default_value).matches?(actual_default_value)
|
50
|
+
metadata_matches = RSpec::Matchers::BuiltIn::Match.new(@metadata).matches?(actual_metadata)
|
51
|
+
|
52
|
+
case [default_value_matches, metadata_matches]
|
53
|
+
when [false, true]
|
54
|
+
message += " but default value is #{actual_default_value.inspect}"
|
55
|
+
when [true, false]
|
56
|
+
message += " but metadata is #{actual_metadata.inspect}"
|
57
|
+
else
|
58
|
+
message += " but default value is #{actual_default_value.inspect} and metadata is #{actual_metadata.inspect}"
|
59
|
+
end
|
62
60
|
else
|
63
|
-
|
61
|
+
message += " but there is no option found with the given name"
|
64
62
|
end
|
65
|
-
|
66
|
-
msg
|
63
|
+
message
|
67
64
|
end
|
68
65
|
|
69
66
|
description do
|
@@ -83,6 +80,10 @@ RSpec::Matchers.define :has_option do |option_name|
|
|
83
80
|
chain :with_default_value do |val|
|
84
81
|
@default_value = val
|
85
82
|
end
|
83
|
+
|
84
|
+
chain :with_metadata do |val|
|
85
|
+
@metadata = val
|
86
|
+
end
|
86
87
|
end
|
87
88
|
|
88
89
|
RSpec::Matchers.alias_matcher :have_option, :has_option
|
data/lib/pg_eventstore/stream.rb
CHANGED
@@ -8,6 +8,8 @@ module PgEventstore
|
|
8
8
|
SYSTEM_STREAM_PREFIX = '$'
|
9
9
|
# @return [Integer]
|
10
10
|
NON_EXISTING_STREAM_REVISION = -1
|
11
|
+
# @return [Array<String>]
|
12
|
+
KNOWN_SYSTEM_STREAMS = %w[$streams].freeze
|
11
13
|
|
12
14
|
class << self
|
13
15
|
# Produces "all" stream instance. "all" stream does not represent any specific stream. Instead, it indicates that
|
@@ -18,6 +20,12 @@ module PgEventstore
|
|
18
20
|
stream.instance_variable_set(:@all_stream, true)
|
19
21
|
end
|
20
22
|
end
|
23
|
+
|
24
|
+
# @param name [String]
|
25
|
+
# @return [PgEventstore::Stream]
|
26
|
+
def system_stream(name)
|
27
|
+
new(context: name, stream_name: '', stream_id: '')
|
28
|
+
end
|
21
29
|
end
|
22
30
|
|
23
31
|
# @!attribute context
|
@@ -13,7 +13,7 @@ module PgEventstore
|
|
13
13
|
# Look up commands for the given SubscriptionFeeder and execute them
|
14
14
|
# @return [void]
|
15
15
|
def process
|
16
|
-
|
16
|
+
commands.each do |command|
|
17
17
|
command.exec_cmd(@subscription_feeder)
|
18
18
|
queries.delete(command.id)
|
19
19
|
end
|
@@ -21,6 +21,18 @@ module PgEventstore
|
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
|
+
# @return [Array<PgEventstore::SubscriptionFeederCommands::Base>]
|
25
|
+
def commands
|
26
|
+
commands = queries.find_commands(@subscription_feeder.id)
|
27
|
+
ping_cmd = commands.find do |cmd|
|
28
|
+
cmd.name == 'Ping'
|
29
|
+
end
|
30
|
+
return commands unless ping_cmd
|
31
|
+
|
32
|
+
# "Ping" command should go in prio
|
33
|
+
[ping_cmd, *(commands - [ping_cmd])]
|
34
|
+
end
|
35
|
+
|
24
36
|
# @return [PgEventstore::SubscriptionsSetCommandQueries]
|
25
37
|
def queries
|
26
38
|
SubscriptionsSetCommandQueries.new(connection)
|
@@ -5,7 +5,7 @@ module PgEventstore
|
|
5
5
|
class SubscriptionRunnersCommands
|
6
6
|
# @param config_name [Symbol]
|
7
7
|
# @param runners [Array<PgEventstore::SubscriptionRunner>]
|
8
|
-
# @param subscriptions_set_id [Integer]
|
8
|
+
# @param subscriptions_set_id [Integer, nil]
|
9
9
|
def initialize(config_name, runners, subscriptions_set_id)
|
10
10
|
@config_name = config_name
|
11
11
|
@runners = runners.to_h { |runner| [runner.id, runner] }
|
@@ -64,7 +64,7 @@ module PgEventstore
|
|
64
64
|
end
|
65
65
|
|
66
66
|
# @param subscription_ids [Array<Integer>]
|
67
|
-
# @param subscriptions_set_id [Integer]
|
67
|
+
# @param subscriptions_set_id [Integer, nil]
|
68
68
|
# @return [Array<PgEventstore::SubscriptionRunnerCommands::Base>]
|
69
69
|
def find_commands(subscription_ids, subscriptions_set_id:)
|
70
70
|
return [] if subscription_ids.empty?
|