pg_eventstore 0.2.3 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -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: 9aec0b53f755f972ad70c47fa0fd38e3635c02cee43bb90c8f8012e873c30f67
|
4
|
+
data.tar.gz: fe2465953794eff73f05c0f326e582f40c69557b94c2e9dfe515e25fb22d5dcc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 427953c92c15b66fd2e7b5361d91ab0a58fbe91ee69fd878b9168876448be2e4b789f08ae7b8126a420678cc11cb06794337341bb8041b55bbfab7c54d37dd08
|
7
|
+
data.tar.gz: 561149f51f904798f98878b8632f77ed2b638dbffd045b4cf3d163a07471ded9c6e324c8f90461103958e41ff26710d2cf65a0df10c4cf2004651b531be5b4c7
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## [0.2.5] - 2023-12-20
|
2
|
+
|
3
|
+
- Fix bug when migrations files are returned in unsorted order on some systems
|
4
|
+
|
5
|
+
## [0.2.4] - 2023-12-20
|
6
|
+
|
7
|
+
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.**
|
8
|
+
|
9
|
+
**Warning** The migrations this version has, requires you to shut down applications that use `pg_eventstore` and only then run `rake pg_eventstore:migrate`.
|
10
|
+
|
1
11
|
## [0.2.3] - 2023-12-18
|
2
12
|
|
3
13
|
- 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}"].sort.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.5
|
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
|