pg_eventstore 0.2.2 → 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 +10 -0
- data/db/migrations/2_adjust_global_position_index.sql +4 -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/client.rb +20 -6
- data/lib/pg_eventstore/commands/append.rb +4 -4
- data/lib/pg_eventstore/commands/multiple.rb +1 -1
- data/lib/pg_eventstore/commands/read.rb +2 -2
- data/lib/pg_eventstore/queries/event_queries.rb +93 -0
- data/lib/pg_eventstore/queries/event_type_queries.rb +50 -0
- data/lib/pg_eventstore/queries/stream_queries.rb +64 -0
- data/lib/pg_eventstore/queries/transaction_queries.rb +41 -0
- data/lib/pg_eventstore/queries.rb +15 -119
- 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 +12 -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,13 @@
|
|
|
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
|
+
|
|
7
|
+
## [0.2.3] - 2023-12-18
|
|
8
|
+
|
|
9
|
+
- Fix performance when searching by event type only(under certain circumstances PosetgreSQL was picking wrong index).
|
|
10
|
+
|
|
1
11
|
## [0.2.2] - 2023-12-14
|
|
2
12
|
|
|
3
13
|
- Fix `pg_eventstore:drop` rake task to also drop `migrations` table
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
CREATE INDEX idx_events_global_position_including_type ON public.events USING btree (global_position) INCLUDE (type);
|
|
2
|
+
COMMENT ON INDEX idx_events_global_position_including_type IS 'Usually "type" column has low distinct values. Thus, composit index by "type" and "global_position" columns may not be picked by Query Planner properly. Improve an index by "global_position" by including "type" column which allows Query Planner to perform better by picking the correct index.';
|
|
3
|
+
|
|
4
|
+
DROP INDEX idx_events_global_position;
|
|
@@ -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;
|
data/lib/pg_eventstore/client.rb
CHANGED
|
@@ -27,7 +27,11 @@ module PgEventstore
|
|
|
27
27
|
# @raise [PgEventstore::WrongExpectedRevisionError]
|
|
28
28
|
def append_to_stream(stream, events_or_event, options: {}, middlewares: nil)
|
|
29
29
|
result =
|
|
30
|
-
Commands::Append.new(
|
|
30
|
+
Commands::Append.new(
|
|
31
|
+
Queries.new(
|
|
32
|
+
streams: stream_queries, events: event_queries(middlewares(middlewares)), transactions: transaction_queries
|
|
33
|
+
)
|
|
34
|
+
).call(stream, *events_or_event, options: options)
|
|
31
35
|
events_or_event.is_a?(Array) ? result : result.first
|
|
32
36
|
end
|
|
33
37
|
|
|
@@ -43,7 +47,7 @@ module PgEventstore
|
|
|
43
47
|
#
|
|
44
48
|
# @return the result of the given block
|
|
45
49
|
def multiple(&blk)
|
|
46
|
-
Commands::Multiple.new(
|
|
50
|
+
Commands::Multiple.new(Queries.new(transactions: transaction_queries)).call(&blk)
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
# Read events from the specific stream or from "all" stream.
|
|
@@ -101,7 +105,7 @@ module PgEventstore
|
|
|
101
105
|
# @raise [PgEventstore::StreamNotFoundError]
|
|
102
106
|
def read(stream, options: {}, middlewares: nil)
|
|
103
107
|
Commands::Read.
|
|
104
|
-
new(
|
|
108
|
+
new(Queries.new(streams: stream_queries, events: event_queries(middlewares(middlewares)))).
|
|
105
109
|
call(stream, options: { max_count: config.max_count }.merge(options))
|
|
106
110
|
end
|
|
107
111
|
|
|
@@ -120,10 +124,20 @@ module PgEventstore
|
|
|
120
124
|
PgEventstore.connection(config.name)
|
|
121
125
|
end
|
|
122
126
|
|
|
127
|
+
# @return [PgEventstore::StreamQueries]
|
|
128
|
+
def stream_queries
|
|
129
|
+
StreamQueries.new(connection)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @return [PgEventstore::TransactionQueries]
|
|
133
|
+
def transaction_queries
|
|
134
|
+
TransactionQueries.new(connection)
|
|
135
|
+
end
|
|
136
|
+
|
|
123
137
|
# @param middlewares [Array<Object<#serialize, #deserialize>>]
|
|
124
|
-
# @return [PgEventstore::
|
|
125
|
-
def
|
|
126
|
-
|
|
138
|
+
# @return [PgEventstore::EventQueries]
|
|
139
|
+
def event_queries(middlewares)
|
|
140
|
+
EventQueries.new(
|
|
127
141
|
connection,
|
|
128
142
|
EventSerializer.new(middlewares),
|
|
129
143
|
PgResultDeserializer.new(middlewares, config.event_class_resolver)
|
|
@@ -14,14 +14,14 @@ module PgEventstore
|
|
|
14
14
|
def call(stream, *events, options: {})
|
|
15
15
|
raise SystemStreamError, stream if stream.system?
|
|
16
16
|
|
|
17
|
-
queries.transaction do
|
|
18
|
-
stream = queries.find_or_create_stream(stream)
|
|
17
|
+
queries.transactions.transaction do
|
|
18
|
+
stream = queries.streams.find_or_create_stream(stream)
|
|
19
19
|
revision = stream.stream_revision
|
|
20
20
|
assert_expected_revision!(revision, options[:expected_revision]) if options[:expected_revision]
|
|
21
21
|
events.map.with_index(1) do |event, index|
|
|
22
|
-
queries.insert(stream, prepared_event(event, revision + index))
|
|
22
|
+
queries.events.insert(stream, prepared_event(event, revision + index))
|
|
23
23
|
end.tap do
|
|
24
|
-
queries.update_stream_revision(stream, revision + events.size)
|
|
24
|
+
queries.streams.update_stream_revision(stream, revision + events.size)
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -15,9 +15,9 @@ module PgEventstore
|
|
|
15
15
|
# @return [Array<PgEventstore::Event>]
|
|
16
16
|
# @raise [PgEventstore::StreamNotFoundError]
|
|
17
17
|
def call(stream, options: {})
|
|
18
|
-
stream = queries.find_stream(stream) || raise(StreamNotFoundError, stream) unless stream.all_stream?
|
|
18
|
+
stream = queries.streams.find_stream(stream) || raise(StreamNotFoundError, stream) unless stream.all_stream?
|
|
19
19
|
|
|
20
|
-
queries.stream_events(stream, options)
|
|
20
|
+
queries.events.stream_events(stream, options)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pg_eventstore/query_builders/events_filtering_query'
|
|
4
|
+
|
|
5
|
+
module PgEventstore
|
|
6
|
+
# @!visibility private
|
|
7
|
+
class EventQueries
|
|
8
|
+
attr_reader :connection, :serializer, :deserializer
|
|
9
|
+
private :connection, :serializer, :deserializer
|
|
10
|
+
|
|
11
|
+
# @param connection [PgEventstore::Connection]
|
|
12
|
+
# @param serializer [PgEventstore::EventSerializer]
|
|
13
|
+
# @param deserializer [PgEventstore::PgResultDeserializer]
|
|
14
|
+
def initialize(connection, serializer, deserializer)
|
|
15
|
+
@connection = connection
|
|
16
|
+
@serializer = serializer
|
|
17
|
+
@deserializer = deserializer
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @see PgEventstore::Client#read for more info
|
|
21
|
+
# @param stream [PgEventstore::Stream]
|
|
22
|
+
# @param options [Hash]
|
|
23
|
+
# @return [Array<PgEventstore::Event>]
|
|
24
|
+
def stream_events(stream, options)
|
|
25
|
+
options = include_event_types_ids(options)
|
|
26
|
+
exec_params = events_filtering(stream, options).to_exec_params
|
|
27
|
+
pg_result = connection.with do |conn|
|
|
28
|
+
conn.exec_params(*exec_params)
|
|
29
|
+
end
|
|
30
|
+
deserializer.deserialize_many(pg_result)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param stream [PgEventstore::Stream] persisted stream
|
|
34
|
+
# @param event [PgEventstore::Event]
|
|
35
|
+
# @return [PgEventstore::Event]
|
|
36
|
+
def insert(stream, event)
|
|
37
|
+
serializer.serialize(event)
|
|
38
|
+
|
|
39
|
+
attributes = event.options_hash.slice(:id, :data, :metadata, :stream_revision, :link_id).compact
|
|
40
|
+
attributes[:stream_id] = stream.id
|
|
41
|
+
attributes[:event_type_id] = event_type_queries.find_or_create_type(event.type)
|
|
42
|
+
|
|
43
|
+
sql = <<~SQL
|
|
44
|
+
INSERT INTO events (#{attributes.keys.join(', ')})
|
|
45
|
+
VALUES (#{positional_vars(attributes.values)})
|
|
46
|
+
RETURNING *, $#{attributes.values.size + 1} as type
|
|
47
|
+
SQL
|
|
48
|
+
|
|
49
|
+
pg_result = connection.with do |conn|
|
|
50
|
+
conn.exec_params(sql, [*attributes.values, event.type])
|
|
51
|
+
end
|
|
52
|
+
deserializer.without_middlewares.deserialize_one(pg_result).tap do |persisted_event|
|
|
53
|
+
persisted_event.stream = stream
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# @param stream [PgEventstore::Stream]
|
|
60
|
+
# @param options [Hash]
|
|
61
|
+
# @param offset [Integer]
|
|
62
|
+
# @return [PgEventstore::EventsFilteringQuery]
|
|
63
|
+
def events_filtering(stream, options, offset: 0)
|
|
64
|
+
return QueryBuilders::EventsFiltering.all_stream_filtering(options, offset: offset) if stream.all_stream?
|
|
65
|
+
|
|
66
|
+
QueryBuilders::EventsFiltering.specific_stream_filtering(stream, options, offset: offset)
|
|
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
|
|
92
|
+
end
|
|
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
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgEventstore
|
|
4
|
+
# @!visibility private
|
|
5
|
+
class StreamQueries
|
|
6
|
+
attr_reader :connection
|
|
7
|
+
private :connection
|
|
8
|
+
|
|
9
|
+
# @param connection [PgEventstore::Connection]
|
|
10
|
+
def initialize(connection)
|
|
11
|
+
@connection = connection
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Finds a stream in the database by the given Stream object
|
|
15
|
+
# @param stream [PgEventstore::Stream]
|
|
16
|
+
# @return [PgEventstore::Stream, nil] persisted stream
|
|
17
|
+
def find_stream(stream)
|
|
18
|
+
builder =
|
|
19
|
+
SQLBuilder.new.
|
|
20
|
+
from('streams').
|
|
21
|
+
where('streams.context = ? AND streams.stream_name = ? AND streams.stream_id = ?', *stream.to_a).
|
|
22
|
+
limit(1)
|
|
23
|
+
pg_result = connection.with do |conn|
|
|
24
|
+
conn.exec_params(*builder.to_exec_params)
|
|
25
|
+
end
|
|
26
|
+
deserialize(pg_result) if pg_result.ntuples == 1
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @param stream [PgEventstore::Stream]
|
|
30
|
+
# @return [PgEventstore::RawStream] persisted stream
|
|
31
|
+
def create_stream(stream)
|
|
32
|
+
create_sql = <<~SQL
|
|
33
|
+
INSERT INTO streams (context, stream_name, stream_id) VALUES ($1, $2, $3) RETURNING *
|
|
34
|
+
SQL
|
|
35
|
+
pg_result = connection.with do |conn|
|
|
36
|
+
conn.exec_params(create_sql, stream.to_a)
|
|
37
|
+
end
|
|
38
|
+
deserialize(pg_result)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [PgEventstore::Stream] persisted stream
|
|
42
|
+
def find_or_create_stream(stream)
|
|
43
|
+
find_stream(stream) || create_stream(stream)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param stream [PgEventstore::Stream] persisted stream
|
|
47
|
+
# @return [void]
|
|
48
|
+
def update_stream_revision(stream, revision)
|
|
49
|
+
connection.with do |conn|
|
|
50
|
+
conn.exec_params(<<~SQL, [revision, stream.id])
|
|
51
|
+
UPDATE streams SET stream_revision = $1 WHERE id = $2
|
|
52
|
+
SQL
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# @param pg_result [PG::Result]
|
|
59
|
+
# @return [PgEventstore::Stream, nil]
|
|
60
|
+
def deserialize(pg_result)
|
|
61
|
+
PgEventstore::Stream.new(**pg_result.to_a.first.transform_keys(&:to_sym))
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgEventstore
|
|
4
|
+
# @!visibility private
|
|
5
|
+
class TransactionQueries
|
|
6
|
+
attr_reader :connection
|
|
7
|
+
private :connection
|
|
8
|
+
|
|
9
|
+
# @param connection [PgEventstore::Connection]
|
|
10
|
+
def initialize(connection)
|
|
11
|
+
@connection = connection
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [void]
|
|
15
|
+
def transaction
|
|
16
|
+
connection.with do |conn|
|
|
17
|
+
# We are inside a transaction already - no need to start another one
|
|
18
|
+
if [PG::PQTRANS_ACTIVE, PG::PQTRANS_INTRANS].include?(conn.transaction_status)
|
|
19
|
+
next yield
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
pg_transaction(conn) do
|
|
23
|
+
yield
|
|
24
|
+
end
|
|
25
|
+
end
|
|
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
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -1,127 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '
|
|
3
|
+
require_relative 'queries/transaction_queries'
|
|
4
|
+
require_relative 'queries/event_queries'
|
|
5
|
+
require_relative 'queries/stream_queries'
|
|
6
|
+
require_relative 'queries/event_type_queries'
|
|
4
7
|
|
|
5
8
|
module PgEventstore
|
|
6
9
|
# @!visibility private
|
|
7
10
|
class Queries
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# @return [void]
|
|
21
|
-
def transaction
|
|
22
|
-
connection.with do |conn|
|
|
23
|
-
# We are inside a transaction already - no need to start another one
|
|
24
|
-
if [PG::PQTRANS_ACTIVE, PG::PQTRANS_INTRANS].include?(conn.transaction_status)
|
|
25
|
-
next yield
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
conn.transaction do
|
|
29
|
-
conn.exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
|
|
30
|
-
yield
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
rescue PG::TRSerializationFailure, PG::TRDeadlockDetected => e
|
|
34
|
-
retry if [PG::PQTRANS_IDLE, PG::PQTRANS_UNKNOWN].include?(e.connection.transaction_status)
|
|
35
|
-
raise
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Finds a stream in the database by the given Stream object
|
|
39
|
-
# @param stream [PgEventstore::Stream]
|
|
40
|
-
# @return [PgEventstore::Stream, nil] persisted stream
|
|
41
|
-
def find_stream(stream)
|
|
42
|
-
builder =
|
|
43
|
-
SQLBuilder.new.
|
|
44
|
-
from('streams').
|
|
45
|
-
where('streams.context = ? AND streams.stream_name = ? AND streams.stream_id = ?', *stream.to_a).
|
|
46
|
-
limit(1)
|
|
47
|
-
pg_result = connection.with do |conn|
|
|
48
|
-
conn.exec_params(*builder.to_exec_params)
|
|
49
|
-
end
|
|
50
|
-
PgEventstore::Stream.new(**pg_result.to_a.first.transform_keys(&:to_sym)) if pg_result.ntuples == 1
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# @param stream [PgEventstore::Stream]
|
|
54
|
-
# @return [PgEventstore::RawStream] persisted stream
|
|
55
|
-
def create_stream(stream)
|
|
56
|
-
create_sql = <<~SQL
|
|
57
|
-
INSERT INTO streams (context, stream_name, stream_id) VALUES ($1, $2, $3) RETURNING *
|
|
58
|
-
SQL
|
|
59
|
-
pg_result = connection.with do |conn|
|
|
60
|
-
conn.exec_params(create_sql, stream.to_a)
|
|
61
|
-
end
|
|
62
|
-
PgEventstore::Stream.new(**pg_result.to_a.first.transform_keys(&:to_sym))
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# @return [PgEventstore::Stream] persisted stream
|
|
66
|
-
def find_or_create_stream(stream)
|
|
67
|
-
find_stream(stream) || create_stream(stream)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# @see PgEventstore::Client#read for more info
|
|
71
|
-
# @param stream [PgEventstore::Stream]
|
|
72
|
-
# @param options [Hash]
|
|
73
|
-
# @return [Array<PgEventstore::Event>]
|
|
74
|
-
def stream_events(stream, options)
|
|
75
|
-
exec_params = events_filtering(stream, options).to_exec_params
|
|
76
|
-
pg_result = connection.with do |conn|
|
|
77
|
-
conn.exec_params(*exec_params)
|
|
78
|
-
end
|
|
79
|
-
deserializer.deserialize_many(pg_result)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# @param stream [PgEventstore::Stream] persisted stream
|
|
83
|
-
# @param event [PgEventstore::Event]
|
|
84
|
-
# @return [PgEventstore::Event]
|
|
85
|
-
def insert(stream, event)
|
|
86
|
-
serializer.serialize(event)
|
|
87
|
-
|
|
88
|
-
attributes = event.options_hash.slice(:id, :type, :data, :metadata, :stream_revision, :link_id).compact
|
|
89
|
-
attributes[:stream_id] = stream.id
|
|
90
|
-
|
|
91
|
-
sql = <<~SQL
|
|
92
|
-
INSERT INTO events (#{attributes.keys.join(', ')})
|
|
93
|
-
VALUES (#{(1..attributes.values.size).map { |n| "$#{n}" }.join(', ')})
|
|
94
|
-
RETURNING *
|
|
95
|
-
SQL
|
|
96
|
-
|
|
97
|
-
pg_result = connection.with do |conn|
|
|
98
|
-
conn.exec_params(sql, attributes.values)
|
|
99
|
-
end
|
|
100
|
-
deserializer.without_middlewares.deserialize_one(pg_result).tap do |persisted_event|
|
|
101
|
-
persisted_event.stream = stream
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# @param stream [PgEventstore::Stream] persisted stream
|
|
106
|
-
# @return [void]
|
|
107
|
-
def update_stream_revision(stream, revision)
|
|
108
|
-
connection.with do |conn|
|
|
109
|
-
conn.exec_params(<<~SQL, [revision, stream.id])
|
|
110
|
-
UPDATE streams SET stream_revision = $1 WHERE id = $2
|
|
111
|
-
SQL
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
private
|
|
116
|
-
|
|
117
|
-
# @param stream [PgEventstore::Stream]
|
|
118
|
-
# @param options [Hash]
|
|
119
|
-
# @param offset [Integer]
|
|
120
|
-
# @return [PgEventstore::EventsFilteringQuery]
|
|
121
|
-
def events_filtering(stream, options, offset: 0)
|
|
122
|
-
return QueryBuilders::EventsFiltering.all_stream_filtering(options, offset: offset) if stream.all_stream?
|
|
123
|
-
|
|
124
|
-
QueryBuilders::EventsFiltering.specific_stream_filtering(stream, options, offset: offset)
|
|
125
|
-
end
|
|
11
|
+
include Extensions::OptionsExtension
|
|
12
|
+
|
|
13
|
+
# @!attribute events
|
|
14
|
+
# @return [PgEventstore::EventQueries, nil]
|
|
15
|
+
attribute(:events)
|
|
16
|
+
# @!attribute streams
|
|
17
|
+
# @return [PgEventstore::StreamQueries, nil]
|
|
18
|
+
attribute(:streams)
|
|
19
|
+
# @!attribute transactions
|
|
20
|
+
# @return [PgEventstore::TransactionQueries, nil]
|
|
21
|
+
attribute(:transactions)
|
|
126
22
|
end
|
|
127
23
|
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
|
|
@@ -55,6 +55,12 @@ files:
|
|
|
55
55
|
- db/initial/tables.sql
|
|
56
56
|
- db/migrations/0_improve_all_stream_indexes.sql
|
|
57
57
|
- db/migrations/1_improve_specific_stream_indexes.sql
|
|
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
|
|
58
64
|
- docs/appending_events.md
|
|
59
65
|
- docs/configuration.md
|
|
60
66
|
- docs/events_and_streams.md
|
|
@@ -78,6 +84,10 @@ files:
|
|
|
78
84
|
- lib/pg_eventstore/middleware.rb
|
|
79
85
|
- lib/pg_eventstore/pg_result_deserializer.rb
|
|
80
86
|
- lib/pg_eventstore/queries.rb
|
|
87
|
+
- lib/pg_eventstore/queries/event_queries.rb
|
|
88
|
+
- lib/pg_eventstore/queries/event_type_queries.rb
|
|
89
|
+
- lib/pg_eventstore/queries/stream_queries.rb
|
|
90
|
+
- lib/pg_eventstore/queries/transaction_queries.rb
|
|
81
91
|
- lib/pg_eventstore/query_builders/events_filtering_query.rb
|
|
82
92
|
- lib/pg_eventstore/rspec/has_option_matcher.rb
|
|
83
93
|
- lib/pg_eventstore/sql_builder.rb
|