ruby_event_store 1.3.0 → 2.0.0
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/.mutant.yml +1 -0
- data/Gemfile +2 -4
- data/Gemfile.lock +118 -0
- data/Makefile +13 -3
- data/lib/ruby_event_store.rb +4 -7
- data/lib/ruby_event_store/broker.rb +3 -3
- data/lib/ruby_event_store/client.rb +47 -23
- data/lib/ruby_event_store/composed_dispatcher.rb +2 -2
- data/lib/ruby_event_store/constants.rb +1 -0
- data/lib/ruby_event_store/errors.rb +0 -1
- data/lib/ruby_event_store/event.rb +8 -1
- data/lib/ruby_event_store/immediate_async_dispatcher.rb +2 -2
- data/lib/ruby_event_store/in_memory_repository.rb +98 -59
- data/lib/ruby_event_store/instrumented_dispatcher.rb +2 -2
- data/lib/ruby_event_store/mappers/default.rb +28 -6
- data/lib/ruby_event_store/mappers/deprecated_wrapper.rb +33 -0
- data/lib/ruby_event_store/mappers/encryption_mapper.rb +1 -4
- data/lib/ruby_event_store/mappers/instrumented_mapper.rb +8 -4
- data/lib/ruby_event_store/mappers/json_mapper.rb +2 -4
- data/lib/ruby_event_store/mappers/pipeline.rb +26 -5
- data/lib/ruby_event_store/mappers/pipeline_mapper.rb +6 -2
- data/lib/ruby_event_store/mappers/transformation/domain_event.rb +16 -8
- data/lib/ruby_event_store/mappers/transformation/encryption.rb +20 -12
- data/lib/ruby_event_store/mappers/transformation/event_class_remapper.rb +11 -4
- data/lib/ruby_event_store/mappers/transformation/serialization.rb +16 -14
- data/lib/ruby_event_store/mappers/transformation/stringify_metadata_keys.rb +12 -7
- data/lib/ruby_event_store/mappers/transformation/symbolize_metadata_keys.rb +12 -7
- data/lib/ruby_event_store/null.rb +13 -0
- data/lib/ruby_event_store/projection.rb +2 -13
- data/lib/ruby_event_store/record.rb +68 -0
- data/lib/ruby_event_store/serialized_record.rb +23 -4
- data/lib/ruby_event_store/spec/broker_lint.rb +9 -9
- data/lib/ruby_event_store/spec/event_repository_lint.rb +200 -36
- data/lib/ruby_event_store/spec/mapper_lint.rb +6 -6
- data/lib/ruby_event_store/spec/subscriptions_lint.rb +6 -0
- data/lib/ruby_event_store/specification.rb +100 -7
- data/lib/ruby_event_store/specification_reader.rb +2 -2
- data/lib/ruby_event_store/specification_result.rb +86 -2
- data/lib/ruby_event_store/version.rb +1 -1
- data/ruby_event_store.gemspec +0 -2
- metadata +8 -9
- data/lib/ruby_event_store/mappers/protobuf.rb +0 -24
- data/lib/ruby_event_store/mappers/transformation/item.rb +0 -56
- data/lib/ruby_event_store/mappers/transformation/proto_event.rb +0 -17
- data/lib/ruby_event_store/mappers/transformation/protobuf_encoder.rb +0 -30
- data/lib/ruby_event_store/mappers/transformation/protobuf_nested_struct_metadata.rb +0 -30
- data/lib/ruby_event_store/mappers/transformation/serialized_record.rb +0 -27
@@ -23,27 +23,35 @@ module RubyEventStore
|
|
23
23
|
@forgotten_data = forgotten_data
|
24
24
|
end
|
25
25
|
|
26
|
-
def dump(
|
27
|
-
data =
|
28
|
-
metadata =
|
29
|
-
event_class = Object.const_get(
|
26
|
+
def dump(record)
|
27
|
+
data = record.data
|
28
|
+
metadata = record.metadata.dup
|
29
|
+
event_class = Object.const_get(record.event_type)
|
30
30
|
|
31
31
|
crypto_description = encryption_metadata(data, encryption_schema(event_class))
|
32
32
|
metadata[:encryption] = crypto_description unless crypto_description.empty?
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
Record.new(
|
35
|
+
event_id: record.event_id,
|
36
|
+
event_type: record.event_type,
|
37
|
+
data: encrypt_data(deep_dup(data), crypto_description),
|
38
|
+
metadata: metadata,
|
39
|
+
timestamp: record.timestamp,
|
40
|
+
valid_at: record.valid_at,
|
37
41
|
)
|
38
42
|
end
|
39
43
|
|
40
|
-
def load(
|
41
|
-
metadata =
|
44
|
+
def load(record)
|
45
|
+
metadata = record.metadata.dup
|
42
46
|
crypto_description = Hash(metadata.delete(:encryption))
|
43
47
|
|
44
|
-
|
45
|
-
|
46
|
-
|
48
|
+
Record.new(
|
49
|
+
event_id: record.event_id,
|
50
|
+
event_type: record.event_type,
|
51
|
+
data: decrypt_data(record.data, crypto_description),
|
52
|
+
metadata: metadata,
|
53
|
+
timestamp: record.timestamp,
|
54
|
+
valid_at: record.valid_at,
|
47
55
|
)
|
48
56
|
end
|
49
57
|
|
@@ -8,12 +8,19 @@ module RubyEventStore
|
|
8
8
|
@class_map = class_map
|
9
9
|
end
|
10
10
|
|
11
|
-
def dump(
|
12
|
-
|
11
|
+
def dump(record)
|
12
|
+
record
|
13
13
|
end
|
14
14
|
|
15
|
-
def load(
|
16
|
-
|
15
|
+
def load(record)
|
16
|
+
Record.new(
|
17
|
+
event_id: record.event_id,
|
18
|
+
event_type: class_map[record.event_type] || record.event_type,
|
19
|
+
data: record.data,
|
20
|
+
metadata: record.metadata,
|
21
|
+
timestamp: record.timestamp,
|
22
|
+
valid_at: record.valid_at,
|
23
|
+
)
|
17
24
|
end
|
18
25
|
|
19
26
|
private
|
@@ -7,26 +7,28 @@ module RubyEventStore
|
|
7
7
|
module Transformation
|
8
8
|
class Serialization
|
9
9
|
def initialize(serializer: YAML)
|
10
|
-
|
10
|
+
warn <<~EOW
|
11
|
+
#{self.class} has been deprecated and is effectively no-op. You should remove this transformation from your pipeline.
|
12
|
+
|
13
|
+
Instead, pass the serializer directly to the repository and the scheduler. For example:
|
14
|
+
|
15
|
+
Rails.configuration.event_store = RailsEventStore::Client.new(
|
16
|
+
mapper: RubyEventStore::Mappers::Default.new,
|
17
|
+
repository: RailsEventStoreActiveRecord::EventRepository.new(serializer: #{serializer}),
|
18
|
+
dispatcher: RubyEventStore::ComposedDispatcher.new(
|
19
|
+
RubyEventStore::ImmediateAsyncDispatcher.new(scheduler: ActiveJobScheduler.new(serializer: #{serializer}),
|
20
|
+
RubyEventStore::Dispatcher.new
|
21
|
+
)
|
22
|
+
)
|
23
|
+
EOW
|
11
24
|
end
|
12
|
-
attr_reader :serializer
|
13
25
|
|
14
26
|
def dump(item)
|
15
|
-
|
16
|
-
event_id: item.event_id,
|
17
|
-
metadata: serializer.dump(item.metadata),
|
18
|
-
data: serializer.dump(item.data),
|
19
|
-
event_type: item.event_type
|
20
|
-
)
|
27
|
+
item
|
21
28
|
end
|
22
29
|
|
23
30
|
def load(item)
|
24
|
-
|
25
|
-
event_id: item.event_id,
|
26
|
-
metadata: serializer.load(item.metadata),
|
27
|
-
data: serializer.load(item.data),
|
28
|
-
event_type: item.event_type
|
29
|
-
)
|
31
|
+
item
|
30
32
|
end
|
31
33
|
end
|
32
34
|
end
|
@@ -4,18 +4,23 @@ module RubyEventStore
|
|
4
4
|
module Mappers
|
5
5
|
module Transformation
|
6
6
|
class StringifyMetadataKeys
|
7
|
-
def dump(
|
8
|
-
stringify(
|
7
|
+
def dump(record)
|
8
|
+
stringify(record)
|
9
9
|
end
|
10
10
|
|
11
|
-
def load(
|
12
|
-
stringify(
|
11
|
+
def load(record)
|
12
|
+
stringify(record)
|
13
13
|
end
|
14
14
|
|
15
15
|
private
|
16
|
-
def stringify(
|
17
|
-
|
18
|
-
|
16
|
+
def stringify(record)
|
17
|
+
Record.new(
|
18
|
+
event_id: record.event_id,
|
19
|
+
event_type: record.event_type,
|
20
|
+
data: record.data,
|
21
|
+
metadata: TransformKeys.stringify(record.metadata),
|
22
|
+
timestamp: record.timestamp,
|
23
|
+
valid_at: record.valid_at,
|
19
24
|
)
|
20
25
|
end
|
21
26
|
end
|
@@ -4,18 +4,23 @@ module RubyEventStore
|
|
4
4
|
module Mappers
|
5
5
|
module Transformation
|
6
6
|
class SymbolizeMetadataKeys
|
7
|
-
def dump(
|
8
|
-
symbolize(
|
7
|
+
def dump(record)
|
8
|
+
symbolize(record)
|
9
9
|
end
|
10
10
|
|
11
|
-
def load(
|
12
|
-
symbolize(
|
11
|
+
def load(record)
|
12
|
+
symbolize(record)
|
13
13
|
end
|
14
14
|
|
15
15
|
private
|
16
|
-
def symbolize(
|
17
|
-
|
18
|
-
|
16
|
+
def symbolize(record)
|
17
|
+
Record.new(
|
18
|
+
event_id: record.event_id,
|
19
|
+
event_type: record.event_type,
|
20
|
+
data: record.data,
|
21
|
+
metadata: TransformKeys.symbolize(record.metadata),
|
22
|
+
timestamp: record.timestamp,
|
23
|
+
valid_at: record.valid_at,
|
19
24
|
)
|
20
25
|
end
|
21
26
|
end
|
@@ -4,19 +4,8 @@ module RubyEventStore
|
|
4
4
|
class Projection
|
5
5
|
private_class_method :new
|
6
6
|
|
7
|
-
def self.from_stream(
|
8
|
-
|
9
|
-
if first_of_streams.respond_to?(:flatten)
|
10
|
-
warn <<~EOW
|
11
|
-
Passing array to .from_stream is not supported. This method expects undefined number
|
12
|
-
of positional arguments (splat) rather than an array.
|
13
|
-
|
14
|
-
Expected: .from_stream(#{first_of_streams.map(&:inspect).join(", ")})
|
15
|
-
Received: .from_stream(#{first_of_streams})
|
16
|
-
EOW
|
17
|
-
streams = first_of_streams
|
18
|
-
end
|
19
|
-
|
7
|
+
def self.from_stream(stream_or_streams)
|
8
|
+
streams = Array(stream_or_streams)
|
20
9
|
raise(ArgumentError, "At least one stream must be given") if streams.empty?
|
21
10
|
new(streams: streams)
|
22
11
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyEventStore
|
4
|
+
class Record
|
5
|
+
StringsRequired = Class.new(StandardError)
|
6
|
+
def initialize(event_id:, data:, metadata:, event_type:, timestamp:, valid_at:)
|
7
|
+
raise StringsRequired unless [event_id, event_type].all? { |v| v.instance_of?(String) }
|
8
|
+
@event_id = event_id
|
9
|
+
@data = data
|
10
|
+
@metadata = metadata
|
11
|
+
@event_type = event_type
|
12
|
+
@timestamp = timestamp
|
13
|
+
@valid_at = valid_at
|
14
|
+
@serialized_records = {}
|
15
|
+
freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :event_id, :data, :metadata, :event_type, :timestamp, :valid_at
|
19
|
+
|
20
|
+
BIG_VALUE = 0b110011100100000010010010110011101011110101010101001100111110011
|
21
|
+
def hash
|
22
|
+
[
|
23
|
+
self.class,
|
24
|
+
event_id,
|
25
|
+
data,
|
26
|
+
metadata,
|
27
|
+
event_type,
|
28
|
+
timestamp,
|
29
|
+
valid_at,
|
30
|
+
].hash ^ BIG_VALUE
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(other)
|
34
|
+
other.instance_of?(self.class) &&
|
35
|
+
other.event_id.eql?(event_id) &&
|
36
|
+
other.data.eql?(data) &&
|
37
|
+
other.metadata.eql?(metadata) &&
|
38
|
+
other.event_type.eql?(event_type) &&
|
39
|
+
other.timestamp.eql?(timestamp) &&
|
40
|
+
other.valid_at.eql?(valid_at)
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_h
|
44
|
+
{
|
45
|
+
event_id: event_id,
|
46
|
+
data: data,
|
47
|
+
metadata: metadata,
|
48
|
+
event_type: event_type,
|
49
|
+
timestamp: timestamp,
|
50
|
+
valid_at: valid_at,
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def serialize(serializer)
|
55
|
+
@serialized_records[serializer] ||=
|
56
|
+
SerializedRecord.new(
|
57
|
+
event_id: event_id,
|
58
|
+
event_type: event_type,
|
59
|
+
data: serializer.dump(data),
|
60
|
+
metadata: serializer.dump(metadata),
|
61
|
+
timestamp: timestamp.iso8601(TIMESTAMP_PRECISION),
|
62
|
+
valid_at: valid_at.iso8601(TIMESTAMP_PRECISION),
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
alias_method :eql?, :==
|
67
|
+
end
|
68
|
+
end
|
@@ -3,18 +3,20 @@
|
|
3
3
|
module RubyEventStore
|
4
4
|
class SerializedRecord
|
5
5
|
StringsRequired = Class.new(StandardError)
|
6
|
-
def initialize(event_id:, data:, metadata:, event_type:)
|
6
|
+
def initialize(event_id:, data:, metadata:, event_type:, timestamp:, valid_at:)
|
7
7
|
raise StringsRequired unless [event_id, event_type].all? { |v| v.instance_of?(String) }
|
8
8
|
@event_id = event_id
|
9
9
|
@data = data
|
10
10
|
@metadata = metadata
|
11
11
|
@event_type = event_type
|
12
|
+
@timestamp = timestamp
|
13
|
+
@valid_at = valid_at
|
12
14
|
freeze
|
13
15
|
end
|
14
16
|
|
15
|
-
attr_reader :event_id, :data, :metadata, :event_type
|
17
|
+
attr_reader :event_id, :data, :metadata, :event_type, :timestamp, :valid_at
|
16
18
|
|
17
|
-
BIG_VALUE =
|
19
|
+
BIG_VALUE = 0b110011100100000010010010110011101011110101010101001100111110111
|
18
20
|
def hash
|
19
21
|
[
|
20
22
|
self.class,
|
@@ -22,6 +24,8 @@ module RubyEventStore
|
|
22
24
|
data,
|
23
25
|
metadata,
|
24
26
|
event_type,
|
27
|
+
timestamp,
|
28
|
+
valid_at,
|
25
29
|
].hash ^ BIG_VALUE
|
26
30
|
end
|
27
31
|
|
@@ -30,7 +34,9 @@ module RubyEventStore
|
|
30
34
|
other.event_id.eql?(event_id) &&
|
31
35
|
other.data.eql?(data) &&
|
32
36
|
other.metadata.eql?(metadata) &&
|
33
|
-
other.event_type.eql?(event_type)
|
37
|
+
other.event_type.eql?(event_type) &&
|
38
|
+
other.timestamp.eql?(timestamp) &&
|
39
|
+
other.valid_at.eql?(valid_at)
|
34
40
|
end
|
35
41
|
|
36
42
|
def to_h
|
@@ -39,9 +45,22 @@ module RubyEventStore
|
|
39
45
|
data: data,
|
40
46
|
metadata: metadata,
|
41
47
|
event_type: event_type,
|
48
|
+
timestamp: timestamp,
|
49
|
+
valid_at: valid_at,
|
42
50
|
}
|
43
51
|
end
|
44
52
|
|
53
|
+
def deserialize(serializer)
|
54
|
+
Record.new(
|
55
|
+
event_id: event_id,
|
56
|
+
event_type: event_type,
|
57
|
+
data: serializer.load(data),
|
58
|
+
metadata: serializer.load(metadata),
|
59
|
+
timestamp: Time.iso8601(timestamp),
|
60
|
+
valid_at: Time.iso8601(valid_at),
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
45
64
|
alias_method :eql?, :==
|
46
65
|
end
|
47
66
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
RSpec.shared_examples :broker do |broker_klass|
|
2
2
|
let(:event) { instance_double(::RubyEventStore::Event, event_type: 'EventType') }
|
3
|
-
let(:
|
3
|
+
let(:record) { instance_double(::RubyEventStore::Record) }
|
4
4
|
let(:handler) { HandlerClass.new }
|
5
5
|
let(:subscriptions) { ::RubyEventStore::Subscriptions.new }
|
6
6
|
let(:dispatcher) { ::RubyEventStore::Dispatcher.new }
|
@@ -9,26 +9,26 @@ RSpec.shared_examples :broker do |broker_klass|
|
|
9
9
|
specify "no dispatch when no subscriptions" do
|
10
10
|
expect(subscriptions).to receive(:all_for).with('EventType').and_return([])
|
11
11
|
expect(dispatcher).not_to receive(:call)
|
12
|
-
broker.call(event,
|
12
|
+
broker.call(event, record)
|
13
13
|
end
|
14
14
|
|
15
15
|
specify "calls subscription" do
|
16
16
|
expect(subscriptions).to receive(:all_for).with('EventType').and_return([handler])
|
17
|
-
expect(dispatcher).to receive(:call).with(handler, event,
|
18
|
-
broker.call(event,
|
17
|
+
expect(dispatcher).to receive(:call).with(handler, event, record)
|
18
|
+
broker.call(event, record)
|
19
19
|
end
|
20
20
|
|
21
21
|
specify "calls subscribed class" do
|
22
22
|
expect(subscriptions).to receive(:all_for).with('EventType').and_return([HandlerClass])
|
23
|
-
expect(dispatcher).to receive(:call).with(HandlerClass, event,
|
24
|
-
broker.call(event,
|
23
|
+
expect(dispatcher).to receive(:call).with(HandlerClass, event, record)
|
24
|
+
broker.call(event, record)
|
25
25
|
end
|
26
26
|
|
27
27
|
specify "calls all subscriptions" do
|
28
28
|
expect(subscriptions).to receive(:all_for).with('EventType').and_return([handler, HandlerClass])
|
29
|
-
expect(dispatcher).to receive(:call).with(handler, event,
|
30
|
-
expect(dispatcher).to receive(:call).with(HandlerClass, event,
|
31
|
-
broker.call(event,
|
29
|
+
expect(dispatcher).to receive(:call).with(handler, event, record)
|
30
|
+
expect(dispatcher).to receive(:call).with(HandlerClass, event, record)
|
31
|
+
broker.call(event, record)
|
32
32
|
end
|
33
33
|
|
34
34
|
specify 'raise error when no subscriber' do
|
@@ -3,15 +3,19 @@ module RubyEventStore
|
|
3
3
|
class SRecord
|
4
4
|
def self.new(
|
5
5
|
event_id: SecureRandom.uuid,
|
6
|
-
data:
|
7
|
-
metadata:
|
8
|
-
event_type: 'SRecordTestEvent'
|
6
|
+
data: {},
|
7
|
+
metadata: {},
|
8
|
+
event_type: 'SRecordTestEvent',
|
9
|
+
timestamp: Time.new.utc,
|
10
|
+
valid_at: nil
|
9
11
|
)
|
10
|
-
|
12
|
+
Record.new(
|
11
13
|
event_id: event_id,
|
12
14
|
data: data,
|
13
15
|
metadata: metadata,
|
14
16
|
event_type: event_type,
|
17
|
+
timestamp: timestamp.round(TIMESTAMP_PRECISION),
|
18
|
+
valid_at: (valid_at || timestamp).round(TIMESTAMP_PRECISION),
|
15
19
|
)
|
16
20
|
end
|
17
21
|
end
|
@@ -22,13 +26,45 @@ module RubyEventStore
|
|
22
26
|
Type2 = Class.new(RubyEventStore::Event)
|
23
27
|
# @private
|
24
28
|
Type3 = Class.new(RubyEventStore::Event)
|
29
|
+
|
30
|
+
# @private
|
31
|
+
class EventRepositoryHelper
|
32
|
+
def supports_concurrent_auto?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def supports_concurrent_any?
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def supports_binary?
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def supports_upsert?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def has_connection_pooling?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
def connection_pool_size
|
53
|
+
end
|
54
|
+
|
55
|
+
def cleanup_concurrency_test
|
56
|
+
end
|
57
|
+
|
58
|
+
def rescuable_concurrency_test_errors
|
59
|
+
[]
|
60
|
+
end
|
61
|
+
end
|
25
62
|
end
|
26
63
|
|
27
64
|
module RubyEventStore
|
28
|
-
RSpec.shared_examples :event_repository do
|
29
|
-
let(:
|
30
|
-
let(:
|
31
|
-
let(:specification) { Specification.new(SpecificationReader.new(repository, mapper)) }
|
65
|
+
::RSpec.shared_examples :event_repository do
|
66
|
+
let(:helper) { EventRepositoryHelper.new }
|
67
|
+
let(:specification) { Specification.new(SpecificationReader.new(repository, Mappers::NullMapper.new)) }
|
32
68
|
let(:global_stream) { Stream.new(GLOBAL_STREAM) }
|
33
69
|
let(:stream) { Stream.new(SecureRandom.uuid) }
|
34
70
|
let(:stream_flow) { Stream.new('flow') }
|
@@ -42,6 +78,11 @@ module RubyEventStore
|
|
42
78
|
let(:version_2) { ExpectedVersion.new(2) }
|
43
79
|
let(:version_3) { ExpectedVersion.new(3) }
|
44
80
|
|
81
|
+
def verify_conncurency_assumptions
|
82
|
+
return unless helper.has_connection_pooling?
|
83
|
+
expect(helper.connection_pool_size).to eq(5)
|
84
|
+
end
|
85
|
+
|
45
86
|
def read_events(scope, stream = nil, from: nil, to: nil, count: nil)
|
46
87
|
scope = scope.stream(stream.name) if stream
|
47
88
|
scope = scope.from(from) if from
|
@@ -393,7 +434,7 @@ module RubyEventStore
|
|
393
434
|
end
|
394
435
|
|
395
436
|
specify 'unlimited concurrency for :any - everything should succeed', timeout: 10, mutant: false do
|
396
|
-
skip unless
|
437
|
+
skip unless helper.supports_concurrent_any?
|
397
438
|
verify_conncurency_assumptions
|
398
439
|
begin
|
399
440
|
concurrency_level = 4
|
@@ -426,12 +467,12 @@ module RubyEventStore
|
|
426
467
|
end
|
427
468
|
expect(events0).to eq(events0.sort_by{|ev| ev.event_id })
|
428
469
|
ensure
|
429
|
-
cleanup_concurrency_test
|
470
|
+
helper.cleanup_concurrency_test
|
430
471
|
end
|
431
472
|
end
|
432
473
|
|
433
474
|
specify 'unlimited concurrency for :any - everything should succeed when linking', timeout: 10, mutant: false do
|
434
|
-
skip unless
|
475
|
+
skip unless helper.supports_concurrent_any?
|
435
476
|
verify_conncurency_assumptions
|
436
477
|
begin
|
437
478
|
concurrency_level = 4
|
@@ -471,12 +512,12 @@ module RubyEventStore
|
|
471
512
|
end
|
472
513
|
expect(events0).to eq(events0.sort_by{|ev| ev.event_id })
|
473
514
|
ensure
|
474
|
-
cleanup_concurrency_test
|
515
|
+
helper.cleanup_concurrency_test
|
475
516
|
end
|
476
517
|
end
|
477
518
|
|
478
519
|
specify 'limited concurrency for :auto - some operations will fail without outside lock, stream is ordered', mutant: false do
|
479
|
-
skip unless
|
520
|
+
skip unless helper.supports_concurrent_auto?
|
480
521
|
verify_conncurency_assumptions
|
481
522
|
begin
|
482
523
|
concurrency_level = 4
|
@@ -494,7 +535,7 @@ module RubyEventStore
|
|
494
535
|
SRecord.new(event_id: eid),
|
495
536
|
], stream, version_auto)
|
496
537
|
sleep(rand(concurrency_level) / 1000.0)
|
497
|
-
rescue WrongExpectedEventVersion, *rescuable_concurrency_test_errors
|
538
|
+
rescue WrongExpectedEventVersion, *helper.rescuable_concurrency_test_errors
|
498
539
|
fail_occurred +=1
|
499
540
|
end
|
500
541
|
end
|
@@ -510,14 +551,14 @@ module RubyEventStore
|
|
510
551
|
ev.event_id.start_with?("0-")
|
511
552
|
end
|
512
553
|
expect(events0).to eq(events0.sort_by{|ev| ev.event_id })
|
513
|
-
additional_limited_concurrency_for_auto_check
|
554
|
+
additional_limited_concurrency_for_auto_check if defined? additional_limited_concurrency_for_auto_check
|
514
555
|
ensure
|
515
|
-
cleanup_concurrency_test
|
556
|
+
helper.cleanup_concurrency_test
|
516
557
|
end
|
517
558
|
end
|
518
559
|
|
519
560
|
specify 'limited concurrency for :auto - some operations will fail without outside lock, stream is ordered', mutant: false do
|
520
|
-
skip unless
|
561
|
+
skip unless helper.supports_concurrent_auto?
|
521
562
|
verify_conncurency_assumptions
|
522
563
|
begin
|
523
564
|
concurrency_level = 4
|
@@ -542,7 +583,7 @@ module RubyEventStore
|
|
542
583
|
eid = "0000000#{i}-#{sprintf("%04d", j)}-0000-0000-000000000000"
|
543
584
|
repository.link_to_stream(eid, stream, version_auto)
|
544
585
|
sleep(rand(concurrency_level) / 1000.0)
|
545
|
-
rescue WrongExpectedEventVersion, *rescuable_concurrency_test_errors
|
586
|
+
rescue WrongExpectedEventVersion, *helper.rescuable_concurrency_test_errors
|
546
587
|
fail_occurred +=1
|
547
588
|
end
|
548
589
|
end
|
@@ -558,9 +599,9 @@ module RubyEventStore
|
|
558
599
|
ev.event_id.start_with?("0-")
|
559
600
|
end
|
560
601
|
expect(events0).to eq(events0.sort_by{|ev| ev.event_id })
|
561
|
-
additional_limited_concurrency_for_auto_check
|
602
|
+
additional_limited_concurrency_for_auto_check if defined? additional_limited_concurrency_for_auto_check
|
562
603
|
ensure
|
563
|
-
cleanup_concurrency_test
|
604
|
+
helper.cleanup_concurrency_test
|
564
605
|
end
|
565
606
|
end
|
566
607
|
|
@@ -573,30 +614,30 @@ module RubyEventStore
|
|
573
614
|
end
|
574
615
|
|
575
616
|
it 'data attributes are retrieved' do
|
576
|
-
event = SRecord.new(data:
|
617
|
+
event = SRecord.new(data: { "order_id" => 3 })
|
577
618
|
repository.append_to_stream(event, stream, version_any)
|
578
619
|
retrieved_event = read_events_forward(repository, count: 1).first
|
579
|
-
expect(retrieved_event.data).to eq(
|
620
|
+
expect(retrieved_event.data).to eq({ "order_id" => 3 })
|
580
621
|
end
|
581
622
|
|
582
623
|
it 'metadata attributes are retrieved' do
|
583
|
-
event = SRecord.new(metadata:
|
624
|
+
event = SRecord.new(metadata: { "request_id" => 3 })
|
584
625
|
repository.append_to_stream(event, stream, version_any)
|
585
626
|
retrieved_event = read_events_forward(repository, count: 1).first
|
586
|
-
expect(retrieved_event.metadata).to eq(
|
627
|
+
expect(retrieved_event.metadata).to eq({ "request_id" => 3 })
|
587
628
|
end
|
588
629
|
|
589
630
|
it 'data and metadata attributes are retrieved when linking' do
|
590
631
|
event = SRecord.new(
|
591
|
-
data:
|
592
|
-
metadata:
|
632
|
+
data: { "order_id" => 3 },
|
633
|
+
metadata: { "request_id" => 4},
|
593
634
|
)
|
594
635
|
repository
|
595
636
|
.append_to_stream(event, stream, version_any)
|
596
637
|
.link_to_stream(event.event_id, stream_flow, version_any)
|
597
638
|
retrieved_event = read_events_forward(repository, stream_flow).first
|
598
|
-
expect(retrieved_event.metadata).to eq(
|
599
|
-
expect(retrieved_event.data).to eq(
|
639
|
+
expect(retrieved_event.metadata).to eq({ "request_id" => 4 })
|
640
|
+
expect(retrieved_event.data).to eq({ "order_id" => 3 })
|
600
641
|
expect(event).to eq(retrieved_event)
|
601
642
|
end
|
602
643
|
|
@@ -918,7 +959,7 @@ module RubyEventStore
|
|
918
959
|
end
|
919
960
|
|
920
961
|
specify 'can store arbitrary binary data' do
|
921
|
-
skip unless
|
962
|
+
skip unless helper.supports_binary?
|
922
963
|
binary = "\xB0"
|
923
964
|
expect(binary.valid_encoding?).to eq(false)
|
924
965
|
binary.force_encoding("binary")
|
@@ -1091,7 +1132,7 @@ module RubyEventStore
|
|
1091
1132
|
|
1092
1133
|
context "#update_messages" do
|
1093
1134
|
specify "changes events" do
|
1094
|
-
skip unless
|
1135
|
+
skip unless helper.supports_upsert?
|
1095
1136
|
events = Array.new(5) { SRecord.new }
|
1096
1137
|
repository.append_to_stream(
|
1097
1138
|
events[0..2],
|
@@ -1104,19 +1145,20 @@ module RubyEventStore
|
|
1104
1145
|
ExpectedVersion.any
|
1105
1146
|
)
|
1106
1147
|
repository.update_messages([
|
1107
|
-
a = SRecord.new(event_id: events[0].event_id.clone, data: events[0].data,
|
1108
|
-
b = SRecord.new(event_id: events[1].event_id.dup,
|
1109
|
-
c = SRecord.new(event_id: events[2].event_id,
|
1110
|
-
d = SRecord.new(event_id: events[3].event_id.clone, data: events[3].data,
|
1111
|
-
e = SRecord.new(event_id: events[4].event_id.dup,
|
1148
|
+
a = SRecord.new(event_id: events[0].event_id.clone, data: events[0].data, metadata: events[0].metadata, event_type: events[0].event_type, timestamp: events[0].timestamp),
|
1149
|
+
b = SRecord.new(event_id: events[1].event_id.dup, data: { "test" => 1 }, metadata: events[1].metadata, event_type: events[1].event_type, timestamp: events[1].timestamp),
|
1150
|
+
c = SRecord.new(event_id: events[2].event_id, data: events[2].data, metadata: { "test" => 2 }, event_type: events[2].event_type, timestamp: events[2].timestamp),
|
1151
|
+
d = SRecord.new(event_id: events[3].event_id.clone, data: events[3].data, metadata: events[3].metadata, event_type: "event_type3", timestamp: events[3].timestamp),
|
1152
|
+
e = SRecord.new(event_id: events[4].event_id.dup, data: { "test" => 4 }, metadata: { "test" => 42 }, event_type: "event_type4", timestamp: events[4].timestamp),
|
1112
1153
|
])
|
1154
|
+
|
1113
1155
|
expect(repository.read(specification.result).to_a).to eq([a,b,c,d,e])
|
1114
1156
|
expect(repository.read(specification.stream("whatever").result).to_a).to eq([a,b,c])
|
1115
1157
|
expect(repository.read(specification.stream("elo").result).to_a).to eq([d,e])
|
1116
1158
|
end
|
1117
1159
|
|
1118
1160
|
specify "cannot change unexisting event" do
|
1119
|
-
skip unless
|
1161
|
+
skip unless helper.supports_upsert?
|
1120
1162
|
e = SRecord.new
|
1121
1163
|
expect{ repository.update_messages([e]) }.to raise_error do |err|
|
1122
1164
|
expect(err).to be_a(EventNotFound)
|
@@ -1124,6 +1166,14 @@ module RubyEventStore
|
|
1124
1166
|
expect(err.message).to eq("Event not found: #{e.event_id}")
|
1125
1167
|
end
|
1126
1168
|
end
|
1169
|
+
|
1170
|
+
specify "does not change timestamp" do
|
1171
|
+
r = SRecord.new(timestamp: Time.utc(2020, 1, 1))
|
1172
|
+
repository.append_to_stream([r], Stream.new("whatever"), ExpectedVersion.any)
|
1173
|
+
repository.update_messages([SRecord.new(event_id: r.event_id, timestamp: Time.utc(2020, 1, 20))])
|
1174
|
+
|
1175
|
+
expect(repository.read(specification.result).first.timestamp).to eq(Time.utc(2020, 1, 1))
|
1176
|
+
end
|
1127
1177
|
end
|
1128
1178
|
|
1129
1179
|
specify do
|
@@ -1228,5 +1278,119 @@ module RubyEventStore
|
|
1228
1278
|
expect(repository.count(specification.stream("Dummy").of_type([Type3]).result)).to eq(2)
|
1229
1279
|
expect(repository.count(specification.stream(stream.name).of_type([Type3]).result)).to eq(0)
|
1230
1280
|
end
|
1281
|
+
|
1282
|
+
specify 'timestamp precision' do
|
1283
|
+
time = Time.utc(2020, 9, 11, 12, 26, 0, 123456)
|
1284
|
+
repository.append_to_stream(SRecord.new(timestamp: time), stream, version_none)
|
1285
|
+
event = read_events_forward(repository, count: 1).first
|
1286
|
+
|
1287
|
+
expect(event.timestamp).to eq(time)
|
1288
|
+
end
|
1289
|
+
|
1290
|
+
specify 'fetching records older than specified date in stream' do
|
1291
|
+
event_1 = SRecord.new(event_id: '8a6f053e-3ce2-4c82-a55b-4d02c66ae6ea', timestamp: Time.utc(2020, 1, 1))
|
1292
|
+
event_2 = SRecord.new(event_id: '8cee1139-4f96-483a-a175-2b947283c3c7', timestamp: Time.utc(2020, 1, 2))
|
1293
|
+
event_3 = SRecord.new(event_id: 'd345f86d-b903-4d78-803f-38990c078d9e', timestamp: Time.utc(2020, 1, 3))
|
1294
|
+
repository.append_to_stream([event_1, event_2, event_3], Stream.new('whatever'), version_any)
|
1295
|
+
|
1296
|
+
expect(repository.read(specification.stream('whatever').older_than(Time.utc(2020, 1, 2)).result).to_a).to eq([event_1])
|
1297
|
+
end
|
1298
|
+
|
1299
|
+
specify 'fetching records older than or equal to specified date in stream' do
|
1300
|
+
event_1 = SRecord.new(event_id: '8a6f053e-3ce2-4c82-a55b-4d02c66ae6ea', timestamp: Time.utc(2020, 1, 1))
|
1301
|
+
event_2 = SRecord.new(event_id: '8cee1139-4f96-483a-a175-2b947283c3c7', timestamp: Time.utc(2020, 1, 2))
|
1302
|
+
event_3 = SRecord.new(event_id: 'd345f86d-b903-4d78-803f-38990c078d9e', timestamp: Time.utc(2020, 1, 3))
|
1303
|
+
repository.append_to_stream([event_1, event_2, event_3], Stream.new('whatever'), version_any)
|
1304
|
+
|
1305
|
+
expect(repository.read(specification.stream('whatever').older_than_or_equal(Time.utc(2020, 1, 2)).result).to_a).to eq([event_1, event_2])
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
specify 'fetching records newer than specified date in stream' do
|
1309
|
+
event_1 = SRecord.new(event_id: '8a6f053e-3ce2-4c82-a55b-4d02c66ae6ea', timestamp: Time.utc(2020, 1, 1))
|
1310
|
+
event_2 = SRecord.new(event_id: '8cee1139-4f96-483a-a175-2b947283c3c7', timestamp: Time.utc(2020, 1, 2))
|
1311
|
+
event_3 = SRecord.new(event_id: 'd345f86d-b903-4d78-803f-38990c078d9e', timestamp: Time.utc(2020, 1, 3))
|
1312
|
+
repository.append_to_stream([event_1, event_2, event_3], Stream.new('whatever'), version_any)
|
1313
|
+
|
1314
|
+
expect(repository.read(specification.stream('whatever').newer_than(Time.utc(2020, 1, 2)).result).to_a).to eq([event_3])
|
1315
|
+
end
|
1316
|
+
|
1317
|
+
specify 'fetching records newer than or equal to specified date in stream' do
|
1318
|
+
event_1 = SRecord.new(event_id: '8a6f053e-3ce2-4c82-a55b-4d02c66ae6ea', timestamp: Time.utc(2020, 1, 1))
|
1319
|
+
event_2 = SRecord.new(event_id: '8cee1139-4f96-483a-a175-2b947283c3c7', timestamp: Time.utc(2020, 1, 2))
|
1320
|
+
event_3 = SRecord.new(event_id: 'd345f86d-b903-4d78-803f-38990c078d9e', timestamp: Time.utc(2020, 1, 3))
|
1321
|
+
repository.append_to_stream([event_1, event_2, event_3], Stream.new('whatever'), version_any)
|
1322
|
+
|
1323
|
+
expect(repository.read(specification.stream('whatever').newer_than_or_equal(Time.utc(2020, 1, 2)).result).to_a).to eq([event_2, event_3])
|
1324
|
+
end
|
1325
|
+
|
1326
|
+
specify 'fetching records older than specified date' do
|
1327
|
+
event_1 = SRecord.new(event_id: '8a6f053e-3ce2-4c82-a55b-4d02c66ae6ea', timestamp: Time.utc(2020, 1, 1))
|
1328
|
+
event_2 = SRecord.new(event_id: '8cee1139-4f96-483a-a175-2b947283c3c7', timestamp: Time.utc(2020, 1, 2))
|
1329
|
+
event_3 = SRecord.new(event_id: 'd345f86d-b903-4d78-803f-38990c078d9e', timestamp: Time.utc(2020, 1, 3))
|
1330
|
+
repository.append_to_stream([event_1, event_2, event_3], Stream.new('whatever'), version_any)
|
1331
|
+
|
1332
|
+
expect(repository.read(specification.older_than(Time.utc(2020, 1, 2)).result).to_a).to eq([event_1])
|
1333
|
+
end
|
1334
|
+
|
1335
|
+
specify 'fetching records older than or equal to specified date' do
|
1336
|
+
event_1 = SRecord.new(event_id: '8a6f053e-3ce2-4c82-a55b-4d02c66ae6ea', timestamp: Time.utc(2020, 1, 1))
|
1337
|
+
event_2 = SRecord.new(event_id: '8cee1139-4f96-483a-a175-2b947283c3c7', timestamp: Time.utc(2020, 1, 2))
|
1338
|
+
event_3 = SRecord.new(event_id: 'd345f86d-b903-4d78-803f-38990c078d9e', timestamp: Time.utc(2020, 1, 3))
|
1339
|
+
repository.append_to_stream([event_1, event_2, event_3], Stream.new('whatever'), version_any)
|
1340
|
+
|
1341
|
+
expect(repository.read(specification.older_than_or_equal(Time.utc(2020, 1, 2)).result).to_a).to eq([event_1, event_2])
|
1342
|
+
end
|
1343
|
+
|
1344
|
+
specify 'fetching records newer than specified date' do
|
1345
|
+
event_1 = SRecord.new(event_id: '8a6f053e-3ce2-4c82-a55b-4d02c66ae6ea', timestamp: Time.utc(2020, 1, 1))
|
1346
|
+
event_2 = SRecord.new(event_id: '8cee1139-4f96-483a-a175-2b947283c3c7', timestamp: Time.utc(2020, 1, 2))
|
1347
|
+
event_3 = SRecord.new(event_id: 'd345f86d-b903-4d78-803f-38990c078d9e', timestamp: Time.utc(2020, 1, 3))
|
1348
|
+
repository.append_to_stream([event_1, event_2, event_3], Stream.new('whatever'), version_any)
|
1349
|
+
|
1350
|
+
expect(repository.read(specification.newer_than(Time.utc(2020, 1, 2)).result).to_a).to eq([event_3])
|
1351
|
+
end
|
1352
|
+
|
1353
|
+
specify 'fetching records newer than or equal to specified date' do
|
1354
|
+
event_1 = SRecord.new(event_id: '8a6f053e-3ce2-4c82-a55b-4d02c66ae6ea', timestamp: Time.utc(2020, 1, 1))
|
1355
|
+
event_2 = SRecord.new(event_id: '8cee1139-4f96-483a-a175-2b947283c3c7', timestamp: Time.utc(2020, 1, 2))
|
1356
|
+
event_3 = SRecord.new(event_id: 'd345f86d-b903-4d78-803f-38990c078d9e', timestamp: Time.utc(2020, 1, 3))
|
1357
|
+
repository.append_to_stream([event_1, event_2, event_3], Stream.new('whatever'), version_any)
|
1358
|
+
|
1359
|
+
expect(repository.read(specification.newer_than_or_equal(Time.utc(2020, 1, 2)).result).to_a).to eq([event_2, event_3])
|
1360
|
+
end
|
1361
|
+
|
1362
|
+
specify 'fetching records from disjoint periods' do
|
1363
|
+
event_1 = SRecord.new(event_id: '8a6f053e-3ce2-4c82-a55b-4d02c66ae6ea', timestamp: Time.utc(2020, 1, 1))
|
1364
|
+
event_2 = SRecord.new(event_id: '8cee1139-4f96-483a-a175-2b947283c3c7', timestamp: Time.utc(2020, 1, 2))
|
1365
|
+
event_3 = SRecord.new(event_id: 'd345f86d-b903-4d78-803f-38990c078d9e', timestamp: Time.utc(2020, 1, 3))
|
1366
|
+
repository.append_to_stream([event_1, event_2, event_3], Stream.new('whatever'), version_any)
|
1367
|
+
|
1368
|
+
expect(repository.read(specification.older_than(Time.utc(2020, 1, 2)).newer_than(Time.utc(2020, 1, 2)).result).to_a).to eq([])
|
1369
|
+
end
|
1370
|
+
|
1371
|
+
specify 'fetching records within time range' do
|
1372
|
+
event_1 = SRecord.new(event_id: '8a6f053e-3ce2-4c82-a55b-4d02c66ae6ea', timestamp: Time.utc(2020, 1, 1))
|
1373
|
+
event_2 = SRecord.new(event_id: '8cee1139-4f96-483a-a175-2b947283c3c7', timestamp: Time.utc(2020, 1, 2))
|
1374
|
+
event_3 = SRecord.new(event_id: 'd345f86d-b903-4d78-803f-38990c078d9e', timestamp: Time.utc(2020, 1, 3))
|
1375
|
+
repository.append_to_stream([event_1, event_2, event_3], Stream.new('whatever'), version_any)
|
1376
|
+
|
1377
|
+
expect(repository.read(specification.between(Time.utc(2020, 1, 1)...Time.utc(2020, 1, 3)).result).to_a).to eq([event_1, event_2])
|
1378
|
+
end
|
1379
|
+
|
1380
|
+
specify "time order is respected" do
|
1381
|
+
repository.append_to_stream([
|
1382
|
+
SRecord.new(event_id: e1 = SecureRandom.uuid, timestamp: Time.new(2020,1,1), valid_at: Time.new(2020,1,9)),
|
1383
|
+
SRecord.new(event_id: e2 = SecureRandom.uuid, timestamp: Time.new(2020,1,3), valid_at: Time.new(2020,1,6)),
|
1384
|
+
SRecord.new(event_id: e3 = SecureRandom.uuid, timestamp: Time.new(2020,1,2), valid_at: Time.new(2020,1,3)),
|
1385
|
+
],
|
1386
|
+
Stream.new("Dummy"),
|
1387
|
+
ExpectedVersion.any
|
1388
|
+
)
|
1389
|
+
expect(repository.read(specification.result).map(&:event_id)).to eq [e1, e2, e3]
|
1390
|
+
expect(repository.read(specification.as_at.result).map(&:event_id)).to eq [e1, e3, e2]
|
1391
|
+
expect(repository.read(specification.as_at.backward.result).map(&:event_id)).to eq [e2, e3, e1]
|
1392
|
+
expect(repository.read(specification.as_of.result).map(&:event_id)).to eq [e3, e2, e1]
|
1393
|
+
expect(repository.read(specification.as_of.backward.result).map(&:event_id)).to eq [e1, e2, e3]
|
1394
|
+
end
|
1231
1395
|
end
|
1232
1396
|
end
|