pg_eventstore 0.9.0 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
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);