pg_eventstore 0.4.0 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/CODE_OF_CONDUCT.md +1 -1
- data/README.md +1 -0
- data/db/migrations/13_remove_duplicated_index.sql +1 -0
- data/docs/linking_events.md +96 -0
- data/docs/reading_events.md +56 -0
- data/lib/pg_eventstore/client.rb +30 -0
- data/lib/pg_eventstore/commands/append.rb +3 -11
- data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +22 -0
- data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +24 -0
- data/lib/pg_eventstore/commands/link_to.rb +33 -0
- data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +63 -0
- data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +62 -0
- data/lib/pg_eventstore/commands.rb +5 -0
- data/lib/pg_eventstore/errors.rb +21 -4
- data/lib/pg_eventstore/event_deserializer.rb +3 -12
- data/lib/pg_eventstore/extensions/options_extension.rb +44 -6
- data/lib/pg_eventstore/pg_connection.rb +20 -3
- data/lib/pg_eventstore/queries/event_queries.rb +15 -10
- data/lib/pg_eventstore/queries/event_type_queries.rb +11 -0
- data/lib/pg_eventstore/queries/preloader.rb +37 -0
- data/lib/pg_eventstore/queries/stream_queries.rb +14 -1
- data/lib/pg_eventstore/queries/subscription_queries.rb +7 -1
- data/lib/pg_eventstore/queries.rb +1 -0
- data/lib/pg_eventstore/query_builders/events_filtering_query.rb +3 -20
- data/lib/pg_eventstore/stream.rb +15 -1
- data/lib/pg_eventstore/tasks/setup.rake +4 -0
- data/lib/pg_eventstore/version.rb +1 -1
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6af4900fd9dc0524bcc7abcf7becd913c33fc50e1f0cb097c124ab48ee9cf3cd
|
4
|
+
data.tar.gz: c5626c8b58e6265b7deed496b6531881ea352049cbaea6f29b2281abd7a7d5b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c4c6425faea1168e3396b14c7cd66fb3c7f10097d2e2645ebf7dba6ba2b9800ddb6372bac3f6c8d39baf4c78a46bbfc907b352274b3839ef5eeb7acd8dbec02
|
7
|
+
data.tar.gz: '09a4f239984bffc88abaa5e08aa4c4fa31df1856aebcb4c8b5fcd6bdd5e77806d556584680cdc5702cfd5f720bc5f91ecf19bdc98bdf15c4a16f3024e5df0151'
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
## [Unreleased]
|
2
|
+
|
3
|
+
## [0.5.2] - 2024-02-06
|
4
|
+
|
5
|
+
- Improve speed of `PgEventstore::Stream#eql?` a bit
|
6
|
+
|
7
|
+
## [0.5.1] - 2024-02-06
|
8
|
+
|
9
|
+
- Fix `PgEventstore::Stream` to be properly recognizable inside Hash
|
10
|
+
|
11
|
+
## [0.5.0] - 2024-02-05
|
12
|
+
|
13
|
+
- Fix event class resolving when providing `resolve_link_tos: true` option
|
14
|
+
- Return correct stream revision of the `Event#stream` object of the appended event
|
15
|
+
- Implement events linking feature
|
16
|
+
- Implement paginated read
|
17
|
+
- Remove duplicated `idx_events_event_type_id` index
|
18
|
+
|
1
19
|
## [0.4.0] - 2024-01-29
|
2
20
|
|
3
21
|
- Implement asynchronous subscriptions. Refer to the documentation for more info
|
data/CODE_OF_CONDUCT.md
CHANGED
@@ -39,7 +39,7 @@ This Code of Conduct applies within all community spaces, and also applies when
|
|
39
39
|
|
40
40
|
## Enforcement
|
41
41
|
|
42
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at ivan.dzyzenko@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
|
43
43
|
|
44
44
|
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
45
|
|
data/README.md
CHANGED
@@ -52,6 +52,7 @@ Documentation chapters:
|
|
52
52
|
- [Configuration](docs/configuration.md)
|
53
53
|
- [Events and streams definitions](docs/events_and_streams.md)
|
54
54
|
- [Appending events](docs/appending_events.md)
|
55
|
+
- [Linking events](docs/linking_events.md)
|
55
56
|
- [Reading events](docs/reading_events.md)
|
56
57
|
- [Subscriptions](docs/subscriptions.md)
|
57
58
|
- [Writing middlewares](docs/writing_middleware.md)
|
@@ -0,0 +1 @@
|
|
1
|
+
DROP INDEX idx_events_event_type_id;
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# Linking Events
|
2
|
+
|
3
|
+
## Linking single event
|
4
|
+
|
5
|
+
You can create a link to an existing event. Next example demonstrates how you can link an existing event from another stream:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class SomethingHappened < PgEventstore::Event
|
9
|
+
end
|
10
|
+
|
11
|
+
event = SomethingHappened.new(
|
12
|
+
type: 'some-event', data: { title: 'Something happened' }
|
13
|
+
)
|
14
|
+
|
15
|
+
events_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyAwesomeStream', stream_id: '1')
|
16
|
+
projection_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyAwesomeProjection', stream_id: '1')
|
17
|
+
# Persist our event
|
18
|
+
event = PgEventstore.client.append_to_stream(events_stream, event)
|
19
|
+
|
20
|
+
# Link event into your projection
|
21
|
+
PgEventstore.client.link_to(projection_stream, event)
|
22
|
+
```
|
23
|
+
|
24
|
+
The linked event can later be fetched by providing the `:resolve_link_tos` option when reading from the stream:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
projection_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyAwesomeProjection', stream_id: '1')
|
28
|
+
PgEventstore.client.read(projection_stream, options: { resolve_link_tos: true })
|
29
|
+
```
|
30
|
+
|
31
|
+
If you don't provide the `:resolve_link_tos` option, the linked event will be returned instead of the original one.
|
32
|
+
|
33
|
+
## Linking multiple events
|
34
|
+
|
35
|
+
You can provide an array of events to link to the target stream:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class SomethingHappened < PgEventstore::Event
|
39
|
+
end
|
40
|
+
|
41
|
+
events = 3.times.map { |i| SomethingHappened.new(type: 'some-event', data: { title: "Something happened-#{i}" }) }
|
42
|
+
events_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyAwesomeStream', stream_id: '1')
|
43
|
+
projection_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyAwesomeProjection', stream_id: '1')
|
44
|
+
events = PgEventstore.client.append_to_stream(events_stream, events)
|
45
|
+
# Link events
|
46
|
+
PgEventstore.client.link_to(projection_stream, events)
|
47
|
+
```
|
48
|
+
|
49
|
+
## Handling concurrency
|
50
|
+
|
51
|
+
Linking concurrency is implemented the same way as appending concurrency. You can check [**Handling concurrency**](appending_events.md#handling-concurrency) chapter of **Appending Events** section.
|
52
|
+
|
53
|
+
Example:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
require 'securerandom'
|
57
|
+
class SomethingHappened < PgEventstore::Event
|
58
|
+
end
|
59
|
+
|
60
|
+
event1 = SomethingHappened.new
|
61
|
+
event2 = SomethingHappened.new
|
62
|
+
|
63
|
+
events_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyAwesomeStream', stream_id: '1')
|
64
|
+
projection_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyAwesomeProjection', stream_id: SecureRandom.uuid)
|
65
|
+
|
66
|
+
event1, event2 = PgEventstore.client.append_to_stream(events_stream, [event1, event2])
|
67
|
+
|
68
|
+
# Links first event
|
69
|
+
PgEventstore.client.link_to(projection_stream, event1, options: { expected_revision: :no_stream })
|
70
|
+
# Raises PgEventstore::WrongExpectedVersionError error because stream already exists
|
71
|
+
PgEventstore.client.link_to(projection_stream, event2, options: { expected_revision: :no_stream })
|
72
|
+
```
|
73
|
+
|
74
|
+
## Middlewares
|
75
|
+
|
76
|
+
If you would like to modify your link events before they get persisted - you should use the `:middlewares` argument which allows you to pass the list of middlewares you would like to use. **By default no middlewares will be applied to the link event despite on `config.middlewares` option**.
|
77
|
+
|
78
|
+
Let's say you have these registered middlewares:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
PgEventstore.configure do |config|
|
82
|
+
config.middlewares = { foo: FooMiddleware.new, bar: BarMiddleware.new, baz: BazMiddleware.new }
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
And you want to use `FooMiddleware` and `BazMiddleware`. You simply have to provide an array of corresponding middleware keys you would like to use:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
events_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyAwesomeStream', stream_id: '1')
|
90
|
+
projection_stream = PgEventstore::Stream.new(context: 'FooCtx', stream_name: 'MyAwesomeProjection', stream_id: '1')
|
91
|
+
|
92
|
+
event = PgEventstore.client.append_to_stream(events_stream, PgEventstore::Event.new)
|
93
|
+
PgEventstore.client.link_to(projection_stream, event, middlewares: %i[foo baz])
|
94
|
+
```
|
95
|
+
|
96
|
+
See [Writing middleware](writing_middleware.md) chapter for info about what is middleware and how to implement it.
|
data/docs/reading_events.md
CHANGED
@@ -159,3 +159,59 @@ You can also mix filtering by stream's attributes and event types. The result wi
|
|
159
159
|
```ruby
|
160
160
|
PgEventstore.client.read(PgEventstore::Stream.all_stream, options: { filter: { streams: [{ context: 'MyAwesomeContext' }], event_types: %w[Foo Bar] } })
|
161
161
|
```
|
162
|
+
|
163
|
+
|
164
|
+
## Pagination
|
165
|
+
|
166
|
+
You can use `#read_paginated` to iterate over all (filtered) events. It yields each batch of records that was found according to the filter options:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
# Read from the specific stream
|
170
|
+
stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'User', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
|
171
|
+
PgEventstore.client.read_paginated(stream).each do |events|
|
172
|
+
events.each do |event|
|
173
|
+
# iterate through events
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Read from "all" stream
|
178
|
+
PgEventstore.client.read_paginated(PgEventstore::Stream.all_stream).each do |events|
|
179
|
+
events.each do |event|
|
180
|
+
# iterate through events
|
181
|
+
end
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
Options are the same as for `#read` method. Several examples:
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
# Read "Foo" events only from the specific stream
|
189
|
+
stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'User', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
|
190
|
+
PgEventstore.client.read_paginated(stream, options: { filter: { event_types: ['Foo'] } }).each do |events|
|
191
|
+
events.each do |event|
|
192
|
+
# iterate through events
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Backwards read from "all" stream
|
197
|
+
PgEventstore.client.read_paginated(PgEventstore::Stream.all_stream, options: { direction: 'Backwards' }).each do |events|
|
198
|
+
events.each do |event|
|
199
|
+
# iterate through events
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Set batch size to 100
|
204
|
+
PgEventstore.client.read_paginated(PgEventstore::Stream.all_stream, options: { max_count: 100 }).each do |events|
|
205
|
+
events.each do |event|
|
206
|
+
# iterate through events
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Reading from projection stream
|
211
|
+
projection_stream = PgEventstore::Stream.new(context: 'MyAwesomeContext', stream_name: 'MyAwesomeProjection', stream_id: 'f37b82f2-4152-424d-ab6b-0cc6f0a53aae')
|
212
|
+
PgEventstore.client.read_paginated(projection_stream, options: { resolve_link_tos: true }).each do |events|
|
213
|
+
events.each do |event|
|
214
|
+
# iterate through events
|
215
|
+
end
|
216
|
+
end
|
217
|
+
```
|
data/lib/pg_eventstore/client.rb
CHANGED
@@ -109,6 +109,36 @@ module PgEventstore
|
|
109
109
|
call(stream, options: { max_count: config.max_count }.merge(options))
|
110
110
|
end
|
111
111
|
|
112
|
+
# @see {#read} for available params
|
113
|
+
# @return [Enumerator] enumerator will yield PgEventstore::Event
|
114
|
+
def read_paginated(stream, options: {}, middlewares: nil)
|
115
|
+
cmd_class = stream.system? ? Commands::SystemStreamReadPaginated : Commands::RegularStreamReadPaginated
|
116
|
+
cmd_class.
|
117
|
+
new(Queries.new(streams: stream_queries, events: event_queries(middlewares(middlewares)))).
|
118
|
+
call(stream, options: { max_count: config.max_count }.merge(options))
|
119
|
+
end
|
120
|
+
|
121
|
+
# Links event from one stream into another stream. You can later access it by providing :resolve_link_tos option
|
122
|
+
# when reading from a stream. Only existing events can be linked.
|
123
|
+
# @param stream [PgEventstore::Stream]
|
124
|
+
# @param events_or_event [PgEventstore::Event, Array<PgEventstore::Event>]
|
125
|
+
# @param options [Hash]
|
126
|
+
# @option options [Integer] :expected_revision provide your own revision number
|
127
|
+
# @option options [Symbol] :expected_revision provide one of next values: :any, :no_stream or :stream_exists
|
128
|
+
# @param middlewares [Array] provide a list of middleware names to use. Defaults to empty array, meaning no
|
129
|
+
# middlewares will be applied to the "link" event
|
130
|
+
# @return [PgEventstore::Event, Array<PgEventstore::Event>] persisted event(s)
|
131
|
+
# @raise [PgEventstore::WrongExpectedRevisionError]
|
132
|
+
def link_to(stream, events_or_event, options: {}, middlewares: [])
|
133
|
+
result =
|
134
|
+
Commands::LinkTo.new(
|
135
|
+
Queries.new(
|
136
|
+
streams: stream_queries, events: event_queries(middlewares(middlewares)), transactions: transaction_queries
|
137
|
+
)
|
138
|
+
).call(stream, *events_or_event, options: options)
|
139
|
+
events_or_event.is_a?(Array) ? result : result.first
|
140
|
+
end
|
141
|
+
|
112
142
|
private
|
113
143
|
|
114
144
|
# @param middlewares [Array, nil]
|
@@ -9,9 +9,10 @@ module PgEventstore
|
|
9
9
|
# @param options [Hash]
|
10
10
|
# @option options [Integer] :expected_revision provide your own revision number
|
11
11
|
# @option options [Symbol] :expected_revision provide one of next values: :any, :no_stream or :stream_exists
|
12
|
+
# @param event_modifier [#call]
|
12
13
|
# @return [Array<PgEventstore::Event>] persisted events
|
13
14
|
# @raise [PgEventstore::WrongExpectedRevisionError]
|
14
|
-
def call(stream, *events, options: {})
|
15
|
+
def call(stream, *events, options: {}, event_modifier: EventModifiers::PrepareRegularEvent)
|
15
16
|
raise SystemStreamError, stream if stream.system?
|
16
17
|
|
17
18
|
queries.transactions.transaction do
|
@@ -19,7 +20,7 @@ module PgEventstore
|
|
19
20
|
revision = stream.stream_revision
|
20
21
|
assert_expected_revision!(revision, options[:expected_revision]) if options[:expected_revision]
|
21
22
|
events.map.with_index(1) do |event, index|
|
22
|
-
queries.events.insert(stream,
|
23
|
+
queries.events.insert(stream, event_modifier.call(event, revision + index))
|
23
24
|
end.tap do
|
24
25
|
queries.streams.update_stream_revision(stream, revision + events.size)
|
25
26
|
end
|
@@ -28,15 +29,6 @@ module PgEventstore
|
|
28
29
|
|
29
30
|
private
|
30
31
|
|
31
|
-
# @param event [PgEventstore::Event]
|
32
|
-
# @param revision [Integer]
|
33
|
-
# @return [PgEventstore::Event]
|
34
|
-
def prepared_event(event, revision)
|
35
|
-
event.class.new(
|
36
|
-
id: event.id, data: event.data, metadata: event.metadata, type: event.type, stream_revision: revision
|
37
|
-
)
|
38
|
-
end
|
39
|
-
|
40
32
|
# @param revision [Integer]
|
41
33
|
# @param expected_revision [Symbol, Integer]
|
42
34
|
# @raise [PgEventstore::WrongExpectedRevisionError] in case if revision does not satisfy expected revision
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
module EventModifiers
|
6
|
+
# Defines how to transform regular event into a link event
|
7
|
+
# @!visibility private
|
8
|
+
class PrepareLinkEvent
|
9
|
+
class << self
|
10
|
+
# @param event [PgEventstore::Event]
|
11
|
+
# @param revision [Integer]
|
12
|
+
# @return [PgEventstore::Event]
|
13
|
+
def call(event, revision)
|
14
|
+
Event.new(link_id: event.id, type: Event::LINK_TYPE, stream_revision: revision).tap do |e|
|
15
|
+
%i[link_id type stream_revision].each { |attr| e.readonly!(attr) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
module EventModifiers
|
6
|
+
# Defines how to transform regular event before appending it to the stream
|
7
|
+
# @!visibility private
|
8
|
+
class PrepareRegularEvent
|
9
|
+
class << self
|
10
|
+
# @param event [PgEventstore::Event]
|
11
|
+
# @param revision [Integer]
|
12
|
+
# @return [PgEventstore::Event]
|
13
|
+
def call(event, revision)
|
14
|
+
event.class.new(
|
15
|
+
id: event.id, data: event.data, metadata: event.metadata, type: event.type, stream_revision: revision
|
16
|
+
).tap do |e|
|
17
|
+
%i[link_id stream_revision].each { |attr| e.readonly!(attr) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
# @!visibility private
|
6
|
+
class LinkTo < AbstractCommand
|
7
|
+
# @param stream [PgEventstore::Stream]
|
8
|
+
# @param events [Array<PgEventstore::Event>]
|
9
|
+
# @param options [Hash]
|
10
|
+
# @option options [Integer] :expected_revision provide your own revision number
|
11
|
+
# @option options [Symbol] :expected_revision provide one of next values: :any, :no_stream or :stream_exists
|
12
|
+
# @return [Array<PgEventstore::Event>] persisted events
|
13
|
+
# @raise [PgEventstore::WrongExpectedRevisionError]
|
14
|
+
# @raise [PgEventstore::NotPersistedEventError]
|
15
|
+
def call(stream, *events, options: {})
|
16
|
+
events.each(&method(:check_id_presence))
|
17
|
+
append_cmd = Append.new(queries)
|
18
|
+
append_cmd.call(stream, *events, options: options, event_modifier: EventModifiers::PrepareLinkEvent)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Checks if Event#id is present. An event must have the #id value in order to be linked.
|
24
|
+
# @param event [PgEventstore::Event]
|
25
|
+
# @return [void]
|
26
|
+
def check_id_presence(event)
|
27
|
+
return unless event.id.nil?
|
28
|
+
|
29
|
+
raise NotPersistedEventError, event
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
# @!visibility private
|
6
|
+
class RegularStreamReadPaginated < AbstractCommand
|
7
|
+
# @see PgEventstore::Commands::Read for docs
|
8
|
+
def call(stream, options: {})
|
9
|
+
revision = calc_initial_revision(stream, options)
|
10
|
+
Enumerator.new do |yielder|
|
11
|
+
loop do
|
12
|
+
events = read_cmd.call(stream, options: options.merge(from_revision: revision))
|
13
|
+
yielder << events if events.any?
|
14
|
+
raise StopIteration if end_reached?(events, options[:max_count])
|
15
|
+
|
16
|
+
revision = calc_next_revision(events, revision, options[:direction])
|
17
|
+
raise StopIteration if revision.negative?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @param stream [PgEventstore::Stream]
|
25
|
+
# @param options [Hash]
|
26
|
+
# @return [Integer]
|
27
|
+
def calc_initial_revision(stream, options)
|
28
|
+
return options[:from_revision] if options[:from_revision]
|
29
|
+
return 0 if forwards?(options[:direction])
|
30
|
+
|
31
|
+
read_cmd.call(stream, options: options.merge(max_count: 1)).first.stream_revision
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param events [Array<PgEventstore::Event>]
|
35
|
+
# @param max_count [Integer]
|
36
|
+
# @return [Boolean]
|
37
|
+
def end_reached?(events, max_count)
|
38
|
+
events.size < max_count
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param events [Array<PgEventstore::Event>]
|
42
|
+
# @param revision [Integer]
|
43
|
+
# @param direction [String, Symbol, nil]
|
44
|
+
# @return [Integer]
|
45
|
+
def calc_next_revision(events, revision, direction)
|
46
|
+
return revision + events.size if forwards?(direction)
|
47
|
+
|
48
|
+
revision - events.size
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param direction [String, Symbol, nil]
|
52
|
+
# @return [Boolean]
|
53
|
+
def forwards?(direction)
|
54
|
+
QueryBuilders::EventsFiltering::SQL_DIRECTIONS[direction] == QueryBuilders::EventsFiltering::SQL_DIRECTIONS[:asc]
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [PgEventstore::Commands::Read]
|
58
|
+
def read_cmd
|
59
|
+
@read_cmd ||= Read.new(queries)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
module Commands
|
5
|
+
# @!visibility private
|
6
|
+
class SystemStreamReadPaginated < AbstractCommand
|
7
|
+
# @see PgEventstore::Commands::Read for docs
|
8
|
+
def call(stream, options: {})
|
9
|
+
position = calc_initial_position(stream, options)
|
10
|
+
Enumerator.new do |yielder|
|
11
|
+
loop do
|
12
|
+
events = read_cmd.call(stream, options: options.merge(from_position: position))
|
13
|
+
yielder << events if events.any?
|
14
|
+
raise StopIteration if end_reached?(events, options[:max_count])
|
15
|
+
|
16
|
+
position = calc_next_position(events, options[:direction])
|
17
|
+
raise StopIteration if position <= 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @param stream [PgEventstore::Stream]
|
25
|
+
# @param options [Hash]
|
26
|
+
# @return [Integer]
|
27
|
+
def calc_initial_position(stream, options)
|
28
|
+
return options[:from_position] if options[:from_position]
|
29
|
+
return 1 if forwards?(options[:direction])
|
30
|
+
|
31
|
+
read_cmd.call(stream, options: options.merge(max_count: 1)).first.global_position
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param events [Array<PgEventstore::Event>]
|
35
|
+
# @param max_count [Integer]
|
36
|
+
# @return [Boolean]
|
37
|
+
def end_reached?(events, max_count)
|
38
|
+
events.size < max_count
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param events [Array<PgEventstore::Event>]
|
42
|
+
# @param direction [String, Symbol, nil]
|
43
|
+
# @return [Integer]
|
44
|
+
def calc_next_position(events, direction)
|
45
|
+
return events.last.global_position + 1 if forwards?(direction)
|
46
|
+
|
47
|
+
events.last.global_position - 1
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param direction [String, Symbol, nil]
|
51
|
+
# @return [Boolean]
|
52
|
+
def forwards?(direction)
|
53
|
+
QueryBuilders::EventsFiltering::SQL_DIRECTIONS[direction] == QueryBuilders::EventsFiltering::SQL_DIRECTIONS[:asc]
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [PgEventstore::Commands::Read]
|
57
|
+
def read_cmd
|
58
|
+
@read_cmd ||= Read.new(queries)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,6 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'abstract_command'
|
4
|
+
require_relative 'commands/event_modifiers/prepare_link_event'
|
5
|
+
require_relative 'commands/event_modifiers/prepare_regular_event'
|
4
6
|
require_relative 'commands/append'
|
5
7
|
require_relative 'commands/read'
|
8
|
+
require_relative 'commands/regular_stream_read_paginated'
|
9
|
+
require_relative 'commands/system_stream_read_paginated'
|
6
10
|
require_relative 'commands/multiple'
|
11
|
+
require_relative 'commands/link_to'
|
data/lib/pg_eventstore/errors.rb
CHANGED
@@ -137,8 +137,8 @@ module PgEventstore
|
|
137
137
|
|
138
138
|
# @return [String]
|
139
139
|
def user_friendly_message
|
140
|
-
<<~TEXT
|
141
|
-
Could not lock
|
140
|
+
<<~TEXT.strip
|
141
|
+
Could not lock subscription from #{set.inspect} set with #{name.inspect} name. It is already locked by \
|
142
142
|
#{lock_id.inspect} set.
|
143
143
|
TEXT
|
144
144
|
end
|
@@ -161,10 +161,27 @@ module PgEventstore
|
|
161
161
|
|
162
162
|
# @return [String]
|
163
163
|
def user_friendly_message
|
164
|
-
<<~TEXT
|
165
|
-
Failed to unlock
|
164
|
+
<<~TEXT.strip
|
165
|
+
Failed to unlock subscription from #{set.inspect} set with #{name.inspect} name by \
|
166
166
|
#{expected_locked_by.inspect} lock id. It is currently locked by #{actual_locked_by.inspect} lock id.
|
167
167
|
TEXT
|
168
168
|
end
|
169
169
|
end
|
170
|
+
|
171
|
+
class NotPersistedEventError < Error
|
172
|
+
attr_reader :event
|
173
|
+
|
174
|
+
# @param event [PgEventstore::Event]
|
175
|
+
def initialize(event)
|
176
|
+
@event = event
|
177
|
+
super(user_friendly_message)
|
178
|
+
end
|
179
|
+
|
180
|
+
# @return [String]
|
181
|
+
def user_friendly_message
|
182
|
+
<<~TEXT.strip
|
183
|
+
Event#id must be present, got #{event.id.inspect} instead.
|
184
|
+
TEXT
|
185
|
+
end
|
186
|
+
end
|
170
187
|
end
|
@@ -12,18 +12,9 @@ module PgEventstore
|
|
12
12
|
@event_class_resolver = event_class_resolver
|
13
13
|
end
|
14
14
|
|
15
|
-
# @param
|
16
|
-
|
17
|
-
|
18
|
-
pg_result.map(&method(:deserialize))
|
19
|
-
end
|
20
|
-
|
21
|
-
# @param pg_result [PG::Result]
|
22
|
-
# @return [PgEventstore::Event, nil]
|
23
|
-
def deserialize_one_pg_result(pg_result)
|
24
|
-
return if pg_result.ntuples.zero?
|
25
|
-
|
26
|
-
deserialize(pg_result.first)
|
15
|
+
# @param raw_events [Array<Hash>]
|
16
|
+
def deserialize_many(raw_events)
|
17
|
+
raw_events.map(&method(:deserialize))
|
27
18
|
end
|
28
19
|
|
29
20
|
# @param attrs [Hash]
|
@@ -50,7 +50,11 @@ module PgEventstore
|
|
50
50
|
self.options = (options + Set.new([opt_name])).freeze
|
51
51
|
warn_already_defined(opt_name)
|
52
52
|
warn_already_defined(:"#{opt_name}=")
|
53
|
-
|
53
|
+
define_method "#{opt_name}=" do |value|
|
54
|
+
readonly_error(opt_name) if readonly?(opt_name)
|
55
|
+
|
56
|
+
instance_variable_set(:"@#{opt_name}", value)
|
57
|
+
end
|
54
58
|
|
55
59
|
define_method opt_name do
|
56
60
|
result = instance_variable_get(:"@#{opt_name}")
|
@@ -77,6 +81,8 @@ module PgEventstore
|
|
77
81
|
end
|
78
82
|
end
|
79
83
|
|
84
|
+
ReadonlyAttributeError = Class.new(StandardError)
|
85
|
+
|
80
86
|
def self.included(klass)
|
81
87
|
klass.singleton_class.attr_accessor(:options)
|
82
88
|
klass.options = Set.new.freeze
|
@@ -84,11 +90,8 @@ module PgEventstore
|
|
84
90
|
end
|
85
91
|
|
86
92
|
def initialize(**options)
|
87
|
-
|
88
|
-
|
89
|
-
value = options.key?(option) ? options[option] : public_send(option)
|
90
|
-
public_send("#{option}=", value)
|
91
|
-
end
|
93
|
+
@readonly = Set.new
|
94
|
+
init_default_values(options)
|
92
95
|
end
|
93
96
|
|
94
97
|
# Construct a hash from options, where key is the option's name and the value is option's
|
@@ -100,6 +103,41 @@ module PgEventstore
|
|
100
103
|
end
|
101
104
|
end
|
102
105
|
alias attributes_hash options_hash
|
106
|
+
|
107
|
+
# @param opt_name [Symbol]
|
108
|
+
# @return [Boolean]
|
109
|
+
def readonly!(opt_name)
|
110
|
+
return false unless self.class.options.include?(opt_name)
|
111
|
+
|
112
|
+
@readonly.add(opt_name)
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
# @param opt_name [Symbol]
|
117
|
+
# @return [Boolean]
|
118
|
+
def readonly?(opt_name)
|
119
|
+
@readonly.include?(opt_name)
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# @param opt_name [Symbol]
|
125
|
+
# @raise [PgEventstore::Extensions::OptionsExtension::ReadOnlyError]
|
126
|
+
def readonly_error(opt_name)
|
127
|
+
raise(
|
128
|
+
ReadonlyAttributeError, "#{opt_name.inspect} attribute was marked as read only. You can no longer modify it."
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
# @param options [Hash]
|
133
|
+
# @return [void]
|
134
|
+
def init_default_values(options)
|
135
|
+
self.class.options.each do |option|
|
136
|
+
# init default values of options
|
137
|
+
value = options.key?(option) ? options[option] : public_send(option)
|
138
|
+
public_send("#{option}=", value)
|
139
|
+
end
|
140
|
+
end
|
103
141
|
end
|
104
142
|
end
|
105
143
|
end
|
@@ -19,11 +19,28 @@ module PgEventstore
|
|
19
19
|
|
20
20
|
sql = sql.gsub(/\$\d+/).each do |matched|
|
21
21
|
value = params[matched[1..].to_i - 1]
|
22
|
-
|
23
|
-
|
24
|
-
value.is_a?(String) ? "'#{value}'" : value
|
22
|
+
value = encode_value(value)
|
23
|
+
normalize_value(value)
|
25
24
|
end unless params&.empty?
|
26
25
|
PgEventstore.logger.debug(sql)
|
27
26
|
end
|
27
|
+
|
28
|
+
def encode_value(value)
|
29
|
+
encoder = type_map_for_queries[value.class]
|
30
|
+
return type_map_for_queries.send(encoder, value).encode(value) if encoder.is_a?(Symbol)
|
31
|
+
|
32
|
+
type_map_for_queries[value.class]&.encode(value) || value
|
33
|
+
end
|
34
|
+
|
35
|
+
def normalize_value(value)
|
36
|
+
case value
|
37
|
+
when String
|
38
|
+
"'#{value}'"
|
39
|
+
when NilClass
|
40
|
+
'NULL'
|
41
|
+
else
|
42
|
+
value
|
43
|
+
end
|
44
|
+
end
|
28
45
|
end
|
29
46
|
end
|
@@ -22,10 +22,11 @@ module PgEventstore
|
|
22
22
|
def stream_events(stream, options)
|
23
23
|
options = event_type_queries.include_event_types_ids(options)
|
24
24
|
exec_params = events_filtering(stream, options).to_exec_params
|
25
|
-
|
25
|
+
raw_events = connection.with do |conn|
|
26
26
|
conn.exec_params(*exec_params)
|
27
|
-
end
|
28
|
-
|
27
|
+
end.to_a
|
28
|
+
preloader.preload_related_objects(raw_events)
|
29
|
+
deserializer.deserialize_many(raw_events)
|
29
30
|
end
|
30
31
|
|
31
32
|
# @param stream [PgEventstore::Stream] persisted stream
|
@@ -44,10 +45,10 @@ module PgEventstore
|
|
44
45
|
RETURNING *, $#{attributes.values.size + 1} as type
|
45
46
|
SQL
|
46
47
|
|
47
|
-
|
48
|
+
raw_event = connection.with do |conn|
|
48
49
|
conn.exec_params(sql, [*attributes.values, event.type])
|
49
|
-
end
|
50
|
-
deserializer.without_middlewares.
|
50
|
+
end.to_a.first
|
51
|
+
deserializer.without_middlewares.deserialize(raw_event).tap do |persisted_event|
|
51
52
|
persisted_event.stream = stream
|
52
53
|
end
|
53
54
|
end
|
@@ -56,17 +57,21 @@ module PgEventstore
|
|
56
57
|
|
57
58
|
# @param stream [PgEventstore::Stream]
|
58
59
|
# @param options [Hash]
|
59
|
-
# @param offset [Integer]
|
60
60
|
# @return [PgEventstore::EventsFilteringQuery]
|
61
|
-
def events_filtering(stream, options
|
62
|
-
return QueryBuilders::EventsFiltering.all_stream_filtering(options
|
61
|
+
def events_filtering(stream, options)
|
62
|
+
return QueryBuilders::EventsFiltering.all_stream_filtering(options) if stream.all_stream?
|
63
63
|
|
64
|
-
QueryBuilders::EventsFiltering.specific_stream_filtering(stream, options
|
64
|
+
QueryBuilders::EventsFiltering.specific_stream_filtering(stream, options)
|
65
65
|
end
|
66
66
|
|
67
67
|
# @return [PgEventstore::EventTypeQueries]
|
68
68
|
def event_type_queries
|
69
69
|
EventTypeQueries.new(connection)
|
70
70
|
end
|
71
|
+
|
72
|
+
# @return [PgEventstore::Preloader]
|
73
|
+
def preloader
|
74
|
+
Preloader.new(connection)
|
75
|
+
end
|
71
76
|
end
|
72
77
|
end
|
@@ -33,6 +33,17 @@ module PgEventstore
|
|
33
33
|
end.to_a.dig(0, 'id')
|
34
34
|
end
|
35
35
|
|
36
|
+
# @param ids [Array<Integer>]
|
37
|
+
# @return [Array<Hash>]
|
38
|
+
def find_by_ids(ids)
|
39
|
+
return [] if ids.empty?
|
40
|
+
|
41
|
+
builder = SQLBuilder.new.from('event_types').where('id = ANY(?)', ids.uniq)
|
42
|
+
connection.with do |conn|
|
43
|
+
conn.exec_params(*builder.to_exec_params)
|
44
|
+
end.to_a
|
45
|
+
end
|
46
|
+
|
36
47
|
# @param types [Array<String>]
|
37
48
|
# @return [Array<Integer, nil>]
|
38
49
|
def find_event_types(types)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgEventstore
|
4
|
+
# @!visibility private
|
5
|
+
class Preloader
|
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 raw_events [Array<Hash>]
|
15
|
+
# @return [Array<Hash>]
|
16
|
+
def preload_related_objects(raw_events)
|
17
|
+
streams = stream_queries.find_by_ids(raw_events.map { _1['stream_id'] }).to_h { [_1['id'], _1] }
|
18
|
+
types = event_type_queries.find_by_ids(raw_events.map { _1['event_type_id'] }).to_h { [_1['id'], _1] }
|
19
|
+
raw_events.each do |event|
|
20
|
+
event['stream'] = streams[event['stream_id']]
|
21
|
+
event['type'] = types[event['event_type_id']]['type']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# @return [PgEventstore::EventTypeQueries]
|
28
|
+
def event_type_queries
|
29
|
+
EventTypeQueries.new(connection)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [PgEventstore::StreamQueries]
|
33
|
+
def stream_queries
|
34
|
+
StreamQueries.new(connection)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -26,6 +26,17 @@ module PgEventstore
|
|
26
26
|
deserialize(pg_result) if pg_result.ntuples == 1
|
27
27
|
end
|
28
28
|
|
29
|
+
# @param ids [Array<Integer>]
|
30
|
+
# @return [Array<Hash>]
|
31
|
+
def find_by_ids(ids)
|
32
|
+
return [] if ids.empty?
|
33
|
+
|
34
|
+
builder = SQLBuilder.new.from('streams').where('id = ANY(?)', ids.uniq.sort)
|
35
|
+
connection.with do |conn|
|
36
|
+
conn.exec_params(*builder.to_exec_params)
|
37
|
+
end.to_a
|
38
|
+
end
|
39
|
+
|
29
40
|
# @param stream [PgEventstore::Stream]
|
30
41
|
# @return [PgEventstore::RawStream] persisted stream
|
31
42
|
def create_stream(stream)
|
@@ -44,13 +55,15 @@ module PgEventstore
|
|
44
55
|
end
|
45
56
|
|
46
57
|
# @param stream [PgEventstore::Stream] persisted stream
|
47
|
-
# @return [
|
58
|
+
# @return [PgEventstore::Stream]
|
48
59
|
def update_stream_revision(stream, revision)
|
49
60
|
connection.with do |conn|
|
50
61
|
conn.exec_params(<<~SQL, [revision, stream.id])
|
51
62
|
UPDATE streams SET stream_revision = $1 WHERE id = $2
|
52
63
|
SQL
|
53
64
|
end
|
65
|
+
stream.stream_revision = revision
|
66
|
+
stream
|
54
67
|
end
|
55
68
|
|
56
69
|
private
|
@@ -79,9 +79,10 @@ module PgEventstore
|
|
79
79
|
return [] if query_options.empty?
|
80
80
|
|
81
81
|
final_builder = union_builders(query_options.map { |id, opts| query_builder(id, opts) })
|
82
|
-
connection.with do |conn|
|
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
86
|
end
|
86
87
|
|
87
88
|
# @param id [Integer] subscription's id
|
@@ -151,6 +152,11 @@ module PgEventstore
|
|
151
152
|
EventTypeQueries.new(connection)
|
152
153
|
end
|
153
154
|
|
155
|
+
# @return [PgEventstore::Preloader]
|
156
|
+
def preloader
|
157
|
+
Preloader.new(connection)
|
158
|
+
end
|
159
|
+
|
154
160
|
# @param hash [Hash]
|
155
161
|
# @return [Hash]
|
156
162
|
def deserialize(hash)
|
@@ -10,6 +10,7 @@ require_relative 'queries/subscription_queries'
|
|
10
10
|
require_relative 'queries/subscriptions_set_queries'
|
11
11
|
require_relative 'queries/subscription_command_queries'
|
12
12
|
require_relative 'queries/subscriptions_set_command_queries'
|
13
|
+
require_relative 'queries/preloader'
|
13
14
|
|
14
15
|
module PgEventstore
|
15
16
|
# @!visibility private
|
@@ -4,7 +4,6 @@ module PgEventstore
|
|
4
4
|
module QueryBuilders
|
5
5
|
# @!visibility private
|
6
6
|
class EventsFiltering
|
7
|
-
DEFAULT_OFFSET = 0
|
8
7
|
DEFAULT_LIMIT = 1_000
|
9
8
|
SQL_DIRECTIONS = {
|
10
9
|
'asc' => 'ASC',
|
@@ -26,14 +25,12 @@ module PgEventstore
|
|
26
25
|
end
|
27
26
|
|
28
27
|
# @param options [Hash]
|
29
|
-
# @param offset [Integer]
|
30
28
|
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
31
|
-
def all_stream_filtering(options
|
29
|
+
def all_stream_filtering(options)
|
32
30
|
event_filter = new
|
33
31
|
options in { filter: { event_type_ids: Array => event_type_ids } }
|
34
32
|
event_filter.add_event_types(event_type_ids)
|
35
33
|
event_filter.add_limit(options[:max_count])
|
36
|
-
event_filter.add_offset(offset)
|
37
34
|
event_filter.resolve_links(options[:resolve_link_tos])
|
38
35
|
options in { filter: { streams: Array => streams } }
|
39
36
|
streams&.each { |attrs| event_filter.add_stream_attrs(**attrs) }
|
@@ -44,14 +41,12 @@ module PgEventstore
|
|
44
41
|
|
45
42
|
# @param stream [PgEventstore::Stream]
|
46
43
|
# @param options [Hash]
|
47
|
-
# @param offset [Integer]
|
48
44
|
# @return [PgEventstore::QueryBuilders::EventsFiltering]
|
49
|
-
def specific_stream_filtering(stream, options
|
45
|
+
def specific_stream_filtering(stream, options)
|
50
46
|
event_filter = new
|
51
47
|
options in { filter: { event_type_ids: Array => event_type_ids } }
|
52
48
|
event_filter.add_event_types(event_type_ids)
|
53
49
|
event_filter.add_limit(options[:max_count])
|
54
|
-
event_filter.add_offset(offset)
|
55
50
|
event_filter.resolve_links(options[:resolve_link_tos])
|
56
51
|
event_filter.add_stream(stream)
|
57
52
|
event_filter.add_revision(options[:from_revision], options[:direction])
|
@@ -64,13 +59,10 @@ module PgEventstore
|
|
64
59
|
@sql_builder =
|
65
60
|
SQLBuilder.new.
|
66
61
|
select('events.*').
|
67
|
-
select('row_to_json(streams.*) as stream').
|
68
|
-
select('event_types.type as type').
|
69
62
|
from('events').
|
70
63
|
join('JOIN streams ON streams.id = events.stream_id').
|
71
64
|
join('JOIN event_types ON event_types.id = events.event_type_id').
|
72
|
-
limit(DEFAULT_LIMIT)
|
73
|
-
offset(DEFAULT_OFFSET)
|
65
|
+
limit(DEFAULT_LIMIT)
|
74
66
|
end
|
75
67
|
|
76
68
|
# @param context [String, nil]
|
@@ -144,14 +136,6 @@ module PgEventstore
|
|
144
136
|
@sql_builder.limit(limit)
|
145
137
|
end
|
146
138
|
|
147
|
-
# @param offset [Integer, nil]
|
148
|
-
# @return [void]
|
149
|
-
def add_offset(offset)
|
150
|
-
return unless offset
|
151
|
-
|
152
|
-
@sql_builder.offset(offset)
|
153
|
-
end
|
154
|
-
|
155
139
|
# @param should_resolve [Boolean]
|
156
140
|
# @return [void]
|
157
141
|
def resolve_links(should_resolve)
|
@@ -160,7 +144,6 @@ module PgEventstore
|
|
160
144
|
@sql_builder.
|
161
145
|
unselect.
|
162
146
|
select("(COALESCE(original_events.*, events.*)).*").
|
163
|
-
select('row_to_json(streams.*) as stream').
|
164
147
|
join("LEFT JOIN events original_events ON original_events.id = events.link_id")
|
165
148
|
end
|
166
149
|
|
data/lib/pg_eventstore/stream.rb
CHANGED
@@ -18,7 +18,8 @@ module PgEventstore
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
attr_reader :context, :stream_name, :stream_id, :id
|
21
|
+
attr_reader :context, :stream_name, :stream_id, :id
|
22
|
+
attr_accessor :stream_revision
|
22
23
|
|
23
24
|
# @param context [String]
|
24
25
|
# @param stream_name [String]
|
@@ -63,6 +64,19 @@ module PgEventstore
|
|
63
64
|
deconstruct_keys(nil)
|
64
65
|
end
|
65
66
|
|
67
|
+
# @return [Integer]
|
68
|
+
def hash
|
69
|
+
to_hash.hash
|
70
|
+
end
|
71
|
+
|
72
|
+
# @param another [Object]
|
73
|
+
# @return [Boolean]
|
74
|
+
def eql?(another)
|
75
|
+
return false unless another.is_a?(Stream)
|
76
|
+
|
77
|
+
hash == another.hash
|
78
|
+
end
|
79
|
+
|
66
80
|
def ==(other_stream)
|
67
81
|
return false unless other_stream.is_a?(Stream)
|
68
82
|
|
@@ -59,6 +59,10 @@ namespace :pg_eventstore do
|
|
59
59
|
DROP TABLE IF EXISTS public.streams;
|
60
60
|
DROP TABLE IF EXISTS public.event_types;
|
61
61
|
DROP TABLE IF EXISTS public.migrations;
|
62
|
+
DROP TABLE IF EXISTS public.subscriptions_set;
|
63
|
+
DROP TABLE IF EXISTS public.subscriptions;
|
64
|
+
DROP TABLE IF EXISTS public.subscription_commands;
|
65
|
+
DROP TABLE IF EXISTS public.subscriptions_set_commands;
|
62
66
|
DROP EXTENSION IF EXISTS "uuid-ossp";
|
63
67
|
DROP EXTENSION IF EXISTS pgcrypto;
|
64
68
|
SQL
|
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.
|
4
|
+
version: 0.5.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-
|
11
|
+
date: 2024-02-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -57,6 +57,7 @@ files:
|
|
57
57
|
- db/migrations/10_create_subscription_commands.sql
|
58
58
|
- db/migrations/11_create_subscriptions_set_commands.sql
|
59
59
|
- db/migrations/12_improve_events_indexes.sql
|
60
|
+
- db/migrations/13_remove_duplicated_index.sql
|
60
61
|
- db/migrations/1_improve_specific_stream_indexes.sql
|
61
62
|
- db/migrations/2_adjust_global_position_index.sql
|
62
63
|
- db/migrations/3_extract_type_into_separate_table.sql
|
@@ -69,6 +70,7 @@ files:
|
|
69
70
|
- docs/appending_events.md
|
70
71
|
- docs/configuration.md
|
71
72
|
- docs/events_and_streams.md
|
73
|
+
- docs/linking_events.md
|
72
74
|
- docs/multiple_commands.md
|
73
75
|
- docs/reading_events.md
|
74
76
|
- docs/subscriptions.md
|
@@ -79,8 +81,13 @@ files:
|
|
79
81
|
- lib/pg_eventstore/client.rb
|
80
82
|
- lib/pg_eventstore/commands.rb
|
81
83
|
- lib/pg_eventstore/commands/append.rb
|
84
|
+
- lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb
|
85
|
+
- lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb
|
86
|
+
- lib/pg_eventstore/commands/link_to.rb
|
82
87
|
- lib/pg_eventstore/commands/multiple.rb
|
83
88
|
- lib/pg_eventstore/commands/read.rb
|
89
|
+
- lib/pg_eventstore/commands/regular_stream_read_paginated.rb
|
90
|
+
- lib/pg_eventstore/commands/system_stream_read_paginated.rb
|
84
91
|
- lib/pg_eventstore/config.rb
|
85
92
|
- lib/pg_eventstore/connection.rb
|
86
93
|
- lib/pg_eventstore/errors.rb
|
@@ -96,6 +103,7 @@ files:
|
|
96
103
|
- lib/pg_eventstore/queries.rb
|
97
104
|
- lib/pg_eventstore/queries/event_queries.rb
|
98
105
|
- lib/pg_eventstore/queries/event_type_queries.rb
|
106
|
+
- lib/pg_eventstore/queries/preloader.rb
|
99
107
|
- lib/pg_eventstore/queries/stream_queries.rb
|
100
108
|
- lib/pg_eventstore/queries/subscription_command_queries.rb
|
101
109
|
- lib/pg_eventstore/queries/subscription_queries.rb
|