ruby_event_store-rom 1.3.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/ruby_event_store/rom/changesets/create_events.rb +10 -18
  4. data/lib/ruby_event_store/rom/changesets/create_stream_entries.rb +3 -12
  5. data/lib/ruby_event_store/rom/changesets/update_events.rb +33 -19
  6. data/lib/ruby_event_store/rom/event_repository.rb +65 -61
  7. data/lib/ruby_event_store/rom/index_violation_detector.rb +25 -0
  8. data/lib/ruby_event_store/rom/mappers/event_to_serialized_record.rb +10 -6
  9. data/lib/ruby_event_store/rom/mappers/stream_entry_to_serialized_record.rb +11 -6
  10. data/lib/ruby_event_store/rom/rake_task.rb +5 -0
  11. data/lib/ruby_event_store/rom/relations/events.rb +78 -0
  12. data/lib/ruby_event_store/rom/relations/stream_entries.rb +83 -0
  13. data/lib/ruby_event_store/rom/repositories/events.rb +55 -30
  14. data/lib/ruby_event_store/rom/repositories/stream_entries.rb +15 -16
  15. data/lib/ruby_event_store/rom/tasks/migration_tasks.rake +36 -0
  16. data/lib/ruby_event_store/rom/types.rb +16 -14
  17. data/lib/ruby_event_store/rom/unit_of_work.rb +28 -13
  18. data/lib/ruby_event_store/rom/version.rb +1 -1
  19. data/lib/ruby_event_store/rom.rb +28 -103
  20. data/lib/ruby_event_store-rom.rb +1 -1
  21. metadata +31 -49
  22. data/.rubocop.yml +0 -1
  23. data/.rubocop_todo.yml +0 -84
  24. data/CHANGELOG.md +0 -9
  25. data/Gemfile +0 -12
  26. data/Makefile +0 -57
  27. data/Rakefile +0 -20
  28. data/db/migrate/20180327044629_create_ruby_event_store_tables.rb +0 -54
  29. data/db/migrate/20181026152045_index_by_event_type.rb +0 -9
  30. data/lib/ruby_event_store/rom/adapters/memory/changesets/create_events.rb +0 -19
  31. data/lib/ruby_event_store/rom/adapters/memory/changesets/create_stream_entries.rb +0 -19
  32. data/lib/ruby_event_store/rom/adapters/memory/changesets/update_events.rb +0 -18
  33. data/lib/ruby_event_store/rom/adapters/memory/relations/events.rb +0 -56
  34. data/lib/ruby_event_store/rom/adapters/memory/relations/stream_entries.rb +0 -114
  35. data/lib/ruby_event_store/rom/adapters/memory/unit_of_work.rb +0 -36
  36. data/lib/ruby_event_store/rom/adapters/sql/changesets/create_events.rb +0 -15
  37. data/lib/ruby_event_store/rom/adapters/sql/changesets/update_events.rb +0 -41
  38. data/lib/ruby_event_store/rom/adapters/sql/index_violation_detector.rb +0 -31
  39. data/lib/ruby_event_store/rom/adapters/sql/rake_task.rb +0 -5
  40. data/lib/ruby_event_store/rom/adapters/sql/relations/events.rb +0 -27
  41. data/lib/ruby_event_store/rom/adapters/sql/relations/stream_entries.rb +0 -72
  42. data/lib/ruby_event_store/rom/adapters/sql/tasks/migration_tasks.rake +0 -36
  43. data/lib/ruby_event_store/rom/memory.rb +0 -82
  44. data/lib/ruby_event_store/rom/sql.rb +0 -169
  45. data/lib/ruby_event_store/rom/tuple_uniqueness_error.rb +0 -21
  46. data/lib/ruby_event_store/spec/rom/event_repository_lint.rb +0 -176
  47. data/lib/ruby_event_store/spec/rom/relations/events_lint.rb +0 -75
  48. data/lib/ruby_event_store/spec/rom/relations/stream_entries_lint.rb +0 -198
  49. data/lib/ruby_event_store/spec/rom/spec_helper_lint.rb +0 -15
  50. data/lib/ruby_event_store/spec/rom/unit_of_work_lint.rb +0 -37
  51. data/ruby_event_store-rom.gemspec +0 -37
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 301643330ee509f7bb50b8eaab0c25ae261f57fbaeb8e6482c0c5e08e963aa6a
4
- data.tar.gz: fb44e5a4cea7fb9fc1ddcc2d20b3e9f2e1ca31f4d55d3d12385ddc3c5ceb7ed9
3
+ metadata.gz: 3af23fb98d5ccf4b0730bfd4da1dfea36b511d5b7d00e82158daaa7ed3ba6979
4
+ data.tar.gz: 741f05db9dc98c607888e5a3a07a8289a3d02094e8deecfcf48da150d6388954
5
5
  SHA512:
