evt-message_store-postgres 0.1.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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/database/clear-events-table.sh +40 -0
  3. data/database/extensions.sql +1 -0
  4. data/database/functions/category.sql +10 -0
  5. data/database/functions/stream-version.sql +13 -0
  6. data/database/functions/write-event.sql +55 -0
  7. data/database/indexes/events-category-global-position.sql +1 -0
  8. data/database/indexes/events-category.sql +1 -0
  9. data/database/indexes/events-id.sql +1 -0
  10. data/database/indexes/events-stream-name-position-uniq.sql +1 -0
  11. data/database/indexes/events-stream-name.sql +1 -0
  12. data/database/install.sh +103 -0
  13. data/database/list-events.sh +52 -0
  14. data/database/table/events-table.sql +19 -0
  15. data/database/test/write-event-expected-version.sql +1 -0
  16. data/database/test/write-event.sql +1 -0
  17. data/database/uninstall.sh +60 -0
  18. data/lib/message_store/postgres.rb +24 -0
  19. data/lib/message_store/postgres/controls.rb +6 -0
  20. data/lib/message_store/postgres/controls/category.rb +34 -0
  21. data/lib/message_store/postgres/controls/message_data.rb +7 -0
  22. data/lib/message_store/postgres/controls/put.rb +26 -0
  23. data/lib/message_store/postgres/controls/stream_name.rb +22 -0
  24. data/lib/message_store/postgres/get.rb +91 -0
  25. data/lib/message_store/postgres/get/last.rb +111 -0
  26. data/lib/message_store/postgres/get/last/select_statement.rb +47 -0
  27. data/lib/message_store/postgres/get/select_statement.rb +88 -0
  28. data/lib/message_store/postgres/log.rb +11 -0
  29. data/lib/message_store/postgres/put.rb +145 -0
  30. data/lib/message_store/postgres/read.rb +12 -0
  31. data/lib/message_store/postgres/read/iterator.rb +17 -0
  32. data/lib/message_store/postgres/session.rb +128 -0
  33. data/lib/message_store/postgres/settings.rb +30 -0
  34. data/lib/message_store/postgres/stream_name.rb +52 -0
  35. data/lib/message_store/postgres/write.rb +46 -0
  36. data/scripts/evt-pg-create-db +7 -0
  37. data/scripts/evt-pg-delete-db +7 -0
  38. data/scripts/evt-pg-list-events +7 -0
  39. data/scripts/evt-pg-recreate-db +11 -0
  40. data/scripts/scripts_init.rb +12 -0
  41. metadata +156 -0
