evt-message_store-postgres 0.1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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