pg_eventstore 0.9.0 → 0.10.2
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/db/migrations/{3_create_events.sql → 1_create_events.sql} +11 -14
- data/db/migrations/5_partitions.sql +16 -0
- data/docs/configuration.md +1 -1
- data/docs/events_and_streams.md +5 -3
- data/docs/how_it_works.md +80 -0
- data/lib/pg_eventstore/client.rb +11 -7
- data/lib/pg_eventstore/commands/append.rb +17 -8
- data/lib/pg_eventstore/commands/link_to.rb +3 -3
- data/lib/pg_eventstore/commands/read.rb +1 -1
- data/lib/pg_eventstore/errors.rb +17 -6
- data/lib/pg_eventstore/event.rb +5 -1
- data/lib/pg_eventstore/event_deserializer.rb +4 -1
- data/lib/pg_eventstore/queries/event_queries.rb +65 -26
- data/lib/pg_eventstore/queries/links_resolver.rb +31 -0
- data/lib/pg_eventstore/queries/partition_queries.rb +184 -0
- data/lib/pg_eventstore/queries/subscription_queries.rb +12 -15
- data/lib/pg_eventstore/queries/transaction_queries.rb +13 -0
- data/lib/pg_eventstore/queries.rb +5 -6
- data/lib/pg_eventstore/query_builders/events_filtering_query.rb +10 -31
- data/lib/pg_eventstore/rspec/test_helpers.rb +16 -1
- data/lib/pg_eventstore/sql_builder.rb +34 -4
- data/lib/pg_eventstore/stream.rb +3 -8
- data/lib/pg_eventstore/subscriptions/events_processor.rb +10 -2
- data/lib/pg_eventstore/subscriptions/subscription.rb +1 -0
- data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +1 -1
- data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +2 -3
- data/lib/pg_eventstore/version.rb +1 -1
- metadata +10 -11
- data/db/migrations/1_create_streams.sql +0 -13
- data/db/migrations/2_create_event_types.sql +0 -10
- data/lib/pg_eventstore/queries/event_type_queries.rb +0 -74
- data/lib/pg_eventstore/queries/preloader.rb +0 -37
- data/lib/pg_eventstore/queries/stream_queries.rb +0 -77
- /data/db/migrations/{4_create_subscriptions.sql → 2_create_subscriptions.sql} +0 -0
- /data/db/migrations/{5_create_subscription_commands.sql → 3_create_subscription_commands.sql} +0 -0
- /data/db/migrations/{6_create_subscriptions_set_commands.sql → 4_create_subscriptions_set_commands.sql} +0 -0
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
# @!visibility private
|
5
|
+
class PartitionQueries
|
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 stream [PgEventstore::Stream]
|
15
|
+
# @return [Hash] partition attributes
|
16
|
+
def create_context_partition(stream)
|
17
|
+
attributes = { context: stream.context, table_name: context_partition_name(stream) }
|
18
|
+
|
19
|
+
loop do
|
20
|
+
break unless partition_name_taken?(attributes[:table_name])
|
21
|
+
|
22
|
+
attributes[:table_name] = attributes[:table_name].next
|
23
|
+
end
|
24
|
+
|
25
|
+
partition_sql = <<~SQL
|
26
|
+
INSERT INTO partitions (#{attributes.keys.join(', ')})
|
27
|
+
VALUES (#{Utils.positional_vars(attributes.values)}) RETURNING *
|
28
|
+
SQL
|
29
|
+
partition = connection.with do |conn|
|
30
|
+
conn.exec_params(partition_sql, [*attributes.values])
|
31
|
+
end.to_a.first
|
32
|
+
connection.with do |conn|
|
33
|
+
conn.exec(<<~SQL)
|
34
|
+
CREATE TABLE #{attributes[:table_name]} PARTITION OF events
|
35
|
+
FOR VALUES IN('#{conn.escape_string(stream.context)}') PARTITION BY LIST (stream_name)
|
36
|
+
SQL
|
37
|
+
end
|
38
|
+
partition
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param stream [PgEventstore::Stream]
|
42
|
+
# @param context_partition_name [String]
|
43
|
+
# @return [Hash] partition attributes
|
44
|
+
def create_stream_name_partition(stream, context_partition_name)
|
45
|
+
attributes = {
|
46
|
+
context: stream.context, stream_name: stream.stream_name, table_name: stream_name_partition_name(stream)
|
47
|
+
}
|
48
|
+
|
49
|
+
loop do
|
50
|
+
break unless partition_name_taken?(attributes[:table_name])
|
51
|
+
|
52
|
+
attributes[:table_name] = attributes[:table_name].next
|
53
|
+
end
|
54
|
+
|
55
|
+
partition_sql = <<~SQL
|
56
|
+
INSERT INTO partitions (#{attributes.keys.join(', ')})
|
57
|
+
VALUES (#{Utils.positional_vars(attributes.values)}) RETURNING *
|
58
|
+
SQL
|
59
|
+
partition = connection.with do |conn|
|
60
|
+
conn.exec_params(partition_sql, [*attributes.values])
|
61
|
+
end.to_a.first
|
62
|
+
connection.with do |conn|
|
63
|
+
conn.exec(<<~SQL)
|
64
|
+
CREATE TABLE #{attributes[:table_name]} PARTITION OF #{context_partition_name}
|
65
|
+
FOR VALUES IN('#{conn.escape_string(stream.stream_name)}') PARTITION BY LIST (type)
|
66
|
+
SQL
|
67
|
+
end
|
68
|
+
partition
|
69
|
+
end
|
70
|
+
|
71
|
+
# @param stream [PgEventstore::Stream]
|
72
|
+
# @param stream_name_partition_name [String]
|
73
|
+
# @return [Hash] partition attributes
|
74
|
+
def create_event_type_partition(stream, event_type, stream_name_partition_name)
|
75
|
+
attributes = {
|
76
|
+
context: stream.context, stream_name: stream.stream_name, event_type: event_type,
|
77
|
+
table_name: event_type_partition_name(stream, event_type)
|
78
|
+
}
|
79
|
+
|
80
|
+
loop do
|
81
|
+
break unless partition_name_taken?(attributes[:table_name])
|
82
|
+
|
83
|
+
attributes[:table_name] = attributes[:table_name].next
|
84
|
+
end
|
85
|
+
|
86
|
+
partition_sql = <<~SQL
|
87
|
+
INSERT INTO partitions (#{attributes.keys.join(', ')})
|
88
|
+
VALUES (#{Utils.positional_vars(attributes.values)}) RETURNING *
|
89
|
+
SQL
|
90
|
+
partition = connection.with do |conn|
|
91
|
+
conn.exec_params(partition_sql, [*attributes.values])
|
92
|
+
end.to_a.first
|
93
|
+
connection.with do |conn|
|
94
|
+
conn.exec(<<~SQL)
|
95
|
+
CREATE TABLE #{attributes[:table_name]} PARTITION OF #{stream_name_partition_name}
|
96
|
+
FOR VALUES IN('#{conn.escape_string(event_type)}')
|
97
|
+
SQL
|
98
|
+
end
|
99
|
+
partition
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param stream [PgEventstore::Stream]
|
103
|
+
# @param event_type [String]
|
104
|
+
# @return [Boolean]
|
105
|
+
def partition_required?(stream, event_type)
|
106
|
+
event_type_partition(stream, event_type).nil?
|
107
|
+
end
|
108
|
+
|
109
|
+
# @param stream [PgEventstore::Stream]
|
110
|
+
# @param event_type [String]
|
111
|
+
# @return [void]
|
112
|
+
def create_partitions(stream, event_type)
|
113
|
+
return unless partition_required?(stream, event_type)
|
114
|
+
|
115
|
+
context_partition = context_partition(stream) || create_context_partition(stream)
|
116
|
+
stream_name_partition = stream_name_partition(stream) ||
|
117
|
+
create_stream_name_partition(stream, context_partition['table_name'])
|
118
|
+
|
119
|
+
create_event_type_partition(stream, event_type, stream_name_partition['table_name'])
|
120
|
+
end
|
121
|
+
|
122
|
+
# @param stream [PgEventstore::Stream]
|
123
|
+
# @return [Hash, nil] partition attributes
|
124
|
+
def context_partition(stream)
|
125
|
+
connection.with do |conn|
|
126
|
+
conn.exec_params(
|
127
|
+
'select * from partitions where context = $1 and stream_name is null and event_type is null',
|
128
|
+
[stream.context]
|
129
|
+
)
|
130
|
+
end.first
|
131
|
+
end
|
132
|
+
|
133
|
+
# @param stream [PgEventstore::Stream]
|
134
|
+
# @return [Hash, nil] partition attributes
|
135
|
+
def stream_name_partition(stream)
|
136
|
+
connection.with do |conn|
|
137
|
+
conn.exec_params(
|
138
|
+
<<~SQL,
|
139
|
+
select * from partitions where context = $1 and stream_name = $2 and event_type is null
|
140
|
+
SQL
|
141
|
+
[stream.context, stream.stream_name]
|
142
|
+
)
|
143
|
+
end.first
|
144
|
+
end
|
145
|
+
|
146
|
+
# @param stream [PgEventstore::Stream]
|
147
|
+
# @param event_type [String]
|
148
|
+
# @return [Hash, nil] partition attributes
|
149
|
+
def event_type_partition(stream, event_type)
|
150
|
+
connection.with do |conn|
|
151
|
+
conn.exec_params(
|
152
|
+
<<~SQL,
|
153
|
+
select * from partitions where context = $1 and stream_name = $2 and event_type = $3
|
154
|
+
SQL
|
155
|
+
[stream.context, stream.stream_name, event_type]
|
156
|
+
)
|
157
|
+
end.first
|
158
|
+
end
|
159
|
+
|
160
|
+
# @param table_name [String]
|
161
|
+
# @return [Boolean]
|
162
|
+
def partition_name_taken?(table_name)
|
163
|
+
connection.with do |conn|
|
164
|
+
conn.exec_params('select 1 as exists from partitions where table_name = $1', [table_name])
|
165
|
+
end.to_a.dig(0, 'exists') == 1
|
166
|
+
end
|
167
|
+
|
168
|
+
# @param stream [PgEventstore::Stream]
|
169
|
+
# @return [String]
|
170
|
+
def context_partition_name(stream)
|
171
|
+
"contexts_#{Digest::MD5.hexdigest(stream.context)[0..5]}"
|
172
|
+
end
|
173
|
+
|
174
|
+
# @param stream [PgEventstore::Stream]
|
175
|
+
# @return [String]
|
176
|
+
def stream_name_partition_name(stream)
|
177
|
+
"stream_names_#{Digest::MD5.hexdigest("#{stream.context}-#{stream.stream_name}")[0..5]}"
|
178
|
+
end
|
179
|
+
|
180
|
+
def event_type_partition_name(stream, event_type)
|
181
|
+
"event_types_#{Digest::MD5.hexdigest("#{stream.context}-#{stream.stream_name}-#{event_type}")[0..5]}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -73,16 +73,20 @@ module PgEventstore
|
|
73
73
|
deserialize(pg_result.to_a.first)
|
74
74
|
end
|
75
75
|
|
76
|
-
# @param query_options [
|
77
|
-
# @return [
|
76
|
+
# @param query_options [Hash{Integer => Hash}] runner_id/query options association
|
77
|
+
# @return [Hash{Integer => Hash}] runner_id/events association
|
78
78
|
def subscriptions_events(query_options)
|
79
|
-
return
|
79
|
+
return {} if query_options.empty?
|
80
80
|
|
81
81
|
final_builder = union_builders(query_options.map { |id, opts| query_builder(id, opts) })
|
82
82
|
raw_events = connection.with do |conn|
|
83
83
|
conn.exec_params(*final_builder.to_exec_params)
|
84
84
|
end.to_a
|
85
|
-
|
85
|
+
raw_events.group_by { _1['runner_id'] }.to_h do |runner_id, runner_raw_events|
|
86
|
+
next [runner_id, runner_raw_events] unless query_options[runner_id][:resolve_link_tos]
|
87
|
+
|
88
|
+
[runner_id, links_resolver.resolve(runner_raw_events)]
|
89
|
+
end
|
86
90
|
end
|
87
91
|
|
88
92
|
# @param id [Integer] subscription's id
|
@@ -128,9 +132,7 @@ module PgEventstore
|
|
128
132
|
# @param options [Hash] query options
|
129
133
|
# @return [PgEventstore::SQLBuilder]
|
130
134
|
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
|
135
|
+
builder = PgEventstore::QueryBuilders::EventsFiltering.subscriptions_events_filtering(options).to_sql_builder
|
134
136
|
builder.select("#{id} as runner_id")
|
135
137
|
end
|
136
138
|
|
@@ -147,14 +149,9 @@ module PgEventstore
|
|
147
149
|
TransactionQueries.new(connection)
|
148
150
|
end
|
149
151
|
|
150
|
-
# @return [PgEventstore::
|
151
|
-
def
|
152
|
-
|
153
|
-
end
|
154
|
-
|
155
|
-
# @return [PgEventstore::Preloader]
|
156
|
-
def preloader
|
157
|
-
Preloader.new(connection)
|
152
|
+
# @return [PgEventstore::LinksResolver]
|
153
|
+
def links_resolver
|
154
|
+
LinksResolver.new(connection)
|
158
155
|
end
|
159
156
|
|
160
157
|
# @param hash [Hash]
|
@@ -36,6 +36,19 @@ module PgEventstore
|
|
36
36
|
end
|
37
37
|
rescue PG::TRSerializationFailure, PG::TRDeadlockDetected
|
38
38
|
retry
|
39
|
+
rescue MissingPartitions => error
|
40
|
+
error.event_types.each do |event_type|
|
41
|
+
transaction do
|
42
|
+
partition_queries.create_partitions(error.stream, event_type)
|
43
|
+
end
|
44
|
+
rescue PG::UniqueViolation
|
45
|
+
retry
|
46
|
+
end
|
47
|
+
retry
|
48
|
+
end
|
49
|
+
|
50
|
+
def partition_queries
|
51
|
+
PartitionQueries.new(connection)
|
39
52
|
end
|
40
53
|
end
|
41
54
|
end
|
@@ -4,13 +4,12 @@ require_relative 'sql_builder'
|
|
4
4
|
require_relative 'query_builders/events_filtering_query'
|
5
5
|
require_relative 'queries/transaction_queries'
|
6
6
|
require_relative 'queries/event_queries'
|
7
|
-
require_relative 'queries/
|
8
|
-
require_relative 'queries/event_type_queries'
|
7
|
+
require_relative 'queries/partition_queries'
|
9
8
|
require_relative 'queries/subscription_queries'
|
10
9
|
require_relative 'queries/subscriptions_set_queries'
|
11
10
|
require_relative 'queries/subscription_command_queries'
|
12
11
|
require_relative 'queries/subscriptions_set_command_queries'
|
13
|
-
require_relative 'queries/
|
12
|
+
require_relative 'queries/links_resolver'
|
14
13
|
|
15
14
|
module PgEventstore
|
16
15
|
# @!visibility private
|
@@ -20,9 +19,9 @@ module PgEventstore
|
|
20
19
|
# @!attribute events
|
21
20
|
# @return [PgEventstore::EventQueries, nil]
|
22
21
|
attribute(:events)
|
23
|
-
# @!attribute
|
24
|
-
# @return [PgEventstore::
|
25
|
-
attribute(:
|
22
|
+
# @!attribute partitions
|
23
|
+
# @return [PgEventstore::PartitionQueries, nil]
|
24
|
+
attribute(:partitions)
|
26
25
|
# @!attribute transactions
|
27
26
|
# @return [PgEventstore::TransactionQueries, nil]
|
28
27
|
attribute(:transactions)
|
@@ -28,10 +28,9 @@ module PgEventstore
|
|
28
28
|
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
29
29
|
def all_stream_filtering(options)
|
30
30
|
event_filter = new
|
31
|
-
options in { filter: {
|
31
|
+
options in { filter: { event_types: Array => event_type_ids } }
|
32
32
|
event_filter.add_event_types(event_type_ids)
|
33
33
|
event_filter.add_limit(options[:max_count])
|
34
|
-
event_filter.resolve_links(options[:resolve_link_tos])
|
35
34
|
options in { filter: { streams: Array => streams } }
|
36
35
|
streams&.each { |attrs| event_filter.add_stream_attrs(**attrs) }
|
37
36
|
event_filter.add_global_position(options[:from_position], options[:direction])
|
@@ -44,11 +43,10 @@ module PgEventstore
|
|
44
43
|
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
45
44
|
def specific_stream_filtering(stream, options)
|
46
45
|
event_filter = new
|
47
|
-
options in { filter: {
|
46
|
+
options in { filter: { event_types: Array => event_type_ids } }
|
48
47
|
event_filter.add_event_types(event_type_ids)
|
49
48
|
event_filter.add_limit(options[:max_count])
|
50
|
-
event_filter.
|
51
|
-
event_filter.add_stream(stream)
|
49
|
+
event_filter.add_stream_attrs(**stream.to_hash)
|
52
50
|
event_filter.add_revision(options[:from_revision], options[:direction])
|
53
51
|
event_filter.add_stream_direction(options[:direction])
|
54
52
|
event_filter
|
@@ -60,8 +58,6 @@ module PgEventstore
|
|
60
58
|
SQLBuilder.new.
|
61
59
|
select('events.*').
|
62
60
|
from('events').
|
63
|
-
join('JOIN streams ON streams.id = events.stream_id').
|
64
|
-
join('JOIN event_types ON event_types.id = events.event_type_id').
|
65
61
|
limit(DEFAULT_LIMIT)
|
66
62
|
end
|
67
63
|
|
@@ -75,27 +71,21 @@ module PgEventstore
|
|
75
71
|
|
76
72
|
stream_attrs.compact!
|
77
73
|
sql = stream_attrs.map do |attr, _|
|
78
|
-
"
|
74
|
+
"events.#{attr} = ?"
|
79
75
|
end.join(" AND ")
|
80
76
|
@sql_builder.where_or(sql, *stream_attrs.values)
|
81
77
|
end
|
82
78
|
|
83
|
-
# @param
|
79
|
+
# @param event_types [Array<String>, nil]
|
84
80
|
# @return [void]
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
# @param event_type_ids [Array<Integer>, nil]
|
90
|
-
# @return [void]
|
91
|
-
def add_event_types(event_type_ids)
|
92
|
-
return if event_type_ids.nil?
|
93
|
-
return if event_type_ids.empty?
|
81
|
+
def add_event_types(event_types)
|
82
|
+
return if event_types.nil?
|
83
|
+
return if event_types.empty?
|
94
84
|
|
95
|
-
sql =
|
85
|
+
sql = event_types.size.times.map do
|
96
86
|
"?"
|
97
87
|
end.join(", ")
|
98
|
-
@sql_builder.where("events.
|
88
|
+
@sql_builder.where("events.type IN (#{sql})", *event_types)
|
99
89
|
end
|
100
90
|
|
101
91
|
# @param revision [Integer, nil]
|
@@ -136,17 +126,6 @@ module PgEventstore
|
|
136
126
|
@sql_builder.limit(limit)
|
137
127
|
end
|
138
128
|
|
139
|
-
# @param should_resolve [Boolean]
|
140
|
-
# @return [void]
|
141
|
-
def resolve_links(should_resolve)
|
142
|
-
return unless should_resolve
|
143
|
-
|
144
|
-
@sql_builder.
|
145
|
-
unselect.
|
146
|
-
select("(COALESCE(original_events.*, events.*)).*").
|
147
|
-
join("LEFT JOIN events original_events ON original_events.id = events.link_id")
|
148
|
-
end
|
149
|
-
|
150
129
|
# @return [PgEventstore::SQLBuilder]
|
151
130
|
def to_sql_builder
|
152
131
|
@sql_builder
|
@@ -4,10 +4,25 @@ module PgEventstore
|
|
4
4
|
module TestHelpers
|
5
5
|
class << self
|
6
6
|
def clean_up_db
|
7
|
+
clean_up_data
|
8
|
+
clean_up_partitions
|
9
|
+
end
|
10
|
+
|
11
|
+
def clean_up_partitions
|
12
|
+
PgEventstore.connection.with do |conn|
|
13
|
+
# Dropping parent partition also drops all child partitions
|
14
|
+
conn.exec("select tablename from pg_tables where tablename like 'contexts_%'").each do |attrs|
|
15
|
+
conn.exec("drop table #{attrs['tablename']}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def clean_up_data
|
7
21
|
tables_to_purge = PgEventstore.connection.with do |conn|
|
8
22
|
conn.exec(<<~SQL)
|
9
23
|
SELECT tablename
|
10
|
-
FROM pg_catalog.pg_tables
|
24
|
+
FROM pg_catalog.pg_tables
|
25
|
+
WHERE schemaname NOT IN ('pg_catalog', 'information_schema') AND tablename != 'migrations'
|
11
26
|
SQL
|
12
27
|
end.map { |attrs| attrs['tablename'] }
|
13
28
|
tables_to_purge.each do |table_name|
|
@@ -9,6 +9,7 @@ module PgEventstore
|
|
9
9
|
@from_value = nil
|
10
10
|
@where_values = { 'AND' => [], 'OR' => [] }
|
11
11
|
@join_values = []
|
12
|
+
@group_values = []
|
12
13
|
@order_values = []
|
13
14
|
@limit_value = nil
|
14
15
|
@offset_value = nil
|
@@ -68,6 +69,11 @@ module PgEventstore
|
|
68
69
|
self
|
69
70
|
end
|
70
71
|
|
72
|
+
def remove_order
|
73
|
+
@order_values.clear
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
71
77
|
# @param limit [Integer]
|
72
78
|
# @return self
|
73
79
|
def limit(limit)
|
@@ -75,6 +81,11 @@ module PgEventstore
|
|
75
81
|
self
|
76
82
|
end
|
77
83
|
|
84
|
+
def remove_limit
|
85
|
+
@limit_value = nil
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
78
89
|
# @param offset [Integer]
|
79
90
|
# @return self
|
80
91
|
def offset(offset)
|
@@ -89,10 +100,22 @@ module PgEventstore
|
|
89
100
|
self
|
90
101
|
end
|
91
102
|
|
92
|
-
|
93
|
-
|
103
|
+
# @param sql [String]
|
104
|
+
# @return self
|
105
|
+
def group(sql)
|
106
|
+
@group_values.push(sql)
|
107
|
+
self
|
108
|
+
end
|
94
109
|
|
95
|
-
|
110
|
+
def remove_group
|
111
|
+
@group_values.clear
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_exec_params
|
116
|
+
@positional_values.clear
|
117
|
+
@positional_values_size = 0
|
118
|
+
_to_exec_params
|
96
119
|
end
|
97
120
|
|
98
121
|
protected
|
@@ -106,6 +129,12 @@ module PgEventstore
|
|
106
129
|
@positional_values_size = val
|
107
130
|
end
|
108
131
|
|
132
|
+
def _to_exec_params
|
133
|
+
return [single_query_sql, @positional_values] if @union_values.empty?
|
134
|
+
|
135
|
+
[union_query_sql, @positional_values]
|
136
|
+
end
|
137
|
+
|
109
138
|
private
|
110
139
|
|
111
140
|
# @return [String]
|
@@ -114,6 +143,7 @@ module PgEventstore
|
|
114
143
|
sql = "SELECT #{select_sql} FROM #{@from_value}"
|
115
144
|
sql += " #{join_sql}" unless @join_values.empty?
|
116
145
|
sql += " WHERE #{where_sql}" unless where_sql.empty?
|
146
|
+
sql += " GROUP BY #{@group_values.join(', ')}" unless @group_values.empty?
|
117
147
|
sql += " ORDER BY #{order_sql}" unless @order_values.empty?
|
118
148
|
sql += " LIMIT #{@limit_value}" if @limit_value
|
119
149
|
sql += " OFFSET #{@offset_value}" if @offset_value
|
@@ -126,7 +156,7 @@ module PgEventstore
|
|
126
156
|
union_parts = ["(#{sql})"]
|
127
157
|
union_parts += @union_values.map do |builder|
|
128
158
|
builder.positional_values_size = @positional_values_size
|
129
|
-
builder_sql, values = builder.
|
159
|
+
builder_sql, values = builder._to_exec_params
|
130
160
|
@positional_values.push(*values)
|
131
161
|
@positional_values_size += values.size
|
132
162
|
"(#{builder_sql})"
|
data/lib/pg_eventstore/stream.rb
CHANGED
@@ -5,7 +5,7 @@ require 'digest/md5'
|
|
5
5
|
module PgEventstore
|
6
6
|
class Stream
|
7
7
|
SYSTEM_STREAM_PREFIX = '$'
|
8
|
-
|
8
|
+
NON_EXISTING_STREAM_REVISION = -1
|
9
9
|
|
10
10
|
class << self
|
11
11
|
# Produces "all" stream instance. "all" stream does not represent any specific stream. Instead, it indicates that
|
@@ -18,20 +18,15 @@ module PgEventstore
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
attr_reader :context, :stream_name, :stream_id
|
22
|
-
attr_accessor :stream_revision
|
21
|
+
attr_reader :context, :stream_name, :stream_id
|
23
22
|
|
24
23
|
# @param context [String]
|
25
24
|
# @param stream_name [String]
|
26
25
|
# @param stream_id [String]
|
27
|
-
|
28
|
-
# @param stream_revision [Integer, nil] current stream revision, read only
|
29
|
-
def initialize(context:, stream_name:, stream_id:, id: nil, stream_revision: nil)
|
26
|
+
def initialize(context:, stream_name:, stream_id:)
|
30
27
|
@context = context
|
31
28
|
@stream_name = stream_name
|
32
29
|
@stream_id = stream_id
|
33
|
-
@id = id
|
34
|
-
@stream_revision = stream_revision
|
35
30
|
end
|
36
31
|
|
37
32
|
# @return [Boolean]
|
@@ -20,7 +20,7 @@ module PgEventstore
|
|
20
20
|
# @param raw_events [Array<Hash>]
|
21
21
|
# @return [void]
|
22
22
|
def feed(raw_events)
|
23
|
-
callbacks.run_callbacks(:feed, raw_events.last
|
23
|
+
callbacks.run_callbacks(:feed, global_position(raw_events.last))
|
24
24
|
@raw_events.push(*raw_events)
|
25
25
|
end
|
26
26
|
|
@@ -35,7 +35,7 @@ module PgEventstore
|
|
35
35
|
# @param raw_event [Hash]
|
36
36
|
# @return [void]
|
37
37
|
def process_event(raw_event)
|
38
|
-
callbacks.run_callbacks(:process, raw_event
|
38
|
+
callbacks.run_callbacks(:process, global_position(raw_event)) do
|
39
39
|
@handler.call(raw_event)
|
40
40
|
end
|
41
41
|
end
|
@@ -68,5 +68,13 @@ module PgEventstore
|
|
68
68
|
def change_state(...)
|
69
69
|
callbacks.run_callbacks(:change_state, ...)
|
70
70
|
end
|
71
|
+
|
72
|
+
# @param raw_event [Hash, nil]
|
73
|
+
# @return [Integer, nil]
|
74
|
+
def global_position(raw_event)
|
75
|
+
return unless raw_event
|
76
|
+
|
77
|
+
raw_event['link'] ? raw_event['link']['global_position'] : raw_event['global_position']
|
78
|
+
end
|
71
79
|
end
|
72
80
|
end
|
@@ -21,7 +21,7 @@ module PgEventstore
|
|
21
21
|
@max_retries = max_retries
|
22
22
|
@retries_interval = retries_interval
|
23
23
|
@commands_handler = CommandsHandler.new(@config_name, self, @runners)
|
24
|
-
@basic_runner = BasicRunner.new(
|
24
|
+
@basic_runner = BasicRunner.new(0.2, 0)
|
25
25
|
@force_lock = false
|
26
26
|
attach_runner_callbacks
|
27
27
|
end
|
@@ -15,9 +15,8 @@ module PgEventstore
|
|
15
15
|
runners = runners.select(&:running?).select(&:time_to_feed?)
|
16
16
|
return if runners.empty?
|
17
17
|
|
18
|
-
runners_query_options = runners.
|
19
|
-
|
20
|
-
grouped_events = raw_events.group_by { |attrs| attrs['runner_id'] }
|
18
|
+
runners_query_options = runners.to_h { |runner| [runner.id, runner.next_chunk_query_opts] }
|
19
|
+
grouped_events = subscription_queries.subscriptions_events(runners_query_options)
|
21
20
|
runners.each do |runner|
|
22
21
|
runner.feed(grouped_events[runner.id]) if grouped_events[runner.id]
|
23
22
|
end
|
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: 0.
|
4
|
+
version: 0.10.2
|
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-
|
11
|
+
date: 2024-03-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -50,15 +50,15 @@ files:
|
|
50
50
|
- LICENSE.txt
|
51
51
|
- README.md
|
52
52
|
- db/migrations/0_create_extensions.sql
|
53
|
-
- db/migrations/
|
54
|
-
- db/migrations/
|
55
|
-
- db/migrations/
|
56
|
-
- db/migrations/
|
57
|
-
- db/migrations/
|
58
|
-
- db/migrations/6_create_subscriptions_set_commands.sql
|
53
|
+
- db/migrations/1_create_events.sql
|
54
|
+
- db/migrations/2_create_subscriptions.sql
|
55
|
+
- db/migrations/3_create_subscription_commands.sql
|
56
|
+
- db/migrations/4_create_subscriptions_set_commands.sql
|
57
|
+
- db/migrations/5_partitions.sql
|
59
58
|
- docs/appending_events.md
|
60
59
|
- docs/configuration.md
|
61
60
|
- docs/events_and_streams.md
|
61
|
+
- docs/how_it_works.md
|
62
62
|
- docs/linking_events.md
|
63
63
|
- docs/multiple_commands.md
|
64
64
|
- docs/reading_events.md
|
@@ -91,9 +91,8 @@ files:
|
|
91
91
|
- lib/pg_eventstore/pg_connection.rb
|
92
92
|
- lib/pg_eventstore/queries.rb
|
93
93
|
- lib/pg_eventstore/queries/event_queries.rb
|
94
|
-
- lib/pg_eventstore/queries/
|
95
|
-
- lib/pg_eventstore/queries/
|
96
|
-
- lib/pg_eventstore/queries/stream_queries.rb
|
94
|
+
- lib/pg_eventstore/queries/links_resolver.rb
|
95
|
+
- lib/pg_eventstore/queries/partition_queries.rb
|
97
96
|
- lib/pg_eventstore/queries/subscription_command_queries.rb
|
98
97
|
- lib/pg_eventstore/queries/subscription_queries.rb
|
99
98
|
- lib/pg_eventstore/queries/subscriptions_set_command_queries.rb
|
@@ -1,13 +0,0 @@
|
|
1
|
-
CREATE TABLE public.streams
|
2
|
-
(
|
3
|
-
id bigserial NOT NULL,
|
4
|
-
context character varying COLLATE "POSIX" NOT NULL,
|
5
|
-
stream_name character varying COLLATE "POSIX" NOT NULL,
|
6
|
-
stream_id character varying COLLATE "POSIX" NOT NULL,
|
7
|
-
stream_revision integer DEFAULT '-1'::integer NOT NULL
|
8
|
-
);
|
9
|
-
|
10
|
-
ALTER TABLE ONLY public.streams
|
11
|
-
ADD CONSTRAINT streams_pkey PRIMARY KEY (id);
|
12
|
-
|
13
|
-
CREATE UNIQUE INDEX idx_streams_context_and_stream_name_and_stream_id ON public.streams USING btree (context, stream_name, stream_id);
|
@@ -1,10 +0,0 @@
|
|
1
|
-
CREATE TABLE public.event_types
|
2
|
-
(
|
3
|
-
id bigserial NOT NULL,
|
4
|
-
type character varying COLLATE "POSIX" NOT NULL
|
5
|
-
);
|
6
|
-
|
7
|
-
ALTER TABLE ONLY public.event_types
|
8
|
-
ADD CONSTRAINT event_types_pkey PRIMARY KEY (id);
|
9
|
-
|
10
|
-
CREATE UNIQUE INDEX idx_event_types_type ON public.event_types USING btree (type);
|