pg_eventstore 0.2.3 → 0.2.4
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 +6 -0
- data/db/migrations/3_extract_type_into_separate_table.sql +17 -0
- data/db/migrations/4_populate_event_types.rb +11 -0
- data/db/migrations/5_adjust_indexes.sql +6 -0
- data/db/migrations/6_change_events_event_type_id_null_constraint.sql +1 -0
- data/db/migrations/7_change_events_type_constraint.sql +1 -0
- data/lib/pg_eventstore/queries/event_queries.rb +30 -4
- data/lib/pg_eventstore/queries/event_type_queries.rb +50 -0
- data/lib/pg_eventstore/queries/transaction_queries.rb +14 -5
- data/lib/pg_eventstore/queries.rb +1 -0
- data/lib/pg_eventstore/query_builders/events_filtering_query.rb +15 -13
- data/lib/pg_eventstore/tasks/setup.rake +10 -5
- data/lib/pg_eventstore/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc158c2f99fee36514e1902199691dfb93e882ecdebe4738bbcf787fadc8f514
|
4
|
+
data.tar.gz: a70d00d4bdaef48223e35234f01d938a713f9306aa1bfb45bde99733b3654c39
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf38d241b001b4244d9fa915d2dfcabec7ac9456aa83832b8d850fe3fd34e48b0ef8b9ef5f0cca91b776124ec3333dea658220f3bd427309b3b1ae09a12714ae
|
7
|
+
data.tar.gz: af5b5e4394ee696790145852ac6a7761728e5cc7e7aca127e6cde112b857e5516b121ac2bb7bf2280e7e66c93cf0823e33bcbe1f258cadbff31d50f83d9972a8
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## [0.2.4] - 2023-12-20
|
2
|
+
|
3
|
+
Due to performance issues under certain circumstances, searching by event type was giving bad performance. I decided to extract `type` column from `events` table into separated table. **No breaking changes in public API though.**
|
4
|
+
|
5
|
+
**Warning** The migrations this version has, requires you to shut down applications that use `pg_eventstore` and only then run `rake pg_eventstore:migrate`.
|
6
|
+
|
1
7
|
## [0.2.3] - 2023-12-18
|
2
8
|
|
3
9
|
- Fix performance when searching by event type only(under certain circumstances PosetgreSQL was picking wrong index).
|
@@ -0,0 +1,17 @@
|
|
1
|
+
CREATE TABLE public.event_types
|
2
|
+
(
|
3
|
+
id bigserial NOT NULL,
|
4
|
+
type character varying NOT NULL
|
5
|
+
);
|
6
|
+
|
7
|
+
ALTER TABLE ONLY public.events ADD COLUMN event_type_id bigint;
|
8
|
+
|
9
|
+
ALTER TABLE ONLY public.event_types
|
10
|
+
ADD CONSTRAINT event_types_pkey PRIMARY KEY (id);
|
11
|
+
|
12
|
+
ALTER TABLE ONLY public.events
|
13
|
+
ADD CONSTRAINT events_event_type_fk FOREIGN KEY (event_type_id)
|
14
|
+
REFERENCES public.event_types (id);
|
15
|
+
|
16
|
+
CREATE UNIQUE INDEX idx_event_types_type ON public.event_types USING btree (type);
|
17
|
+
CREATE INDEX idx_events_event_type_id ON public.events USING btree (event_type_id);
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
PgEventstore.connection.with do |conn|
|
4
|
+
types = conn.exec('select type from events group by type').to_a.map { |attrs| attrs['type'] }
|
5
|
+
types.each.with_index(1) do |type, index|
|
6
|
+
id = conn.exec_params('SELECT id FROM event_types WHERE type = $1', [type]).to_a.first['id']
|
7
|
+
id ||= conn.exec_params('INSERT INTO event_types (type) VALUES ($1) RETURNING *', [type]).to_a.first['id']
|
8
|
+
conn.exec_params('UPDATE events SET event_type_id = $1 WHERE type = $2 AND event_type_id IS NULL', [id, type])
|
9
|
+
puts "Processed #{index} types of #{types.size}"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
CREATE INDEX idx_events_global_position ON public.events USING btree (global_position);
|
2
|
+
|
3
|
+
DROP INDEX idx_events_stream_id_and_type_and_revision;
|
4
|
+
DROP INDEX idx_events_type_and_stream_id_and_position;
|
5
|
+
DROP INDEX idx_events_global_position_including_type;
|
6
|
+
DROP INDEX idx_events_type_and_position;
|
@@ -0,0 +1 @@
|
|
1
|
+
ALTER TABLE public.events ALTER COLUMN event_type_id SET NOT NULL;
|
@@ -0,0 +1 @@
|
|
1
|
+
ALTER TABLE public.events ALTER COLUMN type DROP NOT NULL;
|
@@ -22,6 +22,7 @@ module PgEventstore
|
|
22
22
|
# @param options [Hash]
|
23
23
|
# @return [Array<PgEventstore::Event>]
|
24
24
|
def stream_events(stream, options)
|
25
|
+
options = include_event_types_ids(options)
|
25
26
|
exec_params = events_filtering(stream, options).to_exec_params
|
26
27
|
pg_result = connection.with do |conn|
|
27
28
|
conn.exec_params(*exec_params)
|
@@ -35,17 +36,18 @@ module PgEventstore
|
|
35
36
|
def insert(stream, event)
|
36
37
|
serializer.serialize(event)
|
37
38
|
|
38
|
-
attributes = event.options_hash.slice(:id, :
|
39
|
+
attributes = event.options_hash.slice(:id, :data, :metadata, :stream_revision, :link_id).compact
|
39
40
|
attributes[:stream_id] = stream.id
|
41
|
+
attributes[:event_type_id] = event_type_queries.find_or_create_type(event.type)
|
40
42
|
|
41
43
|
sql = <<~SQL
|
42
44
|
INSERT INTO events (#{attributes.keys.join(', ')})
|
43
|
-
VALUES (#{(
|
44
|
-
RETURNING
|
45
|
+
VALUES (#{positional_vars(attributes.values)})
|
46
|
+
RETURNING *, $#{attributes.values.size + 1} as type
|
45
47
|
SQL
|
46
48
|
|
47
49
|
pg_result = connection.with do |conn|
|
48
|
-
conn.exec_params(sql, attributes.values)
|
50
|
+
conn.exec_params(sql, [*attributes.values, event.type])
|
49
51
|
end
|
50
52
|
deserializer.without_middlewares.deserialize_one(pg_result).tap do |persisted_event|
|
51
53
|
persisted_event.stream = stream
|
@@ -63,5 +65,29 @@ module PgEventstore
|
|
63
65
|
|
64
66
|
QueryBuilders::EventsFiltering.specific_stream_filtering(stream, options, offset: offset)
|
65
67
|
end
|
68
|
+
|
69
|
+
# Replaces filter by event type strings with filter by event type ids
|
70
|
+
# @param options [Hash]
|
71
|
+
# @return [Hash]
|
72
|
+
def include_event_types_ids(options)
|
73
|
+
options in { filter: { event_types: Array => event_types } }
|
74
|
+
return options unless event_types
|
75
|
+
|
76
|
+
filter = options[:filter].dup
|
77
|
+
filter[:event_type_ids] = event_type_queries.find_event_types(event_types).uniq
|
78
|
+
filter.delete(:event_types)
|
79
|
+
options.merge(filter: filter)
|
80
|
+
end
|
81
|
+
|
82
|
+
# @param array [Array]
|
83
|
+
# @return [String] positional variables, based on array size. Example: "$1, $2, $3"
|
84
|
+
def positional_vars(array)
|
85
|
+
array.size.times.map { |t| "$#{t + 1}" }.join(', ')
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [PgEventstore::EventTypeQueries]
|
89
|
+
def event_type_queries
|
90
|
+
EventTypeQueries.new(connection)
|
91
|
+
end
|
66
92
|
end
|
67
93
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
# @!visibility private
|
5
|
+
class EventTypeQueries
|
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 type [String]
|
15
|
+
# @return [Integer] event type's id
|
16
|
+
def find_or_create_type(type)
|
17
|
+
find_type(type) || create_type(type)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param type [String]
|
21
|
+
# @return [Integer, nil] event type's id
|
22
|
+
def find_type(type)
|
23
|
+
connection.with do |conn|
|
24
|
+
conn.exec_params('SELECT id FROM event_types WHERE type = $1', [type])
|
25
|
+
end.to_a.dig(0, 'id')
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param type [String]
|
29
|
+
# @return [Integer] event type's id
|
30
|
+
def create_type(type)
|
31
|
+
connection.with do |conn|
|
32
|
+
conn.exec_params('INSERT INTO event_types (type) VALUES ($1) RETURNING id', [type])
|
33
|
+
end.to_a.dig(0, 'id')
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param types [Array<String>]
|
37
|
+
# @return [Array<Integer, nil>]
|
38
|
+
def find_event_types(types)
|
39
|
+
connection.with do |conn|
|
40
|
+
conn.exec_params(<<~SQL, [types])
|
41
|
+
SELECT event_types.id, types.type
|
42
|
+
FROM event_types
|
43
|
+
RIGHT JOIN (
|
44
|
+
SELECT unnest($1::varchar[]) type
|
45
|
+
) types ON types.type = event_types.type
|
46
|
+
SQL
|
47
|
+
end.to_a.map { |attrs| attrs['id'] }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -19,14 +19,23 @@ module PgEventstore
|
|
19
19
|
next yield
|
20
20
|
end
|
21
21
|
|
22
|
-
conn
|
23
|
-
conn.exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
|
22
|
+
pg_transaction(conn) do
|
24
23
|
yield
|
25
24
|
end
|
26
25
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# @param pg_connection [PG::Connection]
|
31
|
+
# @return [void]
|
32
|
+
def pg_transaction(pg_connection)
|
33
|
+
pg_connection.transaction do
|
34
|
+
pg_connection.exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
|
35
|
+
yield
|
36
|
+
end
|
37
|
+
rescue PG::TRSerializationFailure, PG::TRDeadlockDetected
|
38
|
+
retry
|
30
39
|
end
|
31
40
|
end
|
32
41
|
end
|
@@ -23,8 +23,8 @@ module PgEventstore
|
|
23
23
|
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
24
24
|
def all_stream_filtering(options, offset: 0)
|
25
25
|
event_filter = new
|
26
|
-
options in { filter: {
|
27
|
-
event_filter.add_event_types(
|
26
|
+
options in { filter: { event_type_ids: Array => event_type_ids } }
|
27
|
+
event_filter.add_event_types(event_type_ids)
|
28
28
|
event_filter.add_limit(options[:max_count])
|
29
29
|
event_filter.add_offset(offset)
|
30
30
|
event_filter.resolve_links(options[:resolve_link_tos])
|
@@ -41,8 +41,8 @@ module PgEventstore
|
|
41
41
|
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
42
42
|
def specific_stream_filtering(stream, options, offset: 0)
|
43
43
|
event_filter = new
|
44
|
-
options in { filter: {
|
45
|
-
event_filter.add_event_types(
|
44
|
+
options in { filter: { event_type_ids: Array => event_type_ids } }
|
45
|
+
event_filter.add_event_types(event_type_ids)
|
46
46
|
event_filter.add_limit(options[:max_count])
|
47
47
|
event_filter.add_offset(offset)
|
48
48
|
event_filter.resolve_links(options[:resolve_link_tos])
|
@@ -58,8 +58,10 @@ module PgEventstore
|
|
58
58
|
SQLBuilder.new.
|
59
59
|
select('events.*').
|
60
60
|
select('row_to_json(streams.*) as stream').
|
61
|
+
select('event_types.type as type').
|
61
62
|
from('events').
|
62
63
|
join('JOIN streams ON streams.id = events.stream_id').
|
64
|
+
join('JOIN event_types ON event_types.id = events.event_type_id').
|
63
65
|
limit(DEFAULT_LIMIT).
|
64
66
|
offset(DEFAULT_OFFSET)
|
65
67
|
end
|
@@ -85,16 +87,16 @@ module PgEventstore
|
|
85
87
|
@sql_builder.where("streams.id = ?", stream.id)
|
86
88
|
end
|
87
89
|
|
88
|
-
# @param
|
90
|
+
# @param event_type_ids [Array<Integer>, nil]
|
89
91
|
# @return [void]
|
90
|
-
def add_event_types(
|
91
|
-
return if
|
92
|
-
return if
|
93
|
-
|
94
|
-
sql =
|
95
|
-
"
|
96
|
-
end.join("
|
97
|
-
@sql_builder.where(sql, *
|
92
|
+
def add_event_types(event_type_ids)
|
93
|
+
return if event_type_ids.nil?
|
94
|
+
return if event_type_ids.empty?
|
95
|
+
|
96
|
+
sql = event_type_ids.size.times.map do
|
97
|
+
"?"
|
98
|
+
end.join(", ")
|
99
|
+
@sql_builder.where("event_types.id IN (#{sql})", *event_type_ids)
|
98
100
|
end
|
99
101
|
|
100
102
|
# @param revision [Integer, nil]
|
@@ -31,12 +31,16 @@ namespace :pg_eventstore do
|
|
31
31
|
latest_migration =
|
32
32
|
conn.exec('SELECT number FROM migrations ORDER BY number DESC LIMIT 1').to_a.dig(0, 'number') || -1
|
33
33
|
|
34
|
-
Dir
|
35
|
-
|
36
|
-
|
34
|
+
Dir.chdir migration_files_root do
|
35
|
+
Dir["*.{sql,rb}"].each do |f_name|
|
36
|
+
number = File.basename(f_name).split('_')[0].to_i
|
37
|
+
next if latest_migration >= number
|
37
38
|
|
38
|
-
|
39
|
-
|
39
|
+
if File.extname(f_name) == '.rb'
|
40
|
+
load f_name
|
41
|
+
else
|
42
|
+
conn.exec(File.read(f_name))
|
43
|
+
end
|
40
44
|
conn.exec_params('INSERT INTO migrations (number) VALUES ($1)', [number])
|
41
45
|
end
|
42
46
|
end
|
@@ -53,6 +57,7 @@ namespace :pg_eventstore do
|
|
53
57
|
conn.exec <<~SQL
|
54
58
|
DROP TABLE IF EXISTS public.events;
|
55
59
|
DROP TABLE IF EXISTS public.streams;
|
60
|
+
DROP TABLE IF EXISTS public.event_types;
|
56
61
|
DROP TABLE IF EXISTS public.migrations;
|
57
62
|
DROP EXTENSION IF EXISTS "uuid-ossp";
|
58
63
|
DROP EXTENSION IF EXISTS pgcrypto;
|
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.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Dzyzenko
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-12-
|
11
|
+
date: 2023-12-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -56,6 +56,11 @@ files:
|
|
56
56
|
- db/migrations/0_improve_all_stream_indexes.sql
|
57
57
|
- db/migrations/1_improve_specific_stream_indexes.sql
|
58
58
|
- db/migrations/2_adjust_global_position_index.sql
|
59
|
+
- db/migrations/3_extract_type_into_separate_table.sql
|
60
|
+
- db/migrations/4_populate_event_types.rb
|
61
|
+
- db/migrations/5_adjust_indexes.sql
|
62
|
+
- db/migrations/6_change_events_event_type_id_null_constraint.sql
|
63
|
+
- db/migrations/7_change_events_type_constraint.sql
|
59
64
|
- docs/appending_events.md
|
60
65
|
- docs/configuration.md
|
61
66
|
- docs/events_and_streams.md
|
@@ -80,6 +85,7 @@ files:
|
|
80
85
|
- lib/pg_eventstore/pg_result_deserializer.rb
|
81
86
|
- lib/pg_eventstore/queries.rb
|
82
87
|
- lib/pg_eventstore/queries/event_queries.rb
|
88
|
+
- lib/pg_eventstore/queries/event_type_queries.rb
|
83
89
|
- lib/pg_eventstore/queries/stream_queries.rb
|
84
90
|
- lib/pg_eventstore/queries/transaction_queries.rb
|
85
91
|
- lib/pg_eventstore/query_builders/events_filtering_query.rb
|