evt-message_store-postgres 2.4.0.5

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7898e559251e6013c71e09ad745cacecca46680f54f279d06eb7e4801d5f4fdc
4
+ data.tar.gz: 251012903cf9814e62eac3db90eee0f9d47467759017d53f81238dac33b0ba32
5
+ SHA512:
6
+ metadata.gz: abb2182486ece01a11d7b15866275f4f7872419cb7e966cf9b5e850f717286efb10342466d3ce873072f7f622c0017a2a7bcba41d89495562fad336885fb0156
7
+ data.tar.gz: 4e7dd8ca3ff0fb4d924bf3fd552f7e00ae14b87d06703ea1a44cdd2f2e98a490d9439f5fa0274cca0a6995e1c79e7be896a57d9c4063b11cffa9b512b2550a46
@@ -0,0 +1,24 @@
1
+ require 'pg'
2
+
3
+ require 'message_store'
4
+
5
+ require 'log'
6
+ require 'telemetry'
7
+ require 'settings'
8
+
9
+ require 'message_store/postgres/log'
10
+
11
+ require 'message_store/postgres/settings'
12
+ require 'message_store/postgres/session'
13
+
14
+ require 'message_store/postgres/put'
15
+ require 'message_store/postgres/write'
16
+
17
+ require 'message_store/postgres/get'
18
+ require 'message_store/postgres/get/condition'
19
+ require 'message_store/postgres/get/stream'
20
+ require 'message_store/postgres/get/stream/last'
21
+ require 'message_store/postgres/get/category'
22
+ require 'message_store/postgres/get/category/correlation'
23
+ require 'message_store/postgres/get/category/consumer_group'
24
+ require 'message_store/postgres/read'
@@ -0,0 +1,7 @@
1
+ require 'message_store/controls'
2
+
3
+ require 'message_store/postgres/controls/position'
4
+ require 'message_store/postgres/controls/category'
5
+ require 'message_store/postgres/controls/stream_name'
6
+ require 'message_store/postgres/controls/message_data'
7
+ require 'message_store/postgres/controls/put'
@@ -0,0 +1,7 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Controls
4
+ Category = MessageStore::Controls::Category
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Controls
4
+ MessageData = MessageStore::Controls::MessageData
5
+
6
+ module MessageData
7
+ module Write
8
+ module List
9
+ Entry = Struct.new(:stream_name, :category, :message_data)
10
+
11
+ def self.get(instances: nil, stream_name: nil, category: nil)
12
+ instances ||= 1
13
+
14
+ list = []
15
+ instances.times do
16
+ instance_stream_name = stream_name || StreamName.example(category: category)
17
+ instance_category = MessageStore::StreamName.get_category(instance_stream_name)
18
+
19
+ write_message = Controls::MessageData::Write.example
20
+
21
+ list << Entry.new(instance_stream_name, instance_category, write_message)
22
+ end
23
+
24
+ list
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Controls
4
+ module Position
5
+ def self.example
6
+ 1
7
+ end
8
+
9
+ def self.max
10
+ (2 ** 63) - 1
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Controls
4
+ module Put
5
+ def self.call(instances: nil, stream_name: nil, message_data: nil, message: nil, category: nil)
6
+ instances ||= 1
7
+ stream_name ||= StreamName.example(category: category)
8
+ message_data ||= message
9
+
10
+ message_specified = !message_data.nil?
11
+
12
+ message_data ||= MessageData::Write.example
13
+
14
+ position = nil
15
+ instances.times do
16
+ position = MessageStore::Postgres::Put.(message_data, stream_name)
17
+
18
+ unless message_specified
19
+ message_data.id = MessageData::Write.id
20
+ end
21
+ end
22
+
23
+ [stream_name, position]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Controls
4
+ StreamName = MessageStore::Controls::StreamName
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,165 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Get
4
+ def self.included(cls)
5
+ cls.class_exec do
6
+ include MessageStore::Get
7
+
8
+ prepend Call
9
+ prepend BatchSize
10
+
11
+ dependency :session, Session
12
+
13
+ abstract :stream_name
14
+ abstract :sql_command
15
+ abstract :parameters
16
+ abstract :parameter_values
17
+ abstract :last_position
18
+ abstract :log_text
19
+
20
+ virtual :specialize_error
21
+ virtual :assure
22
+ end
23
+ end
24
+
25
+ module BatchSize
26
+ def batch_size
27
+ @batch_size ||= Defaults.batch_size
28
+ end
29
+ end
30
+
31
+ def self.build(stream_name, **args)
32
+ cls = specialization(stream_name)
33
+ cls.build(stream_name, **args)
34
+ end
35
+
36
+ def self.configure(receiver, stream_name, **args)
37
+ attr_name = args.delete(:attr_name)
38
+ attr_name ||= :get
39
+
40
+ instance = build(stream_name, **args)
41
+ receiver.public_send("#{attr_name}=", instance)
42
+ end
43
+
44
+ def configure(session: nil)
45
+ Session.configure(self, session: session)
46
+ end
47
+
48
+ def self.call(stream_name, **args)
49
+ position = args.delete(:position)
50
+ instance = build(stream_name, **args)
51
+ instance.(position)
52
+ end
53
+
54
+ module Call
55
+ def call(position=nil, stream_name: nil)
56
+ position ||= self.class::Defaults.position
57
+
58
+ stream_name ||= self.stream_name
59
+
60
+ assure
61
+
62
+ logger.trace(tag: :get) { "Getting message data (#{log_text(stream_name, position)})" }
63
+
64
+ result = get_result(stream_name, position)
65
+
66
+ message_data = convert(result)
67
+
68
+ logger.info(tag: :get) { "Finished getting message data (Count: #{message_data.length}, #{log_text(stream_name, position)})" }
69
+ logger.info(tags: [:data, :message_data]) { message_data.pretty_inspect }
70
+
71
+ message_data
72
+ end
73
+ end
74
+
75
+ def get_result(stream_name, position)
76
+ logger.trace(tag: :get) { "Getting result (#{log_text(stream_name, position)})" }
77
+
78
+ parameter_values = parameter_values(stream_name, position)
79
+
80
+ begin
81
+ result = session.execute(sql_command, parameter_values)
82
+ rescue PG::RaiseException => e
83
+ raise_error(e)
84
+ end
85
+
86
+ logger.debug(tag: :get) { "Finished getting result (Count: #{result.ntuples}, #{log_text(stream_name, position)})" }
87
+
88
+ result
89
+ end
90
+
91
+ def convert(result)
92
+ logger.trace(tag: :get) { "Converting result to message data (Result Count: #{result.ntuples})" }
93
+
94
+ message_data = result.map do |record|
95
+ Get.message_data(record)
96
+ end
97
+
98
+ logger.debug(tag: :get) { "Converted result to message data (Message Data Count: #{message_data.length})" }
99
+
100
+ message_data
101
+ end
102
+
103
+ def self.message_data(record)
104
+ record['data'] = Get::Deserialize.data(record['data'])
105
+ record['metadata'] = Get::Deserialize.metadata(record['metadata'])
106
+ record['time'] = Get::Time.utc_coerced(record['time'])
107
+
108
+ MessageData::Read.build(record)
109
+ end
110
+
111
+ def raise_error(pg_error)
112
+ error_message = Get.error_message(pg_error)
113
+
114
+ error = Condition.error(error_message)
115
+
116
+ if error.nil?
117
+ error = specialize_error(error_message)
118
+ end
119
+
120
+ if not error.nil?
121
+ logger.error { error_message }
122
+ raise error
123
+ end
124
+
125
+ raise pg_error
126
+ end
127
+
128
+ def self.error_message(pg_error)
129
+ pg_error.message.gsub('ERROR:', '').strip
130
+ end
131
+
132
+ def self.specialization(stream_name)
133
+ if StreamName.category?(stream_name)
134
+ Category
135
+ else
136
+ Stream
137
+ end
138
+ end
139
+
140
+ module Deserialize
141
+ def self.data(serialized_data)
142
+ return nil if serialized_data.nil?
143
+ Transform::Read.(serialized_data, :json, MessageData::Hash)
144
+ end
145
+
146
+ def self.metadata(serialized_metadata)
147
+ return nil if serialized_metadata.nil?
148
+ Transform::Read.(serialized_metadata, :json, MessageData::Hash)
149
+ end
150
+ end
151
+
152
+ module Time
153
+ def self.utc_coerced(local_time)
154
+ Clock::UTC.coerce(local_time)
155
+ end
156
+ end
157
+
158
+ module Defaults
159
+ def self.batch_size
160
+ 1000
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,81 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Get
4
+ class Category
5
+ Error = Class.new(RuntimeError)
6
+
7
+ include Get
8
+
9
+ initializer :category, na(:batch_size), :correlation, :consumer_group_member, :consumer_group_size, :condition
10
+ alias :stream_name :category
11
+
12
+ def self.call(category, position: nil, batch_size: nil, correlation: nil, consumer_group_member: nil, consumer_group_size: nil, condition: nil, session: nil)
13
+ instance = build(category, batch_size: batch_size, correlation: correlation, consumer_group_member: consumer_group_member, consumer_group_size: consumer_group_size, condition: condition, session: session)
14
+ instance.(position)
15
+ end
16
+
17
+ def self.build(category, batch_size: nil, correlation: nil, consumer_group_member: nil, consumer_group_size: nil, condition: nil, session: nil)
18
+ instance = new(category, batch_size, correlation, consumer_group_member, consumer_group_size, condition)
19
+ instance.configure(session: session)
20
+ instance
21
+ end
22
+
23
+ def self.configure(receiver, category, attr_name: nil, batch_size: nil, correlation: nil, consumer_group_member: nil, consumer_group_size: nil, condition: nil, session: nil)
24
+ attr_name ||= :get
25
+ instance = build(category, batch_size: batch_size, correlation: correlation, consumer_group_member: consumer_group_member, consumer_group_size: consumer_group_size, condition: condition, session: session)
26
+ receiver.public_send("#{attr_name}=", instance)
27
+ end
28
+
29
+ def sql_command
30
+ "SELECT * FROM get_category_messages(#{parameters});"
31
+ end
32
+
33
+ def parameters
34
+ '$1::varchar, $2::bigint, $3::bigint, $4::varchar, $5::bigint, $6::bigint, $7::varchar'
35
+ end
36
+
37
+ def parameter_values(category, position)
38
+ [
39
+ category,
40
+ position,
41
+ batch_size,
42
+ correlation,
43
+ consumer_group_member,
44
+ consumer_group_size,
45
+ condition
46
+ ]
47
+ end
48
+
49
+ def last_position(batch)
50
+ batch.last.global_position
51
+ end
52
+
53
+ def specialize_error(error_message)
54
+ error = Correlation.error(error_message)
55
+
56
+ if error.nil?
57
+ error = ConsumerGroup.error(error_message)
58
+ end
59
+
60
+ error
61
+ end
62
+
63
+ def log_text(category, position)
64
+ "Category: #{category}, Position: #{position.inspect}, Batch Size: #{batch_size.inspect}, Correlation: #{correlation.inspect}, Consumer Group Member: #{consumer_group_member.inspect}, Consumer Group Size: #{consumer_group_size.inspect}, Condition: #{condition.inspect})"
65
+ end
66
+
67
+ def assure
68
+ if not MessageStore::StreamName.category?(category)
69
+ raise Error, "Must be a category (Stream Name: #{category})"
70
+ end
71
+ end
72
+
73
+ module Defaults
74
+ def self.position
75
+ 1
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,20 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Get
4
+ class Category
5
+ module ConsumerGroup
6
+ Error = Class.new(RuntimeError)
7
+
8
+ def self.error(error_message)
9
+ if error_message.start_with?('Consumer group size must not be less than 1') ||
10
+ error_message.start_with?('Consumer group member must be less than the group size') ||
11
+ error_message.start_with?('Consumer group member must not be less than 0') ||
12
+ error_message.start_with?('Consumer group member and size must be specified')
13
+ Error.new(error_message)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Get
4
+ class Category
5
+ module Correlation
6
+ Error = Class.new(RuntimeError)
7
+
8
+ def self.error(error_message)
9
+ if error_message.start_with?('Correlation must be a category')
10
+ Error.new(error_message)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Get
4
+ module Condition
5
+ Error = Class.new(RuntimeError)
6
+
7
+ def self.error(error_message)
8
+ if error_message.start_with?('Retrieval with SQL condition is not activated')
9
+ Get::Condition::Error.new(error_message)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,67 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Get
4
+ class Stream
5
+ Error = Class.new(RuntimeError)
6
+
7
+ include Get
8
+
9
+ initializer :stream_name, na(:batch_size), :condition
10
+
11
+ def self.call(stream_name, position: nil, batch_size: nil, condition: nil, session: nil)
12
+ instance = build(stream_name, batch_size: batch_size, condition: condition, session: session)
13
+ instance.(position)
14
+ end
15
+
16
+ def self.build(stream_name, batch_size: nil, condition: nil, session: nil)
17
+ instance = new(stream_name, batch_size, condition)
18
+ instance.configure(session: session)
19
+ instance
20
+ end
21
+
22
+ def self.configure(receiver, stream_name, attr_name: nil, batch_size: nil, condition: nil, session: nil)
23
+ attr_name ||= :get
24
+ instance = build(stream_name, batch_size: batch_size, condition: condition, session: session)
25
+ receiver.public_send("#{attr_name}=", instance)
26
+ end
27
+
28
+ def sql_command
29
+ "SELECT * FROM get_stream_messages(#{parameters});"
30
+ end
31
+
32
+ def parameters
33
+ '$1::varchar, $2::bigint, $3::bigint, $4::varchar'
34
+ end
35
+
36
+ def parameter_values(stream_name, position)
37
+ [
38
+ stream_name,
39
+ position,
40
+ batch_size,
41
+ condition
42
+ ]
43
+ end
44
+
45
+ def last_position(batch)
46
+ batch.last.position
47
+ end
48
+
49
+ def log_text(stream_name, position)
50
+ "Stream Name: #{stream_name}, Position: #{position.inspect}, Batch Size: #{batch_size.inspect}, Condition: #{condition.inspect})"
51
+ end
52
+
53
+ def assure
54
+ if MessageStore::StreamName.category?(stream_name)
55
+ raise Error, "Must be a stream name (Category: #{stream_name})"
56
+ end
57
+ end
58
+
59
+ module Defaults
60
+ def self.position
61
+ 0
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,66 @@
1
+ module MessageStore
2
+ module Postgres
3
+ module Get
4
+ class Stream
5
+ class Last
6
+ include MessageStore::Get::Stream::Last
7
+
8
+ dependency :session, Session
9
+
10
+ def configure(session: nil)
11
+ Session.configure(self, session: session)
12
+ end
13
+
14
+ def call(stream_name)
15
+ logger.trace(tag: :get) { "Getting last message data (Stream Name: #{stream_name})" }
16
+
17
+ result = get_result(stream_name)
18
+
19
+ return nil if result.nil?
20
+
21
+ message_data = convert(result[0])
22
+
23
+ logger.info(tag: :get) { "Finished getting message data (Stream Name: #{stream_name})" }
24
+ logger.info(tags: [:data, :message_data]) { message_data.pretty_inspect }
25
+
26
+ message_data
27
+ end
28
+
29
+ def get_result(stream_name)
30
+ logger.trace(tag: :get) { "Getting last record (Stream: #{stream_name})" }
31
+
32
+ sql_command = self.class.sql_command(stream_name)
33
+
34
+ parameter_values = [
35
+ stream_name
36
+ ]
37
+
38
+ result = session.execute(sql_command, parameter_values)
39
+
40
+ logger.debug(tag: :get) { "Finished getting result (Count: #{result.ntuples}, Stream: #{stream_name}" }
41
+
42
+ return nil if result.ntuples == 0
43
+
44
+ result
45
+ end
46
+
47
+ def self.sql_command(stream_name)
48
+ parameters = '$1::varchar'
49
+
50
+ "SELECT * FROM get_last_stream_message(#{parameters});"
51
+ end
52
+
53
+ def convert(record)
54
+ logger.trace(tag: :get) { "Converting record to message data" }
55
+
56
+ message_data = Get.message_data(record)
57
+
58
+ logger.debug(tag: :get) { "Converted record to message data" }
59
+
60
+ message_data
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,9 @@
1
+ module MessageStore
2
+ module Postgres
3
+ class Log < ::Log
4
+ def tag!(tags)
5
+ tags << :message_store
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,146 @@
1
+ module MessageStore
2
+ module Postgres
3
+ class Put
4
+ include Dependency
5
+ include Log::Dependency
6
+
7
+ dependency :session, Session
8
+ dependency :identifier, Identifier::UUID::Random
9
+
10
+ def self.build(session: nil)
11
+ new.tap do |instance|
12
+ instance.configure(session: session)
13
+ end
14
+ end
15
+
16
+ def configure(session: nil)
17
+ Session.configure(self, session: session)
18
+ Identifier::UUID::Random.configure(self)
19
+ end
20
+
21
+ def self.configure(receiver, session: nil, attr_name: nil)
22
+ attr_name ||= :put
23
+ instance = build(session: session)
24
+ receiver.public_send "#{attr_name}=", instance
25
+ end
26
+
27
+ def self.call(write_message, stream_name, expected_version: nil, session: nil)
28
+ instance = build(session: session)
29
+ instance.(write_message, stream_name, expected_version: expected_version)
30
+ end
31
+
32
+ def call(write_message, stream_name, expected_version: nil)
33
+ logger.trace(tag: :put) { "Putting message data (Type: #{write_message.type}, Stream Name: #{stream_name}, Expected Version: #{expected_version.inspect})" }
34
+ logger.trace(tags: [:data, :message_data]) { write_message.pretty_inspect }
35
+
36
+ write_message.id ||= identifier.get
37
+
38
+ id, type, data, metadata = destructure_message(write_message)
39
+ expected_version = ExpectedVersion.canonize(expected_version)
40
+
41
+ insert_message(id, stream_name, type, data, metadata, expected_version).tap do |position|
42
+ logger.info(tag: :put) { "Put message data (Type: #{write_message.type}, Stream Name: #{stream_name}, Expected Version: #{expected_version.inspect}, ID: #{id.inspect}, Position: #{position})" }
43
+ logger.info(tags: [:data, :message_data]) { write_message.pretty_inspect }
44
+ end
45
+ end
46
+
47
+ def destructure_message(write_message)
48
+ id = write_message.id
49
+ type = write_message.type
50
+ data = write_message.data
51
+ metadata = write_message.metadata
52
+
53
+ logger.debug(tags: [:data, :message_data]) { "ID: #{id.pretty_inspect}" }
54
+ logger.debug(tags: [:data, :message_data]) { "Type: #{type.pretty_inspect}" }
55
+ logger.debug(tags: [:data, :message_data]) { "Data: #{data.pretty_inspect}" }
56
+ logger.debug(tags: [:data, :message_data]) { "Metadata: #{metadata.pretty_inspect}" }
57
+
58
+ return id, type, data, metadata
59
+ end
60
+
61
+ def insert_message(id, stream_name, type, data, metadata, expected_version)
62
+ transformed_data = transformed_data(data)
63
+ transformed_metadata = transformed_metadata(metadata)
64
+ records = execute_query(id, stream_name, type, transformed_data, transformed_metadata, expected_version)
65
+ position(records)
66
+ end
67
+
68
+ def execute_query(id, stream_name, type, transformed_data, transformed_metadata, expected_version)
69
+ logger.trace(tag: :put) { "Executing insert (Stream Name: #{stream_name}, Type: #{type}, Expected Version: #{expected_version.inspect}, ID: #{id.inspect})" }
70
+
71
+ params = [
72
+ id,
73
+ stream_name,
74
+ type,
75
+ transformed_data,
76
+ transformed_metadata,
77
+ expected_version
78
+ ]
79
+
80
+ begin
81
+ records = session.execute(self.class.statement, params)
82
+ rescue PG::RaiseException => e
83
+ raise_error e
84
+ end
85
+
86
+ logger.debug(tag: :put) { "Executed insert (Type: #{type}, Stream Name: #{stream_name}, Expected Version: #{expected_version.inspect}, ID: #{id.inspect})" }
87
+
88
+ records
89
+ end
90
+
91
+ def self.statement
92
+ @statement ||= "SELECT write_message($1::varchar, $2::varchar, $3::varchar, $4::jsonb, $5::jsonb, $6::bigint);"
93
+ end
94
+
95
+ def transformed_data(data)
96
+ transformed_data = nil
97
+
98
+ if data.is_a?(Hash) && data.empty?
99
+ data = nil
100
+ end
101
+
102
+ unless data.nil?
103
+ transformable_data = MessageData::Hash[data]
104
+ transformed_data = Transform::Write.(transformable_data, :json)
105
+ end
106
+
107
+ logger.debug(tags: [:data, :serialize]) { "Transformed Data: #{transformed_data.inspect}" }
108
+ transformed_data
109
+ end
110
+
111
+ def transformed_metadata(metadata)
112
+ transformed_metadata = nil
113
+
114
+ if metadata.is_a?(Hash) && metadata.empty?
115
+ metadata = nil
116
+ end
117
+
118
+ unless metadata.nil?
119
+ transformable_metadata = MessageData::Hash[metadata]
120
+ transformed_metadata = Transform::Write.(transformable_metadata, :json)
121
+ end
122
+
123
+ logger.debug(tags: [:data, :serialize]) { "Transformed Metadata: #{transformed_metadata.inspect}" }
124
+ transformed_metadata
125
+ end
126
+
127
+ def position(records)
128
+ position = nil
129
+ unless records[0].nil?
130
+ position = records[0].values[0]
131
+ end
132
+ position
133
+ end
134
+
135
+ def raise_error(pg_error)
136
+ error_message = pg_error.message
137
+ if error_message.include? 'Wrong expected version'
138
+ error_message.gsub!('ERROR:', '').strip!
139
+ logger.error { error_message }
140
+ raise ExpectedVersion::Error, error_message
141
+ end
142
+ raise pg_error
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,17 @@
1
+ module MessageStore
2
+ module Postgres
3
+ class Read
4
+ include MessageStore::Read
5
+
6
+ def configure(session: nil, condition: nil)
7
+ Get.configure(self.iterator, self.stream_name, batch_size: batch_size, condition: condition, session: session)
8
+ end
9
+
10
+ module Defaults
11
+ def self.batch_size
12
+ Postgres::Get::Defaults.batch_size
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,148 @@
1
+ module MessageStore
2
+ module Postgres
3
+ class Session
4
+ Error = Class.new(RuntimeError)
5
+
6
+ include Settings::Setting
7
+
8
+ include Log::Dependency
9
+
10
+ def self.settings
11
+ Settings.names
12
+ end
13
+
14
+ settings.each do |s|
15
+ setting s
16
+ end
17
+
18
+ attr_accessor :connection
19
+
20
+ def self.build(settings: nil)
21
+ new.tap do |instance|
22
+ settings ||= Settings.instance
23
+ settings.set(instance)
24
+ end
25
+ end
26
+
27
+ def self.configure(receiver, session: nil, settings: nil, attr_name: nil)
28
+ attr_name ||= :session
29
+
30
+ if session != nil && settings != nil
31
+ error_msg = "Session configured with both settings and session arguments. Use one or the other, but not both."
32
+ logger.error(tag: :session) { error_msg }
33
+ raise Error, error_msg
34
+ end
35
+
36
+ instance = session || build(settings: settings)
37
+ receiver.public_send "#{attr_name}=", instance
38
+ instance
39
+ end
40
+
41
+ def open
42
+ logger.trace(tag: :session) { "Connecting to database" }
43
+
44
+ if connected?
45
+ logger.debug(tag: :session) { "Already connected. A new connection will not be built." }
46
+ return connection
47
+ end
48
+
49
+ logger.debug(tag: :session) { "Not connected. A new connection will be built." }
50
+ connection = self.class.build_connection(self)
51
+ self.connection = connection
52
+
53
+ logger.debug(tag: :session) { "Connected to database" }
54
+
55
+ connection
56
+ end
57
+ alias :connect :open
58
+
59
+ def self.build_connection(instance)
60
+ settings = instance.settings
61
+ logger.trace(tag: :session) { "Building new connection to database (Settings: #{LogText.settings(settings).inspect})" }
62
+
63
+ connection = PG::Connection.open(settings)
64
+ connection.type_map_for_results = PG::BasicTypeMapForResults.new(connection)
65
+
66
+ logger.debug(tag: :session) { "Built new connection to database (Settings: #{LogText.settings(settings).inspect})" }
67
+
68
+ connection
69
+ end
70
+
71
+ def connected?
72
+ return false if connection.nil?
73
+
74
+ status = PG::CONNECTION_OK
75
+ begin
76
+ status = connection.status
77
+ rescue PG::ConnectionBad
78
+ status = nil
79
+ end
80
+
81
+ status == PG::CONNECTION_OK
82
+ end
83
+ alias :open? :connected?
84
+
85
+ def close
86
+ connection.close
87
+ connection = nil
88
+ end
89
+
90
+ def reset
91
+ connection.reset
92
+ end
93
+
94
+ def settings
95
+ settings = {}
96
+ self.class.settings.each do |s|
97
+ val = public_send(s)
98
+ settings[s] = val unless val.nil?
99
+ end
100
+ settings
101
+ end
102
+
103
+ def execute(sql_command, params=nil)
104
+ logger.trace(tag: :session) { "Executing SQL command" }
105
+ logger.trace(tag: :sql) { sql_command }
106
+ logger.trace(tag: :data) { params.pretty_inspect }
107
+
108
+ unless connected?
109
+ connect
110
+ end
111
+
112
+ if params.nil?
113
+ connection.exec(sql_command).tap do
114
+ logger.debug(tag: :session) { "Executed SQL command (no params)" }
115
+ end
116
+ else
117
+ connection.exec_params(sql_command, params).tap do
118
+ logger.debug(tag: :session) { "Executed SQL command with params" }
119
+ end
120
+ end
121
+ end
122
+
123
+ def transaction(&blk)
124
+ unless connected?
125
+ connect
126
+ end
127
+
128
+ connection.transaction(&blk)
129
+ end
130
+
131
+ def self.logger
132
+ @logger ||= Log.get self
133
+ end
134
+
135
+ module LogText
136
+ def self.settings(settings)
137
+ s = settings.dup
138
+
139
+ if s.has_key?(:password)
140
+ s[:password] = '*' * 8
141
+ end
142
+
143
+ s
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,36 @@
1
+ module MessageStore
2
+ module Postgres
3
+ class Settings < ::Settings
4
+ def self.instance
5
+ @instance ||= build
6
+ end
7
+
8
+ def self.data_source
9
+ Defaults.data_source
10
+ end
11
+
12
+ def self.names
13
+ [
14
+ :dbname,
15
+ :host,
16
+ :hostaddr,
17
+ :port,
18
+ :user,
19
+ :password,
20
+ :connect_timeout,
21
+ :options,
22
+ :sslmode,
23
+ :krbsrvname,
24
+ :gsslib,
25
+ :service
26
+ ]
27
+ end
28
+
29
+ class Defaults
30
+ def self.data_source
31
+ ENV['MESSAGE_STORE_SETTINGS_PATH'] || 'settings/message_store_postgres.json'
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,52 @@
1
+ module MessageStore
2
+ module Postgres
3
+ class Write
4
+ include MessageStore::Write
5
+
6
+ dependency :put
7
+
8
+ def configure(session: nil)
9
+ Put.configure(self, session: session)
10
+ end
11
+
12
+ def write(batch, stream_name, expected_version: nil)
13
+ logger.trace(tag: :write) do
14
+ message_types = batch.map {|message_data| message_data.type }.uniq.join(', ')
15
+ "Writing batch (Stream Name: #{stream_name}, Types: #{message_types}, Number of Messages: #{batch.length}, Expected Version: #{expected_version.inspect})"
16
+ end
17
+
18
+ unless expected_version.nil?
19
+ expected_version = ExpectedVersion.canonize(expected_version)
20
+ end
21
+
22
+ last_position = nil
23
+ put.session.transaction do
24
+ batch.each do |message_data|
25
+ last_position = write_message_data(message_data, stream_name, expected_version: expected_version)
26
+
27
+ unless expected_version.nil?
28
+ expected_version += 1
29
+ end
30
+ end
31
+ end
32
+
33
+ logger.debug(tag: :write) do
34
+ message_types = batch.map {|message_data| message_data.type }.uniq.join(', ')
35
+ "Wrote batch (Stream Name: #{stream_name}, Types: #{message_types}, Number of Messages: #{batch.length}, Expected Version: #{expected_version.inspect})"
36
+ end
37
+
38
+ last_position
39
+ end
40
+
41
+ def write_message_data(message_data, stream_name, expected_version: nil)
42
+ logger.trace(tag: :write) { "Writing message data (Stream Name: #{stream_name}, Type: #{message_data.type}, Expected Version: #{expected_version.inspect})" }
43
+ logger.trace(tags: [:data, :message_data]) { message_data.pretty_inspect }
44
+
45
+ put.(message_data, stream_name, expected_version: expected_version).tap do
46
+ logger.debug(tag: :write) { "Wrote message data (Stream Name: #{stream_name}, Type: #{message_data.type}, Expected Version: #{expected_version.inspect})" }
47
+ logger.debug(tags: [:data, :message_data]) { message_data.pretty_inspect }
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: evt-message_store-postgres
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.4.0.5
5
+ platform: ruby
6
+ authors:
7
+ - The Eventide Project
8
+ autorequire:
9
+ bindir: scripts
10
+ cert_chain: []
11
+ date: 2020-07-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: evt-message_store
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: evt-log
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: evt-settings
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: message-db
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pg
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: test_bench
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: evt-diagnostics-sample
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: ntl-actor
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: " "
126
+ email: opensource@eventide-project.org
127
+ executables: []
128
+ extensions: []
129
+ extra_rdoc_files: []
130
+ files:
131
+ - lib/message_store/postgres.rb
132
+ - lib/message_store/postgres/controls.rb
133
+ - lib/message_store/postgres/controls/category.rb
134
+ - lib/message_store/postgres/controls/message_data.rb
135
+ - lib/message_store/postgres/controls/position.rb
136
+ - lib/message_store/postgres/controls/put.rb
137
+ - lib/message_store/postgres/controls/stream_name.rb
138
+ - lib/message_store/postgres/get.rb
139
+ - lib/message_store/postgres/get/category.rb
140
+ - lib/message_store/postgres/get/category/consumer_group.rb
141
+ - lib/message_store/postgres/get/category/correlation.rb
142
+ - lib/message_store/postgres/get/condition.rb
143
+ - lib/message_store/postgres/get/stream.rb
144
+ - lib/message_store/postgres/get/stream/last.rb
145
+ - lib/message_store/postgres/log.rb
146
+ - lib/message_store/postgres/put.rb
147
+ - lib/message_store/postgres/read.rb
148
+ - lib/message_store/postgres/session.rb
149
+ - lib/message_store/postgres/settings.rb
150
+ - lib/message_store/postgres/write.rb
151
+ homepage: https://github.com/eventide-project/message-store-postgres
152
+ licenses:
153
+ - MIT
154
+ metadata: {}
155
+ post_install_message:
156
+ rdoc_options: []
157
+ require_paths:
158
+ - lib
159
+ required_ruby_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: 2.4.0
164
+ required_rubygems_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ requirements: []
170
+ rubygems_version: 3.1.2
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: Message store implementation for PostgreSQL
174
+ test_files: []