6
- metadata.gz: d0678314a60a994e96459db4016d5f5d3b5282a2bef564e1f77ed038d47640882739aa5190913aca160707ca8e63c68ac8597419199c1cea8fdf0e0bcf4e605a
7
- data.tar.gz: b5b4749ffc9e53e85f9eba739777b23340bd8e628cb3d10a204cf42c33876ea0989a7dc58674b2912ac8dd0c936672c0fef5c5c2992dc70225788d746fadd4f4
6
+ metadata.gz: f6846ccb455e16a59fe7a3e655f912f77998af55527d7e0c49278f52454d3d283e9113b70d7fff4289268dc13018d3b0ab4e0dcb774767a313078c8081e7d7fa
7
+ data.tar.gz: b02ae36dbe6641fda9bb4005821f220954f1ff09672736f2f895963ba0fa366442b1aa21100fee0b20663d9b718d5f66f2a28461419caf1b45c6589cefd92bc3
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # RubyEventStore ROM Event Repository
2
2
 
3
+ ![RubyEventStore ROM Event Repository](https://github.com/RailsEventStore/rails_event_store/workflows/ruby_event_store-rom/badge.svg)
4
+
3
5
  A Ruby Object Model (ROM) implementation of events repository for [Ruby Event Store](https://github.com/RailsEventStore/rails_event_store).
4
6
 
5
7
  This version of the ROM adapter supports [rom-sql](https://github.com/rom-rb/rom-sql) at this time. It is an alternative to the ActiveRecord `EventRepository` implementation used in `rails_event_store` gem.
6
8
 
7
9
  [Read the docs to get started.](http://railseventstore.org/docs/repository/)
8
-
9
- _Additonal backing stores via ROM are being tracked here: [#299](https://github.com/RailsEventStore/rails_event_store/issues/299)._
@@ -4,27 +4,19 @@ module RubyEventStore
4
4
  module ROM
5
5
  module Changesets
6
6
  class CreateEvents < ::ROM::Changeset::Create
7
- module Defaults
8
- def self.included(base)
9
- base.class_eval do
10
- relation :events
7
+ relation :events
11
8
 
12
- # Convert to Hash
13
- map(&:to_h)
14
-
15
- map do
16
- rename_keys event_id: :id
17
- accept_keys %i[id data metadata event_type]
18
- end
19
-
20
- map do |tuple|
21
- Hash(created_at: RubyEventStore::ROM::Types::DateTime.call(nil)).merge(tuple)
22
- end
23
- end
24
- end
9
+ map(&:to_h)
10
+ map do
11
+ rename_keys timestamp: :created_at
12
+ map_value :created_at, ->(time) { Time.iso8601(time).localtime }
13
+ map_value :valid_at, ->(time) { Time.iso8601(time).localtime }
14
+ accept_keys %i[event_id data metadata event_type created_at valid_at]
25
15
  end
26
16
 
27
- include Defaults
17
+ def commit
18
+ relation.multi_insert(to_a)
19
+ end
28
20
  end
29
21
  end
30
22
  end
@@ -4,19 +4,10 @@ module RubyEventStore
4
4
  module ROM
5
5
  module Changesets
6
6
  class CreateStreamEntries < ::ROM::Changeset::Create
7
- module Defaults
8
- def self.included(base)
9
- base.class_eval do
10
- relation :stream_entries
7
+ relation :stream_entries
11
8
 
12
- map do |tuple|
13
- Hash(created_at: RubyEventStore::ROM::Types::DateTime.call(nil)).merge(tuple)
14
- end
15
- end
16
- end
17
- end
18
-
19
- include Defaults
9
+ map { |tuple| Hash(created_at: RubyEventStore::ROM::Types::DateTime.call(nil)).merge(tuple) }
10
+ map { map_value :created_at, ->(datetime) { datetime.to_time.localtime } }
20
11
  end
21
12
  end
22
13
  end
@@ -4,27 +4,41 @@ module RubyEventStore
4
4
  module ROM
5
5
  module Changesets
6
6
  class UpdateEvents < ::ROM::Changeset::Update
7
- module Defaults
8
- def self.included(base)
9
- base.class_eval do
10
- relation :events
11
-
12
- # Convert to Hash
13
- map(&:to_h)
14
-
15
- map do
16
- rename_keys event_id: :id
17
- accept_keys %i[id data metadata event_type created_at]
18
- end
19
-
20
- map do |tuple|
21
- Hash(created_at: RubyEventStore::ROM::Types::DateTime.call(nil)).merge(tuple)
22
- end
23
- end
24
- end
7
+ relation :events
8
+
9
+ map(&:to_h)
10
+ map do
11
+ rename_keys timestamp: :created_at
12
+ map_value :created_at, ->(time) { Time.iso8601(time).localtime }
13
+ map_value :valid_at, ->(time) { Time.iso8601(time).localtime }
14
+ accept_keys %i[event_id data metadata event_type created_at valid_at]
15
+ end
16
+
17
+ UPSERT_COLUMNS = %i[event_type data metadata valid_at].freeze
18
+
19
+ def commit
20
+ supports_on_duplicate_key_update? ? commit_on_duplicate_key_update : commit_insert_conflict_update
21
+ end
22
+
23
+ private
24
+
25
+ def supports_on_duplicate_key_update?
26
+ relation.dataset.db.adapter_scheme =~ /mysql/
25
27
  end
26
28
 
27
- include Defaults
29
+ def commit_on_duplicate_key_update
30
+ relation.dataset.on_duplicate_key_update(*UPSERT_COLUMNS).multi_insert(to_a)
31
+ end
32
+
33
+ def commit_insert_conflict_update
34
+ relation
35
+ .dataset
36
+ .insert_conflict(
37
+ target: :event_id,
38
+ update: UPSERT_COLUMNS.each_with_object({}) { |column, memo| memo[column] = Sequel[:excluded][column] }
39
+ )
40
+ .multi_insert(to_a)
41
+ end
28
42
  end
29
43
  end
30
44
  end
@@ -1,41 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ruby_event_store/rom/unit_of_work'
4
- require 'forwardable'
5
-
6
3
  module RubyEventStore
7
4
  module ROM
8
5
  class EventRepository
9
- extend Forwardable
10
-
11
- def_delegator :@rom, :handle_error, :guard_for
12
- def_delegators :@rom, :unit_of_work
13
-
14
- def initialize(rom: ROM.env)
15
- raise ArgumentError, 'Must specify rom' unless rom && rom.instance_of?(Env)
16
-
17
- @rom = rom
18
- @events = Repositories::Events.new(rom.rom_container)
19
- @stream_entries = Repositories::StreamEntries.new(rom.rom_container)
6
+ def initialize(rom:, serializer:)
7
+ @serializer = serializer
8
+ @events = Repositories::Events.new(rom)
9
+ @stream_entries = Repositories::StreamEntries.new(rom)
10
+ @unit_of_work = UnitOfWork.new(rom.gateways.fetch(:default))
20
11
  end
21
12
 
22
- def append_to_stream(events, stream, expected_version)
23
- events = Array(events)
24
- event_ids = events.map(&:event_id)
25
-
26
- guard_for(:unique_violation) do
27
- unit_of_work do |changesets|
28
- # Create changesets inside transaction because
29
- # we want to find the last position (a.k.a. version)
30
- # again if the transaction is retried due to a
31
- # deadlock in MySQL
32
- changesets << @events.create_changeset(events)
33
- changesets << @stream_entries.create_changeset(
34
- event_ids,
35
- stream,
36
- @stream_entries.resolve_version(stream, expected_version),
37
- global_stream: true
38
- )
13
+ def append_to_stream(records, stream, expected_version)
14
+ serialized_records = records.map { |record| record.serialize(@serializer) }
15
+ event_ids = records.map(&:event_id)
16
+
17
+ handle_unique_violation do
18
+ @unit_of_work.call do |changesets|
19
+ changesets << @events.create_changeset(serialized_records)
20
+ changesets <<
21
+ @stream_entries.create_changeset(
22
+ event_ids,
23
+ stream,
24
+ @stream_entries.resolve_version(stream, expected_version)
25
+ )
39
26
  end
40
27
  end
41
28
 
@@ -43,64 +30,81 @@ module RubyEventStore
43
30
  end
44
31
 
45
32
  def link_to_stream(event_ids, stream, expected_version)
46
- event_ids = Array(event_ids)
47
-
48
- # Validate event IDs
49
- @events
50
- .find_nonexistent_pks(event_ids)
51
- .each { |id| raise EventNotFound, id }
52
-
53
- guard_for(:unique_violation) do
54
- unit_of_work do |changesets|
55
- changesets << @stream_entries.create_changeset(
56
- event_ids,
57
- stream,
58
- @stream_entries.resolve_version(stream, expected_version)
59
- )
33
+ validate_event_ids(event_ids)
34
+
35
+ handle_unique_violation do
36
+ @unit_of_work.call do |changesets|
37
+ changesets <<
38
+ @stream_entries.create_changeset(
39
+ event_ids,
40
+ stream,
41
+ @stream_entries.resolve_version(stream, expected_version)
42
+ )
60
43
  end
61
44
  end
62
45
 
63
46
  self
64
47
  end
65
48
 
49
+ def position_in_stream(event_id, stream)
50
+ @stream_entries.position_in_stream(event_id, stream)
51
+ end
52
+
53
+ def global_position(event_id)
54
+ @events.global_position(event_id)
55
+ end
56
+
57
+ def event_in_stream?(event_id, stream)
58
+ @stream_entries.event_in_stream?(event_id, stream)
59
+ end
60
+
66
61
  def delete_stream(stream)
67
62
  @stream_entries.delete(stream)
68
63
  end
69
64
 
70
65
  def has_event?(event_id)
71
- guard_for(:not_found, event_id, swallow: EventNotFound) { @events.exist?(event_id) } || false
66
+ @events.exist?(event_id)
67
+ rescue Sequel::DatabaseError => doh
68
+ raise doh unless doh.message =~ /PG::InvalidTextRepresentation.*uuid/
69
+ false
72
70
  end
73
71
 
74
72
  def last_stream_event(stream)
75
- @events.last_stream_event(stream)
73
+ @events.last_stream_event(stream, @serializer)
76
74
  end
77
75
 
78
76
  def read(specification)
79
- raise ReservedInternalName if specification.stream.name.eql?(@stream_entries.stream_entries.class::SERIALIZED_GLOBAL_STREAM_NAME)
80
-
81
- @events.read(specification)
77
+ @events.read(specification, @serializer)
82
78
  end
83
79
 
84
80
  def count(specification)
85
- raise ReservedInternalName if specification.stream.name.eql?(@stream_entries.stream_entries.class::SERIALIZED_GLOBAL_STREAM_NAME)
86
-
87
81
  @events.count(specification)
88
82
  end
89
83
 
90
- def update_messages(messages)
91
- # Validate event IDs
92
- @events
93
- .find_nonexistent_pks(messages.map(&:event_id))
94
- .each { |id| raise EventNotFound, id }
84
+ def update_messages(records)
85
+ validate_event_ids(records.map(&:event_id))
95
86
 
96
- unit_of_work do |changesets|
97
- changesets << @events.update_changeset(messages)
87
+ @unit_of_work.call do |changesets|
88
+ serialized_records = records.map { |record| record.serialize(@serializer) }
89
+ changesets << @events.update_changeset(serialized_records)
98
90
  end
99
91
  end
100
92
 
101
93
  def streams_of(event_id)
102
- @stream_entries.streams_of(event_id)
103
- .map { |name| Stream.new(name) }
94
+ @stream_entries.streams_of(event_id).map { |name| Stream.new(name) }
95
+ end
96
+
97
+ private
98
+
99
+ def validate_event_ids(event_ids)
100
+ @events.find_nonexistent_pks(event_ids).each { |id| raise EventNotFound, id }
101
+ end
102
+
103
+ def handle_unique_violation
104
+ yield
105
+ rescue ::ROM::SQL::UniqueConstraintError, Sequel::UniqueConstraintViolation => doh
106
+ raise ::RubyEventStore::EventDuplicatedInStream if IndexViolationDetector.new.detect(doh.message)
107
+ raise ::RubyEventStore::WrongExpectedEventVersion
104
108
  end
105
109
  end
106
110
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module ROM
5
+ class IndexViolationDetector
6
+ MYSQL5_PKEY_ERROR = "for key 'index_event_store_events_on_event_id'".freeze
7
+ MYSQL8_PKEY_ERROR = "for key 'event_store_events.index_event_store_events_on_event_id'".freeze
8
+ POSTGRES_PKEY_ERROR = "Key (event_id)".freeze
9
+ SQLITE3_PKEY_ERROR = "event_store_events.event_id".freeze
10
+
11
+ MYSQL5_INDEX_ERROR = "for key 'index_event_store_events_in_streams_on_stream_and_event_id'".freeze
12
+ MYSQL8_INDEX_ERROR =
13
+ "for key 'event_store_events_in_streams.index_event_store_events_in_streams_on_stream_and_event_id'".freeze
14
+ POSTGRES_INDEX_ERROR = "Key (stream, event_id)".freeze
15
+ SQLITE3_INDEX_ERROR = "event_store_events_in_streams.stream, event_store_events_in_streams.event_id".freeze
16
+
17
+ def detect(message)
18
+ message.include?(MYSQL5_PKEY_ERROR) || message.include?(MYSQL8_PKEY_ERROR) ||
19
+ message.include?(POSTGRES_PKEY_ERROR) || message.include?(SQLITE3_PKEY_ERROR) ||
20
+ message.include?(MYSQL5_INDEX_ERROR) || message.include?(MYSQL8_INDEX_ERROR) ||
21
+ message.include?(POSTGRES_INDEX_ERROR) || message.include?(SQLITE3_INDEX_ERROR)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rom/transformer'
4
-
5
3
  module RubyEventStore
6
4
  module ROM
7
5
  module Mappers
@@ -9,10 +7,16 @@ module RubyEventStore
9
7
  relation :events
10
8
  register_as :event_to_serialized_record
11
9
 
12
- map_array do
13
- rename_keys id: :event_id
14
- accept_keys %i[event_id data metadata event_type]
15
- constructor_inject RubyEventStore::SerializedRecord
10
+ map do
11
+ map_value :created_at, ->(time) { time.iso8601(TIMESTAMP_PRECISION) }
12
+ map_value :valid_at, ->(time) { time.iso8601(TIMESTAMP_PRECISION) }
13
+ rename_keys created_at: :timestamp
14
+ accept_keys %i[event_id data metadata event_type timestamp valid_at]
15
+ create_serialized_record
16
+ end
17
+
18
+ def create_serialized_record(attributes)
19
+ RubyEventStore::SerializedRecord.new(**attributes)
16
20
  end
17
21
  end
18
22
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rom/transformer'
4
-
5
3
  module RubyEventStore
6
4
  module ROM
7
5
  module Mappers
@@ -9,10 +7,17 @@ module RubyEventStore
9
7
  relation :stream_entries
10
8
  register_as :stream_entry_to_serialized_record
11
9
 
12
- map_array do
13
- unwrap :event, %i[data metadata event_type]
14
- accept_keys %i[event_id data metadata event_type]
15
- constructor_inject RubyEventStore::SerializedRecord
10
+ map do
11
+ unwrap :event, %i[event_id data metadata event_type created_at valid_at]
12
+ map_value :created_at, ->(time) { time.iso8601(TIMESTAMP_PRECISION) }
13
+ map_value :valid_at, ->(time) { time.iso8601(TIMESTAMP_PRECISION) }
14
+ rename_keys created_at: :timestamp
15
+ accept_keys %i[event_id data metadata event_type timestamp valid_at]
16
+ create_serialized_record
17
+ end
18
+
19
+ def create_serialized_record(attributes)
20
+ RubyEventStore::SerializedRecord.new(**attributes)
16
21
  end
17
22
  end
18
23
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/sql/rake_task"
4
+ require "ruby_event_store/rom"
5
+ load "ruby_event_store/rom/tasks/migration_tasks.rake"
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module ROM
5
+ module Relations
6
+ class Events < ::ROM::Relation[:sql]
7
+ schema(:event_store_events, as: :events, infer: true) do
8
+ attribute :event_id, ::ROM::Types::String
9
+ attribute :data,
10
+ RubyEventStore::ROM::Types::RecordSerializer,
11
+ read: RubyEventStore::ROM::Types::RecordDeserializer
12
+ attribute :metadata,
13
+ RubyEventStore::ROM::Types::RecordSerializer,
14
+ read: RubyEventStore::ROM::Types::RecordDeserializer
15
+ attribute :created_at, RubyEventStore::ROM::Types::DateTime
16
+ attribute :valid_at, RubyEventStore::ROM::Types::DateTime
17
+
18
+ primary_key :event_id
19
+ end
20
+
21
+ def create_changeset(tuples)
22
+ events.changeset(Changesets::CreateEvents, tuples)
23
+ end
24
+
25
+ def update_changeset(tuples)
26
+ events.changeset(Changesets::UpdateEvents, tuples)
27
+ end
28
+
29
+ def by_event_id(event_id)
30
+ where(event_id: event_id)
31
+ end
32
+
33
+ def by_event_type(types)
34
+ where(event_type: types)
35
+ end
36
+
37
+ def newer_than(time)
38
+ where { |r| r.events[:created_at] > time.localtime }
39
+ end
40
+
41
+ def newer_than_or_equal(time)
42
+ where { |r| r.events[:created_at] >= time.localtime }
43
+ end
44
+
45
+ def older_than(time)
46
+ where { |r| r.events[:created_at] < time.localtime }
47
+ end
48
+
49
+ def older_than_or_equal(time)
50
+ where { |r| r.events[:created_at] <= time.localtime }
51
+ end
52
+
53
+ DIRECTION_MAP = { forward: %i[asc > <], backward: %i[desc < >] }.freeze
54
+
55
+ def ordered(direction, offset_entry_id = nil, stop_entry_id = nil, time_sort_by = nil)
56
+ order, operator_offset, operator_stop = DIRECTION_MAP[direction]
57
+
58
+ raise ArgumentError, "Direction must be :forward or :backward" if order.nil?
59
+
60
+ event_order_columns = [:id]
61
+
62
+ case time_sort_by
63
+ when :as_at
64
+ event_order_columns.unshift :created_at
65
+ when :as_of
66
+ event_order_columns.unshift :valid_at
67
+ end
68
+
69
+ query = self
70
+ query = query.where { id.public_send(operator_offset, offset_entry_id) } if offset_entry_id
71
+ query = query.where { id.public_send(operator_stop, stop_entry_id) } if stop_entry_id
72
+
73
+ query.order(*event_order_columns.map { |c| events[c].public_send(order) })
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module ROM
5
+ module Relations
6
+ class StreamEntries < ::ROM::Relation[:sql]
7
+ schema(:event_store_events_in_streams, as: :stream_entries, infer: true) do
8
+ attribute :created_at, RubyEventStore::ROM::Types::DateTime
9
+
10
+ associations { belongs_to :events, as: :event, foreign_key: :event_id }
11
+ end
12
+
13
+ def create_changeset(tuples)
14
+ changeset(ROM::Changesets::CreateStreamEntries, tuples)
15
+ end
16
+
17
+ def by_stream(stream)
18
+ where(stream: stream.name)
19
+ end
20
+
21
+ def by_event_id(event_id)
22
+ where(event_id: event_id)
23
+ end
24
+
25
+ def by_event_type(types)
26
+ join(:events).where(event_type: types)
27
+ end
28
+
29
+ def by_stream_and_event_id(stream, event_id)
30
+ where(stream: stream.name, event_id: event_id).one!
31
+ end
32
+
33
+ def max_position(stream)
34
+ by_stream(stream).select(:position).order(Sequel.desc(:position)).first
35
+ end
36
+
37
+ def newer_than(time)
38
+ join(:events).where { |r| r.events[:created_at] > time.localtime }
39
+ end
40
+
41
+ def newer_than_or_equal(time)
42
+ join(:events).where { |r| r.events[:created_at] >= time.localtime }
43
+ end
44
+
45
+ def older_than(time)
46
+ join(:events).where { |r| r.events[:created_at] < time.localtime }
47
+ end
48
+
49
+ def older_than_or_equal(time)
50
+ join(:events).where { |r| r.events[:created_at] <= time.localtime }
51
+ end
52
+
53
+ DIRECTION_MAP = { forward: %i[asc > <], backward: %i[desc < >] }.freeze
54
+
55
+ def ordered(direction, stream, offset_entry_id = nil, stop_entry_id = nil, time_sort_by = nil)
56
+ order, operator_offset, operator_stop = DIRECTION_MAP[direction]
57
+
58
+ raise ArgumentError, "Direction must be :forward or :backward" if order.nil?
59
+
60
+ event_order_columns = []
61
+ stream_order_columns = %i[id]
62
+
63
+ case time_sort_by
64
+ when :as_at
65
+ event_order_columns.unshift :created_at
66
+ when :as_of
67
+ event_order_columns.unshift :valid_at
68
+ end
69
+
70
+ query = by_stream(stream)
71
+ query = query.where { id.public_send(operator_offset, offset_entry_id) } if offset_entry_id
72
+ query = query.where { id.public_send(operator_stop, stop_entry_id) } if stop_entry_id
73
+
74
+ if event_order_columns.empty?
75
+ query.order { |r| stream_order_columns.map { |c| r[:stream_entries][c].public_send(order) } }
76
+ else
77
+ query.join(:events).order { |r| event_order_columns.map { |c| r.events[c].public_send(order) } }
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end