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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +1 -0
  3. data/Gemfile +2 -4
  4. data/Gemfile.lock +118 -0
  5. data/Makefile +13 -3
  6. data/lib/ruby_event_store.rb +4 -7
  7. data/lib/ruby_event_store/broker.rb +3 -3
  8. data/lib/ruby_event_store/client.rb +47 -23
  9. data/lib/ruby_event_store/composed_dispatcher.rb +2 -2
  10. data/lib/ruby_event_store/constants.rb +1 -0
  11. data/lib/ruby_event_store/errors.rb +0 -1
  12. data/lib/ruby_event_store/event.rb +8 -1
  13. data/lib/ruby_event_store/immediate_async_dispatcher.rb +2 -2
  14. data/lib/ruby_event_store/in_memory_repository.rb +98 -59
  15. data/lib/ruby_event_store/instrumented_dispatcher.rb +2 -2
  16. data/lib/ruby_event_store/mappers/default.rb +28 -6
  17. data/lib/ruby_event_store/mappers/deprecated_wrapper.rb +33 -0
  18. data/lib/ruby_event_store/mappers/encryption_mapper.rb +1 -4
  19. data/lib/ruby_event_store/mappers/instrumented_mapper.rb +8 -4
  20. data/lib/ruby_event_store/mappers/json_mapper.rb +2 -4
  21. data/lib/ruby_event_store/mappers/pipeline.rb +26 -5
  22. data/lib/ruby_event_store/mappers/pipeline_mapper.rb +6 -2
  23. data/lib/ruby_event_store/mappers/transformation/domain_event.rb +16 -8
  24. data/lib/ruby_event_store/mappers/transformation/encryption.rb +20 -12
  25. data/lib/ruby_event_store/mappers/transformation/event_class_remapper.rb +11 -4
  26. data/lib/ruby_event_store/mappers/transformation/serialization.rb +16 -14
  27. data/lib/ruby_event_store/mappers/transformation/stringify_metadata_keys.rb +12 -7
  28. data/lib/ruby_event_store/mappers/transformation/symbolize_metadata_keys.rb +12 -7
  29. data/lib/ruby_event_store/null.rb +13 -0
  30. data/lib/ruby_event_store/projection.rb +2 -13
  31. data/lib/ruby_event_store/record.rb +68 -0
  32. data/lib/ruby_event_store/serialized_record.rb +23 -4
  33. data/lib/ruby_event_store/spec/broker_lint.rb +9 -9
  34. data/lib/ruby_event_store/spec/event_repository_lint.rb +200 -36
  35. data/lib/ruby_event_store/spec/mapper_lint.rb +6 -6
  36. data/lib/ruby_event_store/spec/subscriptions_lint.rb +6 -0
  37. data/lib/ruby_event_store/specification.rb +100 -7
  38. data/lib/ruby_event_store/specification_reader.rb +2 -2
  39. data/lib/ruby_event_store/specification_result.rb +86 -2
  40. data/lib/ruby_event_store/version.rb +1 -1
  41. data/ruby_event_store.gemspec +0 -2
  42. metadata +8 -9
  43. data/lib/ruby_event_store/mappers/protobuf.rb +0 -24
  44. data/lib/ruby_event_store/mappers/transformation/item.rb +0 -56
  45. data/lib/ruby_event_store/mappers/transformation/proto_event.rb +0 -17
  46. data/lib/ruby_event_store/mappers/transformation/protobuf_encoder.rb +0 -30
  47. data/lib/ruby_event_store/mappers/transformation/protobuf_nested_struct_metadata.rb +0 -30
  48. 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(item)
27
- data = item.data
28
- metadata = item.metadata.dup
29
- event_class = Object.const_get(item.event_type)
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
- item.merge(
35
- data: encrypt_data(deep_dup(data), crypto_description),
36
- metadata: metadata
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(item)
41
- metadata = item.metadata.dup
44
+ def load(record)
45
+ metadata = record.metadata.dup
42
46
  crypto_description = Hash(metadata.delete(:encryption))
43
47
 
44
- item.merge(
45
- data: decrypt_data(item.data, crypto_description),
46
- metadata: metadata
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(item)
12
- item
11
+ def dump(record)
12
+ record
13
13
  end
14
14
 
15
- def load(item)
16
- item.merge(event_type: class_map[item.fetch(:event_type)] || item.fetch(:event_type))
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
- @serializer = serializer
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
- Item.new(
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
- Item.new(
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(item)
8
- stringify(item)
7
+ def dump(record)
8
+ stringify(record)
9
9
  end
10
10
 
11
- def load(item)
12
- stringify(item)
11
+ def load(record)
12
+ stringify(record)
13
13
  end
14
14
 
15
15
  private
16
- def stringify(item)
17
- item.merge(
18
- metadata: TransformKeys.stringify(item.metadata),
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(item)
8
- symbolize(item)
7
+ def dump(record)
8
+ symbolize(record)
9
9
  end
10
10
 
11
- def load(item)
12
- symbolize(item)
11
+ def load(record)
12
+ symbolize(record)
13
13
  end
14
14
 
15
15
  private
16
- def symbolize(item)
17
- item.merge(
18
- metadata: TransformKeys.symbolize(item.metadata),
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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module NULL
5
+ def self.dump(value)
6
+ value
7
+ end
8
+
9
+ def self.load(value)
10
+ value
11
+ end
12
+ end
13
+ end
@@ -4,19 +4,8 @@ module RubyEventStore
4
4
  class Projection
5
5
  private_class_method :new
6
6
 
7
- def self.from_stream(*streams)
8
- first_of_streams = streams.first
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 = 0b110011100100000010010010110011101011110101010101001100111110011
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(:serialized_event) { instance_double(::RubyEventStore::SerializedRecord) }
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, serialized_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, serialized_event)
18
- broker.call(event, serialized_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, serialized_event)
24
- broker.call(event, serialized_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, serialized_event)
30
- expect(dispatcher).to receive(:call).with(HandlerClass, event, serialized_event)
31
- broker.call(event, serialized_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
- SerializedRecord.new(
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 |repository_class, rescuable_concurrency_test_errors = []|
29
- let(:repository) { subject || repository_class.new }
30
- let(:mapper) { Mappers::NullMapper.new }
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 test_race_conditions_any
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 test_race_conditions_any
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 test_race_conditions_auto
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 test_race_conditions_auto
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: '{"order_id":3}')
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('{"order_id":3}')
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: '{"request_id":3}')
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('{"request_id":3}')
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: '{"order_id":3}',
592
- metadata: '{"request_id":4}',
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('{"request_id":4}')
599
- expect(retrieved_event.data).to eq('{"order_id":3}')
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 test_binary
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 test_change
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, metadata: events[0].metadata, event_type: events[0].event_type),
1108
- b = SRecord.new(event_id: events[1].event_id.dup, data: '{"test":1}', metadata: events[1].metadata, event_type: events[1].event_type),
1109
- c = SRecord.new(event_id: events[2].event_id, data: events[2].data, metadata: '{"test":2}', event_type: events[2].event_type),
1110
- d = SRecord.new(event_id: events[3].event_id.clone, data: events[3].data, metadata: events[3].metadata, event_type: "event_type3"),
1111
- e = SRecord.new(event_id: events[4].event_id.dup, data: '{"test":4}', metadata: '{"test":42}', event_type: "event_type4"),
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 test_change
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