pg_eventstore 0.2.2 → 0.2.3
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 +4 -0
- data/db/migrations/2_adjust_global_position_index.sql +4 -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 +67 -0
- data/lib/pg_eventstore/queries/stream_queries.rb +64 -0
- data/lib/pg_eventstore/queries/transaction_queries.rb +32 -0
- data/lib/pg_eventstore/queries.rb +14 -119
- data/lib/pg_eventstore/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd2524ab67ce1fa009de14a032cdc3d922051420bc2c02e933cb63ee245db02d
|
4
|
+
data.tar.gz: f49867dab08825770aae44e3e1bec38a8144504e3bea2e6618ce47a0d6e8cb54
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9925413067ca47159cbd5902f2716bbd9e5fe1e0c63aa7397f077c95878bacc2da57cd65f32160a0e1d70d855e101d16fb6d5aa5b537c9630c4290449db8c16
|
7
|
+
data.tar.gz: 65add6ce6e11ebe0634cec3949709ad3c8d42b105ba9325c5d6d45c45bf2339c0e47f9286dbb71edc8e1c99636aa449adc34926f4a6d57c695c50655609d5451
|
data/CHANGELOG.md
CHANGED
@@ -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;
|
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,67 @@
|
|
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
|
+
exec_params = events_filtering(stream, options).to_exec_params
|
26
|
+
pg_result = connection.with do |conn|
|
27
|
+
conn.exec_params(*exec_params)
|
28
|
+
end
|
29
|
+
deserializer.deserialize_many(pg_result)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param stream [PgEventstore::Stream] persisted stream
|
33
|
+
# @param event [PgEventstore::Event]
|
34
|
+
# @return [PgEventstore::Event]
|
35
|
+
def insert(stream, event)
|
36
|
+
serializer.serialize(event)
|
37
|
+
|
38
|
+
attributes = event.options_hash.slice(:id, :type, :data, :metadata, :stream_revision, :link_id).compact
|
39
|
+
attributes[:stream_id] = stream.id
|
40
|
+
|
41
|
+
sql = <<~SQL
|
42
|
+
INSERT INTO events (#{attributes.keys.join(', ')})
|
43
|
+
VALUES (#{(1..attributes.values.size).map { |n| "$#{n}" }.join(', ')})
|
44
|
+
RETURNING *
|
45
|
+
SQL
|
46
|
+
|
47
|
+
pg_result = connection.with do |conn|
|
48
|
+
conn.exec_params(sql, attributes.values)
|
49
|
+
end
|
50
|
+
deserializer.without_middlewares.deserialize_one(pg_result).tap do |persisted_event|
|
51
|
+
persisted_event.stream = stream
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# @param stream [PgEventstore::Stream]
|
58
|
+
# @param options [Hash]
|
59
|
+
# @param offset [Integer]
|
60
|
+
# @return [PgEventstore::EventsFilteringQuery]
|
61
|
+
def events_filtering(stream, options, offset: 0)
|
62
|
+
return QueryBuilders::EventsFiltering.all_stream_filtering(options, offset: offset) if stream.all_stream?
|
63
|
+
|
64
|
+
QueryBuilders::EventsFiltering.specific_stream_filtering(stream, options, offset: offset)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
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,32 @@
|
|
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
|
+
conn.transaction do
|
23
|
+
conn.exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
|
24
|
+
yield
|
25
|
+
end
|
26
|
+
end
|
27
|
+
rescue PG::TRSerializationFailure, PG::TRDeadlockDetected => e
|
28
|
+
retry if [PG::PQTRANS_IDLE, PG::PQTRANS_UNKNOWN].include?(e.connection.transaction_status)
|
29
|
+
raise
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,127 +1,22 @@
|
|
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'
|
4
6
|
|
5
7
|
module PgEventstore
|
6
8
|
# @!visibility private
|
7
9
|
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
|
10
|
+
include Extensions::OptionsExtension
|
11
|
+
|
12
|
+
# @!attribute events
|
13
|
+
# @return [PgEventstore::EventQueries, nil]
|
14
|
+
attribute(:events)
|
15
|
+
# @!attribute streams
|
16
|
+
# @return [PgEventstore::StreamQueries, nil]
|
17
|
+
attribute(:streams)
|
18
|
+
# @!attribute transactions
|
19
|
+
# @return [PgEventstore::TransactionQueries, nil]
|
20
|
+
attribute(:transactions)
|
126
21
|
end
|
127
22
|
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.2.
|
4
|
+
version: 0.2.3
|
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-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -55,6 +55,7 @@ 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
|
58
59
|
- docs/appending_events.md
|
59
60
|
- docs/configuration.md
|
60
61
|
- docs/events_and_streams.md
|
@@ -78,6 +79,9 @@ files:
|
|
78
79
|
- lib/pg_eventstore/middleware.rb
|
79
80
|
- lib/pg_eventstore/pg_result_deserializer.rb
|
80
81
|
- lib/pg_eventstore/queries.rb
|
82
|
+
- lib/pg_eventstore/queries/event_queries.rb
|
83
|
+
- lib/pg_eventstore/queries/stream_queries.rb
|
84
|
+
- lib/pg_eventstore/queries/transaction_queries.rb
|
81
85
|
- lib/pg_eventstore/query_builders/events_filtering_query.rb
|
82
86
|
- lib/pg_eventstore/rspec/has_option_matcher.rb
|
83
87
|
- lib/pg_eventstore/sql_builder.rb
|