pg_eventstore 0.3.0 → 0.5.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 +14 -0
- data/CODE_OF_CONDUCT.md +1 -1
- data/README.md +2 -0
- data/db/migrations/10_create_subscription_commands.sql +15 -0
- data/db/migrations/11_create_subscriptions_set_commands.sql +15 -0
- data/db/migrations/12_improve_events_indexes.sql +1 -0
- data/db/migrations/13_remove_duplicated_index.sql +1 -0
- data/db/migrations/9_create_subscriptions.sql +46 -0
- data/docs/configuration.md +42 -21
- data/docs/linking_events.md +96 -0
- data/docs/reading_events.md +56 -0
- data/docs/subscriptions.md +170 -0
- data/lib/pg_eventstore/callbacks.rb +122 -0
- data/lib/pg_eventstore/client.rb +32 -2
- data/lib/pg_eventstore/commands/append.rb +3 -11
- data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +22 -0
- data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +24 -0
- data/lib/pg_eventstore/commands/link_to.rb +33 -0
- data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +63 -0
- data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +62 -0
- data/lib/pg_eventstore/commands.rb +5 -0
- data/lib/pg_eventstore/config.rb +35 -3
- data/lib/pg_eventstore/errors.rb +80 -0
- data/lib/pg_eventstore/{pg_result_deserializer.rb → event_deserializer.rb} +10 -22
- data/lib/pg_eventstore/extensions/callbacks_extension.rb +95 -0
- data/lib/pg_eventstore/extensions/options_extension.rb +69 -29
- data/lib/pg_eventstore/extensions/using_connection_extension.rb +35 -0
- data/lib/pg_eventstore/pg_connection.rb +20 -3
- data/lib/pg_eventstore/queries/event_queries.rb +18 -34
- data/lib/pg_eventstore/queries/event_type_queries.rb +24 -0
- data/lib/pg_eventstore/queries/preloader.rb +37 -0
- data/lib/pg_eventstore/queries/stream_queries.rb +14 -1
- data/lib/pg_eventstore/queries/subscription_command_queries.rb +81 -0
- data/lib/pg_eventstore/queries/subscription_queries.rb +166 -0
- data/lib/pg_eventstore/queries/subscriptions_set_command_queries.rb +76 -0
- data/lib/pg_eventstore/queries/subscriptions_set_queries.rb +89 -0
- data/lib/pg_eventstore/queries.rb +7 -0
- data/lib/pg_eventstore/query_builders/events_filtering_query.rb +17 -22
- data/lib/pg_eventstore/sql_builder.rb +54 -10
- data/lib/pg_eventstore/stream.rb +2 -1
- data/lib/pg_eventstore/subscriptions/basic_runner.rb +220 -0
- data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +52 -0
- data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +68 -0
- data/lib/pg_eventstore/subscriptions/commands_handler.rb +62 -0
- data/lib/pg_eventstore/subscriptions/events_processor.rb +72 -0
- data/lib/pg_eventstore/subscriptions/runner_state.rb +45 -0
- data/lib/pg_eventstore/subscriptions/subscription.rb +141 -0
- data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +171 -0
- data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +39 -0
- data/lib/pg_eventstore/subscriptions/subscription_runner.rb +125 -0
- data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +38 -0
- data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +105 -0
- data/lib/pg_eventstore/subscriptions/subscriptions_set.rb +97 -0
- data/lib/pg_eventstore/tasks/setup.rake +5 -1
- data/lib/pg_eventstore/utils.rb +66 -0
- data/lib/pg_eventstore/version.rb +1 -1
- data/lib/pg_eventstore.rb +19 -1
- metadata +38 -4
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
# @!visibility private
|
5
|
+
class SubscriptionQueries
|
6
|
+
attr_reader :connection
|
7
|
+
private :connection
|
8
|
+
|
9
|
+
# @param connection [PgEventstore::Connection]
|
10
|
+
def initialize(connection)
|
11
|
+
@connection = connection
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param attrs [Hash]
|
15
|
+
# @return [Hash]
|
16
|
+
def find_or_create_by(attrs)
|
17
|
+
transaction_queries.transaction do
|
18
|
+
find_by(attrs) || create(attrs)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param attrs [Hash]
|
23
|
+
# @return [Hash, nil]
|
24
|
+
def find_by(attrs)
|
25
|
+
builder = SQLBuilder.new.select('*').from('subscriptions')
|
26
|
+
attrs.each do |attr, val|
|
27
|
+
builder.where("#{attr} = ?", val)
|
28
|
+
end
|
29
|
+
|
30
|
+
pg_result = connection.with do |conn|
|
31
|
+
conn.exec_params(*builder.to_exec_params)
|
32
|
+
end
|
33
|
+
return if pg_result.ntuples.zero?
|
34
|
+
|
35
|
+
deserialize(pg_result.to_a.first)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param id [Integer]
|
39
|
+
# @return [Hash]
|
40
|
+
# @raise [PgEventstore::RecordNotFound]
|
41
|
+
def find!(id)
|
42
|
+
find_by(id: id) || raise(RecordNotFound.new("subscriptions", id))
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param attrs [Hash]
|
46
|
+
# @return [Hash]
|
47
|
+
def create(attrs)
|
48
|
+
sql = <<~SQL
|
49
|
+
INSERT INTO subscriptions (#{attrs.keys.join(', ')})
|
50
|
+
VALUES (#{Utils.positional_vars(attrs.values)})
|
51
|
+
RETURNING *
|
52
|
+
SQL
|
53
|
+
pg_result = connection.with do |conn|
|
54
|
+
conn.exec_params(sql, attrs.values)
|
55
|
+
end
|
56
|
+
deserialize(pg_result.to_a.first)
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param id [Integer]
|
60
|
+
# @param attrs [Hash]
|
61
|
+
def update(id, attrs)
|
62
|
+
attrs = { updated_at: Time.now.utc }.merge(attrs)
|
63
|
+
attrs_sql = attrs.keys.map.with_index(1) do |attr, index|
|
64
|
+
"#{attr} = $#{index}"
|
65
|
+
end.join(', ')
|
66
|
+
sql =
|
67
|
+
"UPDATE subscriptions SET #{attrs_sql} WHERE id = $#{attrs.keys.size + 1} RETURNING *"
|
68
|
+
pg_result = connection.with do |conn|
|
69
|
+
conn.exec_params(sql, [*attrs.values, id])
|
70
|
+
end
|
71
|
+
raise(RecordNotFound.new("subscriptions", id)) if pg_result.ntuples.zero?
|
72
|
+
|
73
|
+
deserialize(pg_result.to_a.first)
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param query_options [Array<Array<Integer, Hash>>] array of runner ids and query options
|
77
|
+
# @return [Array<Hash>] array of raw events
|
78
|
+
def subscriptions_events(query_options)
|
79
|
+
return [] if query_options.empty?
|
80
|
+
|
81
|
+
final_builder = union_builders(query_options.map { |id, opts| query_builder(id, opts) })
|
82
|
+
raw_events = connection.with do |conn|
|
83
|
+
conn.exec_params(*final_builder.to_exec_params)
|
84
|
+
end.to_a
|
85
|
+
preloader.preload_related_objects(raw_events)
|
86
|
+
end
|
87
|
+
|
88
|
+
# @param id [Integer] subscription's id
|
89
|
+
# @param lock_id [String] UUIDv4 id of the subscriptions set which reserves the subscription
|
90
|
+
# @param force [Boolean] whether to lock the subscription despite on #locked_by value
|
91
|
+
# @return [String] UUIDv4 lock id
|
92
|
+
# @raise [SubscriptionAlreadyLockedError] in case the Subscription is already locked
|
93
|
+
def lock!(id, lock_id, force = false)
|
94
|
+
transaction_queries.transaction do
|
95
|
+
attrs = find!(id)
|
96
|
+
if attrs[:locked_by] && !force
|
97
|
+
raise SubscriptionAlreadyLockedError.new(attrs[:set], attrs[:name], attrs[:locked_by])
|
98
|
+
end
|
99
|
+
connection.with do |conn|
|
100
|
+
conn.exec_params('UPDATE subscriptions SET locked_by = $1 WHERE id = $2', [lock_id, id])
|
101
|
+
end
|
102
|
+
end
|
103
|
+
lock_id
|
104
|
+
end
|
105
|
+
|
106
|
+
# @param id [Integer] subscription's id
|
107
|
+
# @param lock_id [String] UUIDv4 id of the set which reserved the subscription after itself
|
108
|
+
# @return [void]
|
109
|
+
# @raise [SubscriptionUnlockError] in case the Subscription is locked by some SubscriptionsSet, other than the one,
|
110
|
+
# persisted in memory
|
111
|
+
def unlock!(id, lock_id)
|
112
|
+
transaction_queries.transaction do
|
113
|
+
attrs = find!(id)
|
114
|
+
# Normally this should never happen as locking/unlocking happens within the same process. This is done only for
|
115
|
+
# the matter of consistency.
|
116
|
+
unless attrs[:locked_by] == lock_id
|
117
|
+
raise SubscriptionUnlockError.new(attrs[:set], attrs[:name], lock_id, attrs[:locked_by])
|
118
|
+
end
|
119
|
+
connection.with do |conn|
|
120
|
+
conn.exec_params('UPDATE subscriptions SET locked_by = $1 WHERE id = $2', [nil, id])
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
# @param id [Integer] runner id
|
128
|
+
# @param options [Hash] query options
|
129
|
+
# @return [PgEventstore::SQLBuilder]
|
130
|
+
def query_builder(id, options)
|
131
|
+
builder = PgEventstore::QueryBuilders::EventsFiltering.subscriptions_events_filtering(
|
132
|
+
event_type_queries.include_event_types_ids(options)
|
133
|
+
).to_sql_builder
|
134
|
+
builder.select("#{id} as runner_id")
|
135
|
+
end
|
136
|
+
|
137
|
+
# @param builders [Array<PgEventstore::SQLBuilder>]
|
138
|
+
# @return [PgEventstore::SQLBuilder]
|
139
|
+
def union_builders(builders)
|
140
|
+
builders[1..].each_with_object(builders[0]) do |builder, first_builder|
|
141
|
+
first_builder.union(builder)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# @return [PgEventstore::TransactionQueries]
|
146
|
+
def transaction_queries
|
147
|
+
TransactionQueries.new(connection)
|
148
|
+
end
|
149
|
+
|
150
|
+
# @return [PgEventstore::EventTypeQueries]
|
151
|
+
def event_type_queries
|
152
|
+
EventTypeQueries.new(connection)
|
153
|
+
end
|
154
|
+
|
155
|
+
# @return [PgEventstore::Preloader]
|
156
|
+
def preloader
|
157
|
+
Preloader.new(connection)
|
158
|
+
end
|
159
|
+
|
160
|
+
# @param hash [Hash]
|
161
|
+
# @return [Hash]
|
162
|
+
def deserialize(hash)
|
163
|
+
hash.transform_keys(&:to_sym)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
# @!visibility private
|
5
|
+
class SubscriptionsSetCommandQueries
|
6
|
+
attr_reader :connection
|
7
|
+
private :connection
|
8
|
+
|
9
|
+
# @param connection [PgEventstore::Connection]
|
10
|
+
def initialize(connection)
|
11
|
+
@connection = connection
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param subscriptions_set_id [Integer]
|
15
|
+
# @param command_name [String]
|
16
|
+
# @return [Hash, nil]
|
17
|
+
def find_by(subscriptions_set_id:, command_name:)
|
18
|
+
sql_builder =
|
19
|
+
SQLBuilder.new.
|
20
|
+
select('*').
|
21
|
+
from('subscriptions_set_commands').
|
22
|
+
where('subscriptions_set_id = ? AND name = ?', subscriptions_set_id, command_name)
|
23
|
+
pg_result = connection.with do |conn|
|
24
|
+
conn.exec_params(*sql_builder.to_exec_params)
|
25
|
+
end
|
26
|
+
return if pg_result.ntuples.zero?
|
27
|
+
|
28
|
+
deserialize(pg_result.to_a.first)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param subscriptions_set_id [Integer]
|
32
|
+
# @param command_name [String]
|
33
|
+
# @return [Hash]
|
34
|
+
def create_by(subscriptions_set_id:, command_name:)
|
35
|
+
sql = <<~SQL
|
36
|
+
INSERT INTO subscriptions_set_commands (name, subscriptions_set_id)
|
37
|
+
VALUES ($1, $2)
|
38
|
+
RETURNING *
|
39
|
+
SQL
|
40
|
+
pg_result = connection.with do |conn|
|
41
|
+
conn.exec_params(sql, [command_name, subscriptions_set_id])
|
42
|
+
end
|
43
|
+
deserialize(pg_result.to_a.first)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param subscriptions_set_id [Integer]
|
47
|
+
# @return [Array<Hash>]
|
48
|
+
def find_commands(subscriptions_set_id)
|
49
|
+
sql_builder =
|
50
|
+
SQLBuilder.new.select('*').
|
51
|
+
from('subscriptions_set_commands').
|
52
|
+
where("subscriptions_set_id = ?", subscriptions_set_id).
|
53
|
+
order('id ASC')
|
54
|
+
pg_result = connection.with do |conn|
|
55
|
+
conn.exec_params(*sql_builder.to_exec_params)
|
56
|
+
end
|
57
|
+
pg_result.to_a.map(&method(:deserialize))
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param id [Integer]
|
61
|
+
# @return [void]
|
62
|
+
def delete(id)
|
63
|
+
connection.with do |conn|
|
64
|
+
conn.exec_params('DELETE FROM subscriptions_set_commands WHERE id = $1', [id])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# @param hash [Hash]
|
71
|
+
# @return [Hash]
|
72
|
+
def deserialize(hash)
|
73
|
+
hash.transform_keys(&:to_sym)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
# @!visibility private
|
5
|
+
class SubscriptionsSetQueries
|
6
|
+
attr_reader :connection
|
7
|
+
private :connection
|
8
|
+
|
9
|
+
# @param connection [PgEventstore::Connection]
|
10
|
+
def initialize(connection)
|
11
|
+
@connection = connection
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param attrs [Hash]
|
15
|
+
# @return [Array<Hash>]
|
16
|
+
def find_all(attrs)
|
17
|
+
builder = SQLBuilder.new.select('*').from('subscriptions_set')
|
18
|
+
attrs.each do |attr, val|
|
19
|
+
builder.where("#{attr} = ?", val)
|
20
|
+
end
|
21
|
+
|
22
|
+
pg_result = connection.with do |conn|
|
23
|
+
conn.exec_params(*builder.to_exec_params)
|
24
|
+
end
|
25
|
+
pg_result.to_a.map(&method(:deserialize))
|
26
|
+
end
|
27
|
+
|
28
|
+
# The same as #find_all, but returns first result
|
29
|
+
# @return [Hash, nil]
|
30
|
+
def find_by(...)
|
31
|
+
find_all(...).first
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param id [String] UUIDv4
|
35
|
+
# @return [Hash]
|
36
|
+
# @raise [PgEventstore::RecordNotFound]
|
37
|
+
def find!(id)
|
38
|
+
find_by(id: id) || raise(RecordNotFound.new("subscriptions_set", id))
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param attrs [Hash]
|
42
|
+
# @return [Hash]
|
43
|
+
def create(attrs)
|
44
|
+
sql = <<~SQL
|
45
|
+
INSERT INTO subscriptions_set (#{attrs.keys.join(', ')})
|
46
|
+
VALUES (#{Utils.positional_vars(attrs.values)})
|
47
|
+
RETURNING *
|
48
|
+
SQL
|
49
|
+
pg_result = connection.with do |conn|
|
50
|
+
conn.exec_params(sql, attrs.values)
|
51
|
+
end
|
52
|
+
deserialize(pg_result.to_a.first)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param id [String] UUIDv4
|
56
|
+
# @param attrs [Hash]
|
57
|
+
def update(id, attrs)
|
58
|
+
attrs = { updated_at: Time.now.utc }.merge(attrs)
|
59
|
+
attrs_sql = attrs.keys.map.with_index(1) do |attr, index|
|
60
|
+
"#{attr} = $#{index}"
|
61
|
+
end.join(', ')
|
62
|
+
sql = <<~SQL
|
63
|
+
UPDATE subscriptions_set SET #{attrs_sql} WHERE id = $#{attrs.keys.size + 1} RETURNING *
|
64
|
+
SQL
|
65
|
+
pg_result = connection.with do |conn|
|
66
|
+
conn.exec_params(sql, [*attrs.values, id])
|
67
|
+
end
|
68
|
+
raise(RecordNotFound.new("subscriptions_set", id)) if pg_result.ntuples.zero?
|
69
|
+
|
70
|
+
deserialize(pg_result.to_a.first)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return id [Integer]
|
74
|
+
# @return [void]
|
75
|
+
def delete(id)
|
76
|
+
connection.with do |conn|
|
77
|
+
conn.exec_params('DELETE FROM subscriptions_set WHERE id = $1', [id])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# @param hash [Hash]
|
84
|
+
# @return [Hash]
|
85
|
+
def deserialize(hash)
|
86
|
+
hash.transform_keys(&:to_sym)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -1,9 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'sql_builder'
|
4
|
+
require_relative 'query_builders/events_filtering_query'
|
3
5
|
require_relative 'queries/transaction_queries'
|
4
6
|
require_relative 'queries/event_queries'
|
5
7
|
require_relative 'queries/stream_queries'
|
6
8
|
require_relative 'queries/event_type_queries'
|
9
|
+
require_relative 'queries/subscription_queries'
|
10
|
+
require_relative 'queries/subscriptions_set_queries'
|
11
|
+
require_relative 'queries/subscription_command_queries'
|
12
|
+
require_relative 'queries/subscriptions_set_command_queries'
|
13
|
+
require_relative 'queries/preloader'
|
7
14
|
|
8
15
|
module PgEventstore
|
9
16
|
# @!visibility private
|
@@ -4,8 +4,7 @@ module PgEventstore
|
|
4
4
|
module QueryBuilders
|
5
5
|
# @!visibility private
|
6
6
|
class EventsFiltering
|
7
|
-
|
8
|
-
DEFAULT_LIMIT = 1000
|
7
|
+
DEFAULT_LIMIT = 1_000
|
9
8
|
SQL_DIRECTIONS = {
|
10
9
|
'asc' => 'ASC',
|
11
10
|
'desc' => 'DESC',
|
@@ -16,17 +15,22 @@ module PgEventstore
|
|
16
15
|
}.tap do |directions|
|
17
16
|
directions.default = 'ASC'
|
18
17
|
end.freeze
|
18
|
+
SUBSCRIPTIONS_OPTIONS = %i[from_position resolve_link_tos filter max_count].freeze
|
19
19
|
|
20
20
|
class << self
|
21
21
|
# @param options [Hash]
|
22
|
-
# @param offset [Integer]
|
23
22
|
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
24
|
-
def
|
23
|
+
def subscriptions_events_filtering(options)
|
24
|
+
all_stream_filtering(options.slice(*SUBSCRIPTIONS_OPTIONS))
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param options [Hash]
|
28
|
+
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
29
|
+
def all_stream_filtering(options)
|
25
30
|
event_filter = new
|
26
31
|
options in { filter: { event_type_ids: Array => event_type_ids } }
|
27
32
|
event_filter.add_event_types(event_type_ids)
|
28
33
|
event_filter.add_limit(options[:max_count])
|
29
|
-
event_filter.add_offset(offset)
|
30
34
|
event_filter.resolve_links(options[:resolve_link_tos])
|
31
35
|
options in { filter: { streams: Array => streams } }
|
32
36
|
streams&.each { |attrs| event_filter.add_stream_attrs(**attrs) }
|
@@ -37,14 +41,12 @@ module PgEventstore
|
|
37
41
|
|
38
42
|
# @param stream [PgEventstore::Stream]
|
39
43
|
# @param options [Hash]
|
40
|
-
# @param offset [Integer]
|
41
44
|
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
42
|
-
def specific_stream_filtering(stream, options
|
45
|
+
def specific_stream_filtering(stream, options)
|
43
46
|
event_filter = new
|
44
47
|
options in { filter: { event_type_ids: Array => event_type_ids } }
|
45
48
|
event_filter.add_event_types(event_type_ids)
|
46
49
|
event_filter.add_limit(options[:max_count])
|
47
|
-
event_filter.add_offset(offset)
|
48
50
|
event_filter.resolve_links(options[:resolve_link_tos])
|
49
51
|
event_filter.add_stream(stream)
|
50
52
|
event_filter.add_revision(options[:from_revision], options[:direction])
|
@@ -57,13 +59,10 @@ module PgEventstore
|
|
57
59
|
@sql_builder =
|
58
60
|
SQLBuilder.new.
|
59
61
|
select('events.*').
|
60
|
-
select('row_to_json(streams.*) as stream').
|
61
|
-
select('event_types.type as type').
|
62
62
|
from('events').
|
63
63
|
join('JOIN streams ON streams.id = events.stream_id').
|
64
64
|
join('JOIN event_types ON event_types.id = events.event_type_id').
|
65
|
-
limit(DEFAULT_LIMIT)
|
66
|
-
offset(DEFAULT_OFFSET)
|
65
|
+
limit(DEFAULT_LIMIT)
|
67
66
|
end
|
68
67
|
|
69
68
|
# @param context [String, nil]
|
@@ -96,7 +95,7 @@ module PgEventstore
|
|
96
95
|
sql = event_type_ids.size.times.map do
|
97
96
|
"?"
|
98
97
|
end.join(", ")
|
99
|
-
@sql_builder.where("
|
98
|
+
@sql_builder.where("events.event_type_id IN (#{sql})", *event_type_ids)
|
100
99
|
end
|
101
100
|
|
102
101
|
# @param revision [Integer, nil]
|
@@ -137,14 +136,6 @@ module PgEventstore
|
|
137
136
|
@sql_builder.limit(limit)
|
138
137
|
end
|
139
138
|
|
140
|
-
# @param offset [Integer, nil]
|
141
|
-
# @return [void]
|
142
|
-
def add_offset(offset)
|
143
|
-
return unless offset
|
144
|
-
|
145
|
-
@sql_builder.offset(offset)
|
146
|
-
end
|
147
|
-
|
148
139
|
# @param should_resolve [Boolean]
|
149
140
|
# @return [void]
|
150
141
|
def resolve_links(should_resolve)
|
@@ -153,10 +144,14 @@ module PgEventstore
|
|
153
144
|
@sql_builder.
|
154
145
|
unselect.
|
155
146
|
select("(COALESCE(original_events.*, events.*)).*").
|
156
|
-
select('row_to_json(streams.*) as stream').
|
157
147
|
join("LEFT JOIN events original_events ON original_events.id = events.link_id")
|
158
148
|
end
|
159
149
|
|
150
|
+
# @return [PgEventstore::SQLBuilder]
|
151
|
+
def to_sql_builder
|
152
|
+
@sql_builder
|
153
|
+
end
|
154
|
+
|
160
155
|
# @return [Array]
|
161
156
|
def to_exec_params
|
162
157
|
@sql_builder.to_exec_params
|
@@ -13,6 +13,8 @@ module PgEventstore
|
|
13
13
|
@limit_value = nil
|
14
14
|
@offset_value = nil
|
15
15
|
@positional_values = []
|
16
|
+
@positional_values_size = 0
|
17
|
+
@union_values = []
|
16
18
|
end
|
17
19
|
|
18
20
|
# @param sql [String]
|
@@ -32,8 +34,7 @@ module PgEventstore
|
|
32
34
|
# @param arguments [Array] positional values
|
33
35
|
# @return self
|
34
36
|
def where(sql, *arguments)
|
35
|
-
|
36
|
-
@where_values['AND'].push("(#{sql})")
|
37
|
+
@where_values['AND'].push([sql, arguments])
|
37
38
|
self
|
38
39
|
end
|
39
40
|
|
@@ -41,8 +42,7 @@ module PgEventstore
|
|
41
42
|
# @param arguments [Object] positional values
|
42
43
|
# @return self
|
43
44
|
def where_or(sql, *arguments)
|
44
|
-
|
45
|
-
@where_values['OR'].push("(#{sql})")
|
45
|
+
@where_values['OR'].push([sql, arguments])
|
46
46
|
self
|
47
47
|
end
|
48
48
|
|
@@ -57,7 +57,7 @@ module PgEventstore
|
|
57
57
|
# @param arguments [Object]
|
58
58
|
# @return self
|
59
59
|
def join(sql, *arguments)
|
60
|
-
@join_values.push(
|
60
|
+
@join_values.push([sql, arguments])
|
61
61
|
self
|
62
62
|
end
|
63
63
|
|
@@ -82,7 +82,34 @@ module PgEventstore
|
|
82
82
|
self
|
83
83
|
end
|
84
84
|
|
85
|
+
# @param another_builder [PgEventstore::SQLBuilder]
|
86
|
+
# @return [self]
|
87
|
+
def union(another_builder)
|
88
|
+
@union_values.push(another_builder)
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
85
92
|
def to_exec_params
|
93
|
+
return [single_query_sql, @positional_values] if @union_values.empty?
|
94
|
+
|
95
|
+
[union_query_sql, @positional_values]
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
# @return [Array<Object>] sql positional values
|
101
|
+
def positional_values
|
102
|
+
@positional_values
|
103
|
+
end
|
104
|
+
|
105
|
+
def positional_values_size=(val)
|
106
|
+
@positional_values_size = val
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# @return [String]
|
112
|
+
def single_query_sql
|
86
113
|
where_sql = [where_sql('OR'), where_sql('AND')].reject(&:empty?).map { |sql| "(#{sql})" }.join(' AND ')
|
87
114
|
sql = "SELECT #{select_sql} FROM #{@from_value}"
|
88
115
|
sql += " #{join_sql}" unless @join_values.empty?
|
@@ -90,10 +117,22 @@ module PgEventstore
|
|
90
117
|
sql += " ORDER BY #{order_sql}" unless @order_values.empty?
|
91
118
|
sql += " LIMIT #{@limit_value}" if @limit_value
|
92
119
|
sql += " OFFSET #{@offset_value}" if @offset_value
|
93
|
-
|
120
|
+
sql
|
94
121
|
end
|
95
122
|
|
96
|
-
|
123
|
+
# @return [String]
|
124
|
+
def union_query_sql
|
125
|
+
sql = single_query_sql
|
126
|
+
union_parts = ["(#{sql})"]
|
127
|
+
union_parts += @union_values.map do |builder|
|
128
|
+
builder.positional_values_size = @positional_values_size
|
129
|
+
builder_sql, values = builder.to_exec_params
|
130
|
+
@positional_values.push(*values)
|
131
|
+
@positional_values_size += values.size
|
132
|
+
"(#{builder_sql})"
|
133
|
+
end
|
134
|
+
union_parts.join(' UNION ALL ')
|
135
|
+
end
|
97
136
|
|
98
137
|
# @return [String]
|
99
138
|
def select_sql
|
@@ -103,12 +142,14 @@ module PgEventstore
|
|
103
142
|
# @param join_pattern [String] "OR"/"AND"
|
104
143
|
# @return [String]
|
105
144
|
def where_sql(join_pattern)
|
106
|
-
@where_values[join_pattern].
|
145
|
+
@where_values[join_pattern].map do |sql, args|
|
146
|
+
"(#{extract_positional_args(sql, *args)})"
|
147
|
+
end.join(" #{join_pattern} ")
|
107
148
|
end
|
108
149
|
|
109
150
|
# @return [String]
|
110
151
|
def join_sql
|
111
|
-
@join_values.join(" ")
|
152
|
+
@join_values.map { |sql, args| extract_positional_args(sql, *args) }.join(" ")
|
112
153
|
end
|
113
154
|
|
114
155
|
# @return [String]
|
@@ -116,10 +157,13 @@ module PgEventstore
|
|
116
157
|
@order_values.join(', ')
|
117
158
|
end
|
118
159
|
|
160
|
+
# Replaces "?" signs in the given string with positional variables and memorize positional values they refer to.
|
161
|
+
# @return [String]
|
119
162
|
def extract_positional_args(sql, *arguments)
|
120
163
|
sql.gsub("?").each_with_index do |_, index|
|
121
164
|
@positional_values.push(arguments[index])
|
122
|
-
|
165
|
+
@positional_values_size += 1
|
166
|
+
"$#{@positional_values_size}"
|
123
167
|
end
|
124
168
|
end
|
125
169
|
end
|
data/lib/pg_eventstore/stream.rb
CHANGED
@@ -18,7 +18,8 @@ module PgEventstore
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
attr_reader :context, :stream_name, :stream_id, :id
|
21
|
+
attr_reader :context, :stream_name, :stream_id, :id
|
22
|
+
attr_accessor :stream_revision
|
22
23
|
|
23
24
|
# @param context [String]
|
24
25
|
# @param stream_name [String]
|