@@ -0,0 +1,7 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Controls
4
+ MessageData = MessageStore::Controls::MessageData
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,26 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Controls
4
+ module Put
5
+ def self.call(instances: nil, stream_name: nil, message: nil, category: nil)
6
+ instances ||= 1
7
+ stream_name ||= StreamName.example(category: category)
8
+
9
+ message_specified = !message.nil?
10
+
11
+ message ||= MessageData::Write.example
12
+
13
+ instances.times do
14
+ MessageStore::Postgres::Put.(message, stream_name)
15
+
16
+ unless message_specified
17
+ message.id = MessageData::Write.id
18
+ end
19
+ end
20
+
21
+ stream_name
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ # module MessageStore
2
+ # module Postgres
3
+ # module Controls
4
+ # StreamName = MessageStore::Controls::StreamName
5
+ # end
6
+ # end
7
+ # end
8
+
9
+ module MessageStore
10
+ module Postgres
11
+ module Controls
12
+ module StreamName
13
+ def self.example(category: nil, id: nil, type: nil, types: nil, randomize_category: nil)
14
+ category ||= Category.example(category: category, randomize_category: randomize_category)
15
+ id ||= Identifier::UUID.random
16
+
17
+ MessageStore::Postgres::StreamName.stream_name(category, id, type: type, types: types)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,91 @@
1
+ module MessageStore
2
+ module Postgres
3
+ class Get
4
+ include MessageStore::Get
5
+
6
+ initializer :batch_size
7
+
8
+ dependency :session, Session
9
+
10
+ def self.build(batch_size: nil, session: nil)
11
+ new(batch_size).tap do |instance|
12
+ instance.configure(session: session)
13
+ end
14
+ end
15
+
16
+ def self.configure(receiver, attr_name: nil, position: nil, batch_size: nil, session: nil)
17
+ attr_name ||= :get
18
+ instance = build(batch_size: batch_size, session: session)
19
+ receiver.public_send "#{attr_name}=", instance
20
+ end
21
+
22
+ def configure(session: nil)
23
+ Session.configure self, session: session
24
+ end
25
+
26
+ def self.call(stream_name, position: nil, batch_size: nil, session: nil)
27
+ instance = build(batch_size: batch_size, session: session)
28
+ instance.(stream_name, position: position)
29
+ end
30
+
31
+ def call(stream_name, position: nil)
32
+ logger.trace { "Getting message data (Position: #{position.inspect}, Stream Name: #{stream_name}, Batch Size: #{batch_size.inspect})" }
33
+
34
+ records = get_records(stream_name, position)
35
+
36
+ messages = convert(records)
37
+
38
+ logger.info { "Finished getting message data (Count: #{messages.length}, Position: #{position.inspect}, Stream Name: #{stream_name}, Batch Size: #{batch_size.inspect})" }
39
+ logger.info(tags: [:data, :message_data]) { messages.pretty_inspect }
40
+
41
+ messages
42
+ end
43
+
44
+ def get_records(stream_name, position)
45
+ logger.trace { "Getting records (Stream: #{stream_name}, Position: #{position.inspect}, Batch Size: #{batch_size.inspect})" }
46
+
47
+ select_statement = SelectStatement.build(stream_name, position: position, batch_size: batch_size)
48
+
49
+ records = session.execute(select_statement.sql)
50
+
51
+ logger.debug { "Finished getting records (Count: #{records.ntuples}, Stream: #{stream_name}, Position: #{position.inspect}, Batch Size: #{batch_size.inspect})" }
52
+
53
+ records
54
+ end
55
+
56
+ def convert(records)
57
+ logger.trace { "Converting records to message data (Records Count: #{records.ntuples})" }
58
+
59
+ messages = records.map do |record|
60
+ record['data'] = Deserialize.data(record['data'])
61
+ record['metadata'] = Deserialize.metadata(record['metadata'])
62
+ record['time'] = Time.utc_coerced(record['time'])
63
+
64
+ MessageData::Read.build record
65
+ end
66
+
67
+ logger.debug { "Converted records to message data (Message Data Count: #{messages.length})" }
68
+
69
+ messages
70
+ end
71
+
72
+ module Deserialize
73
+ def self.data(serialized_data)
74
+ return nil if serialized_data.nil?
75
+ Transform::Read.(serialized_data, MessageData::Hash, :json)
76
+ end
77
+
78
+ def self.metadata(serialized_metadata)
79
+ return nil if serialized_metadata.nil?
80
+ Transform::Read.(serialized_metadata, MessageData::Hash, :json)
81
+ end
82
+ end
83
+
84
+ module Time
85
+ def self.utc_coerced(local_time)
86
+ Clock::UTC.coerce(local_time)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,111 @@
1
+ module MessageStore
2
+ module Postgres
3
+ class Get
4
+ class Last
5
+ include Log::Dependency
6
+
7
+ dependency :session, Session
8
+
9
+ def self.build(session: nil)
10
+ new.tap do |instance|
11
+ instance.configure(session: session)
12
+ end
13
+ end
14
+
15
+ def self.configure(receiver, attr_name: nil, session: nil)
16
+ attr_name ||= :get_last
17
+ instance = build(session: session)
18
+ receiver.public_send "#{attr_name}=", instance
19
+ end
20
+
21
+ def configure(session: nil)
22
+ Session.configure self, session: session
23
+ end
24
+
25
+ def self.call(stream_name, session: nil)
26
+ instance = build(session: session)
27
+ instance.(stream_name)
28
+ end
29
+
30
+ def call(stream_name)
31
+ logger.trace { "Getting last message data (Stream Name: #{stream_name})" }
32
+
33
+ record = get_record(stream_name)
34
+
35
+ return nil if record.nil?
36
+
37
+ message_data = convert(record)
38
+
39
+ logger.info { "Finished getting message data (Stream Name: #{stream_name})" }
40
+ logger.info(tags: [:data, :message_data]) { message_data.pretty_inspect }
41
+
42
+ message_data
43
+ end
44
+
45
+ def get_record(stream_name)
46
+ logger.trace { "Getting last record (Stream: #{stream_name})" }
47
+
48
+ select_statement = SelectStatement.build(stream_name)
49
+
50
+ records = session.execute(select_statement.sql)
51
+
52
+ logger.debug { "Finished getting record (Stream: #{stream_name})" }
53
+
54
+ return nil if records.ntuples == 0
55
+
56
+ records[0]
57
+ end
58
+
59
+ def convert(record)
60
+ logger.trace { "Converting record to message data" }
61
+
62
+ record['data'] = Deserialize.data(record['data'])
63
+ record['metadata'] = Deserialize.metadata(record['metadata'])
64
+ record['time'] = Time.utc_coerced(record['time'])
65
+
66
+ message_data = MessageData::Read.build(record)
67
+
68
+ logger.debug { "Converted record to message data" }
69
+
70
+ message_data
71
+ end
72
+
73
+ def __convert(records)
74
+ logger.trace { "Converting records to message data (Records Count: #{records.ntuples})" }
75
+
76
+ messages = records.map do |record|
77
+ record['data'] = Deserialize.data(record['data'])
78
+ record['metadata'] = Deserialize.metadata(record['metadata'])
79
+ record['time'] = Time.utc_coerced(record['time'])
80
+
81
+ MessageData::Read.build record
82
+
83
+ break
84
+ end
85
+
86
+ logger.debug { "Converted records to message data (Message Data Count: #{messages.length})" }
87
+
88
+ messages
89
+ end
90
+
91
+ module Deserialize
92
+ def self.data(serialized_data)
93
+ return nil if serialized_data.nil?
94
+ Transform::Read.(serialized_data, MessageData::Hash, :json)
95
+ end
96
+
97
+ def self.metadata(serialized_metadata)
98
+ return nil if serialized_metadata.nil?
99
+ Transform::Read.(serialized_metadata, MessageData::Hash, :json)
100
+ end
101
+ end
102
+
103
+ module Time
104
+ def self.utc_coerced(local_time)
105
+ Clock::UTC.coerce(local_time)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,47 @@
1
+ module MessageStore
2
+ module Postgres
3
+ class Get
4
+ class Last
5
+ class SelectStatement
6
+ include Log::Dependency
7
+
8
+ initializer :stream_name
9
+
10
+ def self.build(stream_name)
11
+ new(stream_name)
12
+ end
13
+
14
+ def sql
15
+ logger.trace(tag: :sql) { "Composing select statement (Stream: #{stream_name})" }
16
+
17
+ statement = <<-SQL
18
+ SELECT
19
+ id::varchar,
20
+ stream_name::varchar,
21
+ position::int,
22
+ type::varchar,
23
+ global_position::bigint,
24
+ data::varchar,
25
+ metadata::varchar,
26
+ time::timestamp
27
+ FROM
28
+ events
29
+ WHERE
30
+ stream_name = '#{stream_name}'
31
+ ORDER BY
32
+ position DESC
33
+ LIMIT
34
+ 1
35
+ ;
36
+ SQL
37
+
38
+ logger.debug(tag: :sql) { "Composed select statement (Stream: #{stream_name})" }
39
+ logger.debug(tags: [:data, :sql]) { "Statement: #{statement}" }
40
+
41
+ statement
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,88 @@
1
+ module MessageStore
2
+ module Postgres
3
+ class Get
4
+ class SelectStatement
5
+ include Log::Dependency
6
+
7
+ initializer :stream_name, w(:position), w(:batch_size)
8
+
9
+ def position
10
+ @position ||= Defaults.position
11
+ end
12
+
13
+ def batch_size
14
+ @batch_size ||= Defaults.batch_size
15
+ end
16
+
17
+ def stream_type_list
18
+ @stream_type ||= StreamName.get_type_list(stream_name)
19
+ end
20
+
21
+ def category_stream?
22
+ is_category_stream ||= StreamName.category?(stream_name)
23
+ end
24
+
25
+ def self.build(stream_name, position: nil, batch_size: nil)
26
+ new(stream_name, position, batch_size)
27
+ end
28
+
29
+ def sql
30
+ logger.trace(tag: :sql) { "Composing select statement (Stream: #{stream_name}, Category: #{category_stream?}, Types: #{stream_type_list.inspect}, Position: #{position}, Batch Size: #{batch_size})" }
31
+
32
+ statement = <<-SQL
33
+ SELECT
34
+ id::varchar,
35
+ stream_name::varchar,
36
+ position::int,
37
+ type::varchar,
38
+ global_position::bigint,
39
+ data::varchar,
40
+ metadata::varchar,
41
+ time::timestamp
42
+ FROM
43
+ events
44
+ WHERE
45
+ #{where_clause_field} = '#{stream_name}' AND
46
+ #{position_field} >= #{position}
47
+ ORDER BY
48
+ #{position_field} ASC
49
+ LIMIT
50
+ #{batch_size}
51
+ ;
52
+ SQL
53
+
54
+ logger.debug(tag: :sql) { "Composed select statement (Stream: #{stream_name}, Category: #{category_stream?}, Types: #{stream_type_list.inspect}, Position: #{position}, Batch Size: #{batch_size})" }
55
+ logger.debug(tags: [:data, :sql]) { "Statement: #{statement}" }
56
+
57
+ statement
58
+ end
59
+
60
+ def where_clause_field
61
+ unless category_stream?
62
+ 'stream_name'
63
+ else
64
+ 'category(stream_name)'
65
+ end
66
+ end
67
+
68
+ def position_field
69
+ unless category_stream?
70
+ 'position'
71
+ else
72
+ 'global_position'
73
+ end
74
+ end
75
+
76
+ module Defaults
77
+ def self.position
78
+ 0
79
+ end
80
+
81
+ def self.batch_size
82
+ 1000
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,11 @@
1
+ module MessageStore
2
+ module Postgres
3
+ class Log < ::Log
4
+ def tag!(tags)
5
+ tags << :message_store_postgres
6
+ tags << :library
7
+ tags << :verbose
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,145 @@
1
+ module MessageStore
2
+ module Postgres
3
+ class Put
4
+ include Log::Dependency
5
+
6
+ dependency :session, Session
7
+ dependency :identifier, Session
8
+
9
+ def self.build(session: nil)
10
+ new.tap do |instance|
11
+ instance.configure(session: session)
12
+ end
13
+ end
14
+
15
+ def configure(session: nil)
16
+ Session.configure(self, session: session)
17
+ Identifier::UUID::Random.configure(self)
18
+ end
19
+
20
+ def self.configure(receiver, session: nil, attr_name: nil)
21
+ attr_name ||= :put
22
+ instance = build(session: session)
23
+ receiver.public_send "#{attr_name}=", instance
24
+ end
25
+
26
+ def self.call(write_message, stream_name, expected_version: nil, session: nil)
27
+ instance = build(session: session)
28
+ instance.(write_message, stream_name, expected_version: expected_version)
29
+ end
30
+
31
+ def call(write_message, stream_name, expected_version: nil)
32
+ logger.trace { "Putting message data (Stream Name: #{stream_name}, Type: #{write_message.type}, Expected Version: #{expected_version.inspect})" }
33
+ logger.trace(tags: [:data, :message_data]) { write_message.pretty_inspect }
34
+
35
+ write_message.id ||= identifier.get
36
+
37
+ id, type, data, metadata = destructure_message(write_message)
38
+ expected_version = ExpectedVersion.canonize(expected_version)
39
+
40
+ insert_message(id, stream_name, type, data, metadata, expected_version).tap do |position|
41
+ logger.info { "Put message data (Position: #{position}, Stream Name: #{stream_name}, Type: #{write_message.type}, Expected Version: #{expected_version.inspect}, ID: #{id.inspect})" }
42
+ logger.info(tags: [:data, :message_data]) { write_message.pretty_inspect }
43
+ end
44
+ end
45
+
46
+ def destructure_message(write_message)
47
+ id = write_message.id
48
+ type = write_message.type
49
+ data = write_message.data
50
+ metadata = write_message.metadata
51
+
52
+ logger.debug(tags: [:data, :message_data]) { "ID: #{id.pretty_inspect}" }
53
+ logger.debug(tags: [:data, :message_data]) { "Type: #{type.pretty_inspect}" }
54
+ logger.debug(tags: [:data, :message_data]) { "Data: #{data.pretty_inspect}" }
55
+ logger.debug(tags: [:data, :message_data]) { "Metadata: #{metadata.pretty_inspect}" }
56
+
57
+ return id, type, data, metadata
58
+ end
59
+
60
+ def insert_message(id, stream_name, type, data, metadata, expected_version)
61
+ serialized_data = serialized_data(data)
62
+ serialized_metadata = serialized_metadata(metadata)
63
+ records = execute_query(id, stream_name, type, serialized_data, serialized_metadata, expected_version)
64
+ position(records)
65
+ end
66
+
67
+ def execute_query(id, stream_name, type, serialized_data, serialized_metadata, expected_version)
68
+ logger.trace { "Executing insert (Stream Name: #{stream_name}, Type: #{type}, Expected Version: #{expected_version.inspect}, ID: #{id.inspect})" }
69
+
70
+ params = [
71
+ id,
72
+ stream_name,
73
+ type,
74
+ serialized_data,
75
+ serialized_metadata,
76
+ expected_version
77
+ ]
78
+
79
+ begin
80
+ records = session.execute(self.class.statement, params)
81
+ rescue PG::RaiseException => e
82
+ raise_error e
83
+ end
84
+
85
+ logger.debug { "Executed insert (Stream Name: #{stream_name}, Type: #{type}, Expected Version: #{expected_version.inspect}, ID: #{id.inspect})" }
86
+
87
+ records
88
+ end
89
+
90
+ def self.statement
91
+ @statement ||= "SELECT write_event($1::varchar, $2::varchar, $3::varchar, $4::jsonb, $5::jsonb, $6::int);"
92
+ end
93
+
94
+ def serialized_data(data)
95
+ serialized_data = nil
96
+
97
+ if data.is_a?(Hash) && data.empty?
98
+ data = nil
99
+ end
100
+
101
+ unless data.nil?
102
+ serializable_data = MessageData::Hash[data]
103
+ serialized_data = Transform::Write.(serializable_data, :json)
104
+ end
105
+
106
+ logger.debug(tags: [:data, :serialize]) { "Serialized Data: #{serialized_data.inspect}" }
107
+ serialized_data
108
+ end
109
+
110
+ def serialized_metadata(metadata)
111
+ serialized_metadata = nil
112
+
113
+ if metadata.is_a?(Hash) && metadata.empty?
114
+ metadata = nil
115
+ end
116
+
117
+ unless metadata.nil?
118
+ serializable_metadata = MessageData::Hash[metadata]
119
+ serialized_metadata = Transform::Write.(serializable_metadata, :json)
120
+ end
121
+
122
+ logger.debug(tags: [:data, :serialize]) { "Serialized Metadata: #{serialized_metadata.inspect}" }
123
+ serialized_metadata
124
+ end
125
+
126
+ def position(records)
127
+ position = nil
128
+ unless records[0].nil?
129
+ position = records[0].values[0]
130
+ end
131
+ position
132
+ end
133
+
134
+ def raise_error(pg_error)
135
+ error_message = pg_error.message
136
+ if error_message.include? 'Wrong expected version'
137
+ error_message.gsub!('ERROR:', '').strip!
138
+ logger.error { error_message }
139
+ raise ExpectedVersion::Error, error_message
140
+ end
141
+ raise pg_error
142
+ end
143
+ end
144
+ end
145
+ end