ruby_event_store-rom 0.35.0 → 0.36.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +84 -0
- data/Gemfile +4 -4
- data/Makefile +3 -0
- data/Rakefile +3 -3
- data/db/migrate/20180327044629_create_ruby_event_store_tables.rb +17 -8
- data/lib/ruby_event_store/rom.rb +19 -16
- data/lib/ruby_event_store/rom/adapters/memory/changesets/create_events.rb +17 -0
- data/lib/ruby_event_store/rom/adapters/memory/changesets/create_stream_entries.rb +17 -0
- data/lib/ruby_event_store/rom/adapters/memory/changesets/update_events.rb +16 -0
- data/lib/ruby_event_store/rom/adapters/memory/relations/events.rb +14 -5
- data/lib/ruby_event_store/rom/adapters/memory/relations/stream_entries.rb +8 -4
- data/lib/ruby_event_store/rom/adapters/memory/unit_of_work.rb +6 -21
- data/lib/ruby_event_store/rom/adapters/sql/changesets/create_events.rb +13 -0
- data/lib/ruby_event_store/rom/adapters/sql/changesets/update_events.rb +39 -0
- data/lib/ruby_event_store/rom/adapters/sql/index_violation_detector.rb +15 -16
- data/lib/ruby_event_store/rom/adapters/sql/relations/events.rb +13 -1
- data/lib/ruby_event_store/rom/adapters/sql/relations/stream_entries.rb +8 -4
- data/lib/ruby_event_store/rom/adapters/sql/tasks/migration_tasks.rake +16 -3
- data/lib/ruby_event_store/rom/changesets/create_events.rb +29 -0
- data/lib/ruby_event_store/rom/changesets/create_stream_entries.rb +21 -0
- data/lib/ruby_event_store/rom/changesets/update_events.rb +29 -0
- data/lib/ruby_event_store/rom/event_repository.rb +16 -6
- data/lib/ruby_event_store/rom/mappers/event_to_serialized_record.rb +1 -1
- data/lib/ruby_event_store/rom/mappers/stream_entry_to_serialized_record.rb +1 -1
- data/lib/ruby_event_store/rom/memory.rb +15 -3
- data/lib/ruby_event_store/rom/repositories/events.rb +18 -30
- data/lib/ruby_event_store/rom/repositories/stream_entries.rb +17 -18
- data/lib/ruby_event_store/rom/sql.rb +62 -12
- data/lib/ruby_event_store/rom/types.rb +13 -0
- data/lib/ruby_event_store/rom/unit_of_work.rb +1 -1
- data/lib/ruby_event_store/rom/version.rb +1 -1
- data/lib/ruby_event_store/spec/rom/event_repository_lint.rb +55 -90
- data/lib/ruby_event_store/spec/rom/relations/events_lint.rb +12 -12
- data/lib/ruby_event_store/spec/rom/relations/stream_entries_lint.rb +44 -44
- data/lib/ruby_event_store/spec/rom/spec_helper_lint.rb +1 -1
- data/lib/ruby_event_store/spec/rom/unit_of_work_lint.rb +1 -1
- data/ruby_event_store-rom.gemspec +12 -13
- metadata +40 -31
- data/lib/ruby_event_store/rom/adapters/sql/unit_of_work.rb +0 -37
@@ -0,0 +1,39 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
module ROM
|
3
|
+
module SQL
|
4
|
+
module Changesets
|
5
|
+
class UpdateEvents < ::ROM::Changeset::Create
|
6
|
+
include ROM::Changesets::UpdateEvents::Defaults
|
7
|
+
|
8
|
+
UPSERT_COLUMNS = %i[event_type data metadata created_at]
|
9
|
+
|
10
|
+
def commit
|
11
|
+
if SQL.supports_on_duplicate_key_update?(relation.dataset.db)
|
12
|
+
commit_on_duplicate_key_update
|
13
|
+
elsif SQL.supports_insert_conflict_update?(relation.dataset.db)
|
14
|
+
commit_insert_conflict_update
|
15
|
+
else
|
16
|
+
raise "Database doesn't support upserts: #{relation.dataset.db.adapter_scheme}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def commit_on_duplicate_key_update
|
23
|
+
relation.dataset.on_duplicate_key_update(*UPSERT_COLUMNS).multi_insert(to_a)
|
24
|
+
end
|
25
|
+
|
26
|
+
def commit_insert_conflict_update
|
27
|
+
relation.dataset.insert_conflict(
|
28
|
+
# constraint: 'index_name',
|
29
|
+
target: :id,
|
30
|
+
update: UPSERT_COLUMNS.each_with_object({}) do |column, memo|
|
31
|
+
memo[column] = Sequel[:excluded][column]
|
32
|
+
end
|
33
|
+
).multi_insert(to_a)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -2,23 +2,22 @@ module RubyEventStore
|
|
2
2
|
module ROM
|
3
3
|
module SQL
|
4
4
|
class IndexViolationDetector
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
MYSQL_PKEY_ERROR = "for key 'PRIMARY'".freeze
|
6
|
+
POSTGRES_PKEY_ERROR = 'event_store_events_pkey'.freeze
|
7
|
+
SQLITE3_PKEY_ERROR = 'event_store_events.id'.freeze
|
8
|
+
|
9
|
+
MYSQL_INDEX_ERROR = "for key 'index_event_store_events_in_streams_on_stream_and_event_id'".freeze
|
10
|
+
POSTGRES_INDEX_ERROR = 'Key (stream, event_id)'.freeze
|
11
|
+
SQLITE3_INDEX_ERROR = 'event_store_events_in_streams.stream, event_store_events_in_streams.event_id'.freeze
|
12
|
+
|
14
13
|
def detect(message)
|
15
|
-
message.include?(MYSQL_PKEY_ERROR)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
message.include?(MYSQL_PKEY_ERROR) ||
|
15
|
+
message.include?(POSTGRES_PKEY_ERROR) ||
|
16
|
+
message.include?(SQLITE3_PKEY_ERROR) ||
|
17
|
+
|
18
|
+
message.include?(MYSQL_INDEX_ERROR) ||
|
19
|
+
message.include?(POSTGRES_INDEX_ERROR) ||
|
20
|
+
message.include?(SQLITE3_INDEX_ERROR)
|
22
21
|
end
|
23
22
|
end
|
24
23
|
end
|
@@ -4,7 +4,19 @@ module RubyEventStore
|
|
4
4
|
module Relations
|
5
5
|
class Events < ::ROM::Relation[:sql]
|
6
6
|
schema(:event_store_events, as: :events, infer: true) do
|
7
|
-
attribute :
|
7
|
+
attribute :data, RubyEventStore::ROM::Types::SerializedRecordSerializer,
|
8
|
+
read: RubyEventStore::ROM::Types::SerializedRecordDeserializer
|
9
|
+
attribute :metadata, RubyEventStore::ROM::Types::SerializedRecordSerializer,
|
10
|
+
read: RubyEventStore::ROM::Types::SerializedRecordDeserializer
|
11
|
+
attribute :created_at, RubyEventStore::ROM::Types::DateTime
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_changeset(tuples)
|
15
|
+
events.changeset(Changesets::CreateEvents, tuples)
|
16
|
+
end
|
17
|
+
|
18
|
+
def update_changeset(tuples)
|
19
|
+
events.changeset(Changesets::UpdateEvents, tuples)
|
8
20
|
end
|
9
21
|
end
|
10
22
|
end
|
@@ -4,17 +4,21 @@ module RubyEventStore
|
|
4
4
|
module Relations
|
5
5
|
class StreamEntries < ::ROM::Relation[:sql]
|
6
6
|
schema(:event_store_events_in_streams, as: :stream_entries, infer: true) do
|
7
|
-
attribute :created_at, ::ROM::Types::
|
7
|
+
attribute :created_at, RubyEventStore::ROM::Types::DateTime
|
8
8
|
|
9
9
|
associations do
|
10
10
|
belongs_to :events, as: :event, foreign_key: :event_id
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
|
14
|
+
alias take limit
|
15
15
|
|
16
16
|
SERIALIZED_GLOBAL_STREAM_NAME = 'all'.freeze
|
17
17
|
|
18
|
+
def create_changeset(tuples)
|
19
|
+
changeset(ROM::Changesets::CreateStreamEntries, tuples)
|
20
|
+
end
|
21
|
+
|
18
22
|
def by_stream(stream)
|
19
23
|
where(stream: normalize_stream_name(stream))
|
20
24
|
end
|
@@ -36,8 +40,8 @@ module RubyEventStore
|
|
36
40
|
end
|
37
41
|
|
38
42
|
DIRECTION_MAP = {
|
39
|
-
forward:
|
40
|
-
backward: [
|
43
|
+
forward: %i[asc >],
|
44
|
+
backward: %i[desc <]
|
41
45
|
}.freeze
|
42
46
|
|
43
47
|
def ordered(direction, stream, offset_entry_id = nil)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'ruby_event_store/rom/sql'
|
2
2
|
|
3
|
-
MIGRATIONS_PATH = 'db/migrate'
|
3
|
+
MIGRATIONS_PATH = 'db/migrate'.freeze
|
4
4
|
|
5
5
|
desc 'Setup ROM EventRespository environment'
|
6
6
|
task 'db:setup' do
|
@@ -10,11 +10,24 @@ end
|
|
10
10
|
|
11
11
|
desc 'Copy RubyEventStore SQL migrations to db/migrate'
|
12
12
|
task 'db:migrations:copy' => 'db:setup' do
|
13
|
+
# Optional data type for `data` and `metadata`
|
14
|
+
data_type = ENV['DATA_TYPE']
|
15
|
+
|
13
16
|
Dir[File.join(File.dirname(__FILE__), '../../../../../../', MIGRATIONS_PATH, '/*.rb')].each do |input|
|
14
|
-
|
17
|
+
contents = File.read(input)
|
18
|
+
name = File.basename(input, '.*').sub(/\d+_/, '')
|
19
|
+
|
20
|
+
re_data_type = /(ENV.+?DATA_TYPE.+?\|\|=\s*)['"](jsonb?|text)['"]/
|
21
|
+
|
22
|
+
if data_type && contents =~ re_data_type
|
23
|
+
# Search/replace this string: ENV['DATA_TYPE'] ||= 'text'
|
24
|
+
contents = contents.sub(re_data_type, format('\1"%<data_type>s"', data_type: data_type))
|
25
|
+
name += "_with_#{data_type}"
|
26
|
+
end
|
27
|
+
|
15
28
|
output = ROM::SQL::RakeSupport.create_migration(name, path: File.join(Dir.pwd, MIGRATIONS_PATH))
|
16
29
|
|
17
|
-
File.write output,
|
30
|
+
File.write output, contents
|
18
31
|
|
19
32
|
puts "<= migration file created #{output}"
|
20
33
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
module ROM
|
3
|
+
module Changesets
|
4
|
+
class CreateEvents < ::ROM::Changeset::Create
|
5
|
+
module Defaults
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
relation :events
|
9
|
+
|
10
|
+
# Convert to Hash
|
11
|
+
map(&:to_h)
|
12
|
+
|
13
|
+
map do
|
14
|
+
rename_keys event_id: :id
|
15
|
+
accept_keys %i[id data metadata event_type]
|
16
|
+
end
|
17
|
+
|
18
|
+
map do |tuple|
|
19
|
+
Hash(created_at: RubyEventStore::ROM::Types::DateTime.call(nil)).merge(tuple)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
include Defaults
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
module ROM
|
3
|
+
module Changesets
|
4
|
+
class CreateStreamEntries < ::ROM::Changeset::Create
|
5
|
+
module Defaults
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
relation :stream_entries
|
9
|
+
|
10
|
+
map do |tuple|
|
11
|
+
Hash(created_at: RubyEventStore::ROM::Types::DateTime.call(nil)).merge(tuple)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
include Defaults
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RubyEventStore
|
2
|
+
module ROM
|
3
|
+
module Changesets
|
4
|
+
class UpdateEvents < ::ROM::Changeset::Update
|
5
|
+
module Defaults
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
relation :events
|
9
|
+
|
10
|
+
# Convert to Hash
|
11
|
+
map(&:to_h)
|
12
|
+
|
13
|
+
map do
|
14
|
+
rename_keys event_id: :id
|
15
|
+
accept_keys %i[id data metadata event_type created_at]
|
16
|
+
end
|
17
|
+
|
18
|
+
map do |tuple|
|
19
|
+
Hash(created_at: RubyEventStore::ROM::Types::DateTime.call(nil)).merge(tuple)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
include Defaults
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -10,7 +10,7 @@ module RubyEventStore
|
|
10
10
|
def_delegators :@rom, :unit_of_work
|
11
11
|
|
12
12
|
def initialize(rom: ROM.env)
|
13
|
-
raise ArgumentError,
|
13
|
+
raise ArgumentError, 'Must specify rom' unless rom && rom.instance_of?(Env)
|
14
14
|
|
15
15
|
@rom = rom
|
16
16
|
@events = Repositories::Events.new(rom.container)
|
@@ -46,7 +46,7 @@ module RubyEventStore
|
|
46
46
|
# Validate event IDs
|
47
47
|
@events
|
48
48
|
.find_nonexistent_pks(event_ids)
|
49
|
-
.each { |id| raise EventNotFound
|
49
|
+
.each { |id| raise EventNotFound, id }
|
50
50
|
|
51
51
|
guard_for(:unique_violation) do
|
52
52
|
unit_of_work do |changesets|
|
@@ -66,9 +66,7 @@ module RubyEventStore
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def has_event?(event_id)
|
69
|
-
|
70
|
-
@events.exist?(event_id)
|
71
|
-
end
|
69
|
+
guard_for(:not_found, event_id, swallow: EventNotFound) { @events.exist?(event_id) } || false
|
72
70
|
end
|
73
71
|
|
74
72
|
def last_stream_event(stream)
|
@@ -87,15 +85,27 @@ module RubyEventStore
|
|
87
85
|
@events.count(specification)
|
88
86
|
end
|
89
87
|
|
88
|
+
def update_messages(messages)
|
89
|
+
# Validate event IDs
|
90
|
+
@events
|
91
|
+
.find_nonexistent_pks(messages.map(&:event_id))
|
92
|
+
.each { |id| raise EventNotFound, id }
|
93
|
+
|
94
|
+
unit_of_work do |changesets|
|
95
|
+
changesets << @events.update_changeset(messages)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
90
99
|
def streams_of(event_id)
|
91
100
|
@stream_entries.streams_of(event_id)
|
92
|
-
|
101
|
+
.map { |name| Stream.new(name) }
|
93
102
|
end
|
94
103
|
|
95
104
|
private
|
96
105
|
|
97
106
|
def normalize_to_array(events)
|
98
107
|
return events if events.is_a?(Enumerable)
|
108
|
+
|
99
109
|
[events]
|
100
110
|
end
|
101
111
|
end
|
@@ -6,7 +6,7 @@ module RubyEventStore
|
|
6
6
|
class StreamEntryToSerializedRecord < ::ROM::Transformer
|
7
7
|
relation :stream_entries
|
8
8
|
register_as :stream_entry_to_serialized_record
|
9
|
-
|
9
|
+
|
10
10
|
map_array do
|
11
11
|
unwrap :event, %i[data metadata event_type]
|
12
12
|
accept_keys %i[event_id data metadata event_type]
|
@@ -3,6 +3,9 @@ require 'rom/memory'
|
|
3
3
|
require_relative 'adapters/memory/unit_of_work'
|
4
4
|
require_relative 'adapters/memory/relations/events'
|
5
5
|
require_relative 'adapters/memory/relations/stream_entries'
|
6
|
+
require_relative 'adapters/memory/changesets/create_events'
|
7
|
+
require_relative 'adapters/memory/changesets/update_events'
|
8
|
+
require_relative 'adapters/memory/changesets/create_stream_entries'
|
6
9
|
|
7
10
|
module RubyEventStore
|
8
11
|
module ROM
|
@@ -22,10 +25,11 @@ module RubyEventStore
|
|
22
25
|
def configure(env)
|
23
26
|
env.register_unit_of_work_options(class: UnitOfWork)
|
24
27
|
|
25
|
-
env.register_error_handler :unique_violation,
|
28
|
+
env.register_error_handler :unique_violation, lambda { |ex|
|
26
29
|
case ex
|
27
30
|
when TupleUniquenessError
|
28
31
|
raise EventDuplicatedInStream if ex.message =~ /event_id/
|
32
|
+
|
29
33
|
raise WrongExpectedEventVersion
|
30
34
|
end
|
31
35
|
}
|
@@ -35,7 +39,7 @@ module RubyEventStore
|
|
35
39
|
class SpecHelper
|
36
40
|
attr_reader :env
|
37
41
|
attr_reader :connection_pool_size, :close_pool_connection
|
38
|
-
|
42
|
+
|
39
43
|
def initialize
|
40
44
|
@connection_pool_size = 5
|
41
45
|
@env = ROM.setup(:memory)
|
@@ -54,14 +58,22 @@ module RubyEventStore
|
|
54
58
|
def drop_gateway_schema
|
55
59
|
gateway.connection.data.values.each { |v| v.data.clear }
|
56
60
|
end
|
57
|
-
|
61
|
+
|
58
62
|
def close_gateway_connection
|
59
63
|
gateway.disconnect
|
60
64
|
end
|
61
65
|
|
66
|
+
def gateway_type?(name)
|
67
|
+
name == :memory
|
68
|
+
end
|
69
|
+
|
62
70
|
def has_connection_pooling?
|
63
71
|
true
|
64
72
|
end
|
73
|
+
|
74
|
+
def supports_upsert?
|
75
|
+
true
|
76
|
+
end
|
65
77
|
end
|
66
78
|
end
|
67
79
|
end
|
@@ -1,29 +1,22 @@
|
|
1
1
|
require_relative '../mappers/event_to_serialized_record'
|
2
|
+
require_relative '../changesets/create_events'
|
3
|
+
require_relative '../changesets/update_events'
|
2
4
|
|
3
5
|
module RubyEventStore
|
4
6
|
module ROM
|
5
7
|
module Repositories
|
6
8
|
class Events < ::ROM::Repository[:events]
|
7
|
-
|
8
|
-
|
9
|
-
map(&:to_h)
|
10
|
-
|
11
|
-
map do
|
12
|
-
rename_keys event_id: :id
|
13
|
-
accept_keys %i[id data metadata event_type]
|
14
|
-
end
|
15
|
-
|
16
|
-
map do |tuple|
|
17
|
-
Hash(created_at: Time.now).merge(tuple)
|
18
|
-
end
|
9
|
+
def create_changeset(serialized_records)
|
10
|
+
events.create_changeset(serialized_records)
|
19
11
|
end
|
20
12
|
|
21
|
-
def
|
22
|
-
events.
|
13
|
+
def update_changeset(serialized_records)
|
14
|
+
events.update_changeset(serialized_records)
|
23
15
|
end
|
24
16
|
|
25
17
|
def find_nonexistent_pks(event_ids)
|
26
18
|
return event_ids unless event_ids.any?
|
19
|
+
|
27
20
|
event_ids - events.by_pk(event_ids).pluck(:id)
|
28
21
|
end
|
29
22
|
|
@@ -45,21 +38,19 @@ module RubyEventStore
|
|
45
38
|
query = read_scope(specification)
|
46
39
|
|
47
40
|
if specification.batched?
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
41
|
+
BatchEnumerator.new(
|
42
|
+
specification.batch_size,
|
43
|
+
specification.limit,
|
44
|
+
->(offset, limit) { query_builder(query, offset: offset, limit: limit).to_ary }
|
45
|
+
).each
|
52
46
|
else
|
53
|
-
|
54
|
-
query = query_builder(query, limit: limit)
|
47
|
+
query = query_builder(query, limit: (specification.limit if specification.limit?))
|
55
48
|
if specification.head?
|
56
49
|
specification.first? || specification.last? ? query.first : query.each
|
50
|
+
elsif specification.last?
|
51
|
+
query.to_ary.last
|
57
52
|
else
|
58
|
-
|
59
|
-
query.to_ary.last
|
60
|
-
else
|
61
|
-
specification.first? ? query.first : query.each
|
62
|
-
end
|
53
|
+
specification.first? ? query.first : query.each
|
63
54
|
end
|
64
55
|
end
|
65
56
|
end
|
@@ -70,12 +61,10 @@ module RubyEventStore
|
|
70
61
|
query.count
|
71
62
|
end
|
72
63
|
|
73
|
-
|
64
|
+
protected
|
74
65
|
|
75
66
|
def read_scope(specification)
|
76
|
-
unless specification.head?
|
77
|
-
offset_entry_id = stream_entries.by_stream_and_event_id(specification.stream, specification.start).fetch(:id)
|
78
|
-
end
|
67
|
+
offset_entry_id = stream_entries.by_stream_and_event_id(specification.stream, specification.start).fetch(:id) unless specification.head?
|
79
68
|
|
80
69
|
direction = specification.forward? ? :forward : :backward
|
81
70
|
|
@@ -84,7 +73,6 @@ module RubyEventStore
|
|
84
73
|
end
|
85
74
|
|
86
75
|
query = stream_entries.ordered(direction, specification.stream, offset_entry_id)
|
87
|
-
|
88
76
|
query = query.by_event_id(specification.with_ids) if specification.with_ids
|
89
77
|
query = query.by_event_type(specification.with_types) if specification.with_types?
|
90
78
|
query
|