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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/db/migrations/{3_create_events.sql → 1_create_events.sql} +11 -14
  4. data/db/migrations/5_partitions.sql +16 -0
  5. data/docs/configuration.md +1 -1
  6. data/docs/events_and_streams.md +5 -3
  7. data/docs/how_it_works.md +80 -0
  8. data/lib/pg_eventstore/client.rb +11 -7
  9. data/lib/pg_eventstore/commands/append.rb +17 -8
  10. data/lib/pg_eventstore/commands/link_to.rb +3 -3
  11. data/lib/pg_eventstore/commands/read.rb +1 -1
  12. data/lib/pg_eventstore/errors.rb +17 -6
  13. data/lib/pg_eventstore/event.rb +5 -1
  14. data/lib/pg_eventstore/event_deserializer.rb +4 -1
  15. data/lib/pg_eventstore/queries/event_queries.rb +65 -26
  16. data/lib/pg_eventstore/queries/links_resolver.rb +31 -0
  17. data/lib/pg_eventstore/queries/partition_queries.rb +184 -0
  18. data/lib/pg_eventstore/queries/subscription_queries.rb +12 -15
  19. data/lib/pg_eventstore/queries/transaction_queries.rb +13 -0
  20. data/lib/pg_eventstore/queries.rb +5 -6
  21. data/lib/pg_eventstore/query_builders/events_filtering_query.rb +10 -31
  22. data/lib/pg_eventstore/rspec/test_helpers.rb +16 -1
  23. data/lib/pg_eventstore/sql_builder.rb +34 -4
  24. data/lib/pg_eventstore/stream.rb +3 -8
  25. data/lib/pg_eventstore/subscriptions/events_processor.rb +10 -2
  26. data/lib/pg_eventstore/subscriptions/subscription.rb +1 -0
  27. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +1 -1
  28. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +2 -3
  29. data/lib/pg_eventstore/version.rb +1 -1
  30. metadata +10 -11
  31. data/db/migrations/1_create_streams.sql +0 -13
  32. data/db/migrations/2_create_event_types.sql +0 -10
  33. data/lib/pg_eventstore/queries/event_type_queries.rb +0 -74
  34. data/lib/pg_eventstore/queries/preloader.rb +0 -37
  35. data/lib/pg_eventstore/queries/stream_queries.rb +0 -77
  36. /data/db/migrations/{4_create_subscriptions.sql → 2_create_subscriptions.sql} +0 -0
  37. /data/db/migrations/{5_create_subscription_commands.sql → 3_create_subscription_commands.sql} +0 -0
  38. /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 [Array<Array<Integer, Hash>>] array of runner ids and query options
77
- # @return [Array<Hash>] array of raw events
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 [] if query_options.empty?
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
- preloader.preload_related_objects(raw_events)
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::EventTypeQueries]
151
- def event_type_queries
152
- EventTypeQueries.new(connection)
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/stream_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/preloader'
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 streams
24
- # @return [PgEventstore::StreamQueries, nil]
25
- attribute(:streams)
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: { event_type_ids: Array => event_type_ids } }
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: { event_type_ids: Array => event_type_ids } }
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.resolve_links(options[:resolve_link_tos])
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
- "streams.#{attr} = ?"
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 stream [PgEventstore::Stream]
79
+ # @param event_types [Array<String>, nil]
84
80
  # @return [void]
85
- def add_stream(stream)
86
- @sql_builder.where("streams.id = ?", stream.id)
87
- end
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 = event_type_ids.size.times.map do
85
+ sql = event_types.size.times.map do
96
86
  "?"
97
87
  end.join(", ")
98
- @sql_builder.where("events.event_type_id IN (#{sql})", *event_type_ids)
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 WHERE schemaname NOT IN ('pg_catalog', 'information_schema') AND tablename != 'migrations'
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
- def to_exec_params
93
- return [single_query_sql, @positional_values] if @union_values.empty?
103
+ # @param sql [String]
104
+ # @return self
105
+ def group(sql)
106
+ @group_values.push(sql)
107
+ self
108
+ end
94
109
 
95
- [union_query_sql, @positional_values]
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.to_exec_params
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})"
@@ -5,7 +5,7 @@ require 'digest/md5'
5
5
  module PgEventstore
6
6
  class Stream
7
7
  SYSTEM_STREAM_PREFIX = '$'
8
- INITIAL_STREAM_REVISION = -1 # this is the default value of streams.stream_revision column
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, :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
- # @param id [Integer, nil] internal stream's id, read only
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&.dig('global_position'))
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['global_position']) do
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
@@ -130,6 +130,7 @@ module PgEventstore
130
130
  chunk_query_interval: chunk_query_interval,
131
131
  last_chunk_fed_at: Time.at(0).utc,
132
132
  last_chunk_greatest_position: nil,
133
+ time_between_restarts: time_between_restarts,
133
134
  state: RunnerState::STATES[:initial]
134
135
  )
135
136
  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(1, 0)
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.map { |runner| [runner.id, runner.next_chunk_query_opts] }
19
- raw_events = subscription_queries.subscriptions_events(runners_query_options)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgEventstore
4
- VERSION = "0.9.0"
4
+ VERSION = "0.10.2"
5
5
  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.9.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-02-23 00:00:00.000000000 Z
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/1_create_streams.sql
54
- - db/migrations/2_create_event_types.sql
55
- - db/migrations/3_create_events.sql
56
- - db/migrations/4_create_subscriptions.sql
57
- - db/migrations/5_create_subscription_commands.sql
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/event_type_queries.rb
95
- - lib/pg_eventstore/queries/preloader.rb
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);