evt-message_store-postgres 1.2.0.0 → 2.4.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77c708beeefed6f49a2a8df231fc7d8bb84ef2dd75982dc602cb5b6893662869
4
- data.tar.gz: 3255fa00e94215b04d61fac3054ad4db43e64813c266232cf333e79345d7f49d
3
+ metadata.gz: d39147eb9243bc55ac8f717ea66848d8f52afa81e2765c65fbcb6d8790eddebd
4
+ data.tar.gz: c06dbbfd0bdf1755bba528431be09cdf6102e55895c6310f791ded9bdf6fe6f1
5
5
  SHA512:
6
- metadata.gz: 6b1e8f43a95d701db50f9c21964a52acebe8c435b11c91befb6a0e1c1cdf25b8a33f50d0457f1a4af0f34cfd6d426eb2bd918fef8c953af4deabd861a45ee0a4
7
- data.tar.gz: 5e63109d9eb4a60f9c2af6c50ee4adca1929fd224defbe558d47e96c5264ebb884553235cbf5962f9f3d1575fefa9f49ec58d10f4795143ba160a94a9dad67a4
6
+ metadata.gz: bc425933b84429dfcb09460747dd41b46e652da2b93dcad021759109c8756dcd8bf63b99b300d09200e364559e3220cf03e84e4746cbe8d4716409dac85ec765
7
+ data.tar.gz: a1834321e541d178d01a302b42c82d4d6f344fc51a47eaba2e6ffeecfaa1f63315e87f979de0c7d1edb3c405da6a5dd6309e8dbc23ac40f6ddfe72f3d6b2f4d2
@@ -15,7 +15,10 @@ require 'message_store/postgres/put'
15
15
  require 'message_store/postgres/write'
16
16
 
17
17
  require 'message_store/postgres/get'
18
+ require 'message_store/postgres/get/condition'
18
19
  require 'message_store/postgres/get/stream'
19
- require 'message_store/postgres/get/category'
20
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'
21
24
  require 'message_store/postgres/read'
@@ -1,25 +1,7 @@
1
1
  module MessageStore
2
2
  module Postgres
3
3
  module Controls
4
- module Category
5
- def self.example(category: nil, randomize_category: nil)
6
- if randomize_category.nil?
7
- if !category.nil?
8
- randomize_category = false
9
- end
10
- end
11
-
12
- randomize_category = true if randomize_category.nil?
13
-
14
- category ||= 'test'
15
-
16
- if randomize_category
17
- category = "#{category}#{SecureRandom.hex(16)}XX"
18
- end
19
-
20
- category
21
- end
22
- end
4
+ Category = MessageStore::Controls::Category
23
5
  end
24
6
  end
25
7
  end
@@ -6,19 +6,19 @@ module MessageStore
6
6
  module MessageData
7
7
  module Write
8
8
  module List
9
- Entry = Struct.new(:stream_name, :message_data)
9
+ Entry = Struct.new(:stream_name, :category, :message_data)
10
10
 
11
- def self.get(instances: nil)
11
+ def self.get(instances: nil, stream_name: nil, category: nil)
12
12
  instances ||= 1
13
13
 
14
- stream_name = StreamName.example
15
-
16
14
  list = []
17
15
  instances.times do
18
- stream_name = Controls::StreamName.example
16
+ instance_stream_name = stream_name || StreamName.example(category: category)
17
+ instance_category = MessageStore::StreamName.get_category(instance_stream_name)
18
+
19
19
  write_message = Controls::MessageData::Write.example
20
20
 
21
- list << Entry.new(stream_name, write_message)
21
+ list << Entry.new(instance_stream_name, instance_category, write_message)
22
22
  end
23
23
 
24
24
  list
@@ -2,20 +2,21 @@ module MessageStore
2
2
  module Postgres
3
3
  module Controls
4
4
  module Put
5
- def self.call(instances: nil, stream_name: nil, message: nil, category: nil)
5
+ def self.call(instances: nil, stream_name: nil, message_data: nil, message: nil, category: nil)
6
6
  instances ||= 1
7
7
  stream_name ||= StreamName.example(category: category)
8
+ message_data ||= message
8
9
 
9
- message_specified = !message.nil?
10
+ message_specified = !message_data.nil?
10
11
 
11
- message ||= MessageData::Write.example
12
+ message_data ||= MessageData::Write.example
12
13
 
13
14
  position = nil
14
15
  instances.times do
15
- position = MessageStore::Postgres::Put.(message, stream_name)
16
+ position = MessageStore::Postgres::Put.(message_data, stream_name)
16
17
 
17
18
  unless message_specified
18
- message.id = MessageData::Write.id
19
+ message_data.id = MessageData::Write.id
19
20
  end
20
21
  end
21
22
 
@@ -4,14 +4,21 @@ module MessageStore
4
4
  def self.included(cls)
5
5
  cls.class_exec do
6
6
  include MessageStore::Get
7
+
7
8
  prepend Call
8
9
  prepend BatchSize
9
10
 
10
- extend SQLCommand
11
-
12
11
  dependency :session, Session
13
12
 
14
- initializer :stream_name, na(:batch_size), :condition
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
15
22
  end
16
23
  end
17
24
 
@@ -21,40 +28,44 @@ module MessageStore
21
28
  end
22
29
  end
23
30
 
24
- def self.build(stream_name, batch_size: nil, session: nil, condition: nil)
31
+ def self.build(stream_name, **args)
25
32
  cls = specialization(stream_name)
26
-
27
- cls.new(stream_name, batch_size, condition).tap do |instance|
28
- instance.configure(session: session)
29
- end
33
+ cls.build(stream_name, **args)
30
34
  end
31
35
 
32
- def self.configure(receiver, stream_name, attr_name: nil, batch_size: nil, condition: nil, session: nil)
36
+ def self.configure(receiver, stream_name, **args)
37
+ attr_name = args.delete(:attr_name)
33
38
  attr_name ||= :get
34
- instance = build(stream_name, batch_size: batch_size, condition: condition, session: session)
35
- receiver.public_send "#{attr_name}=", instance
39
+
40
+ instance = build(stream_name, **args)
41
+ receiver.public_send("#{attr_name}=", instance)
36
42
  end
37
43
 
38
44
  def configure(session: nil)
39
- Session.configure self, session: session
45
+ Session.configure(self, session: session)
40
46
  end
41
47
 
42
- def self.call(stream_name, position: nil, batch_size: nil, condition: nil, session: nil)
43
- instance = build(stream_name, batch_size: batch_size, condition: condition, session: session)
48
+ def self.call(stream_name, **args)
49
+ position = args.delete(:position)
50
+ instance = build(stream_name, **args)
44
51
  instance.(position)
45
52
  end
46
53
 
47
54
  module Call
48
- def call(position)
49
- logger.trace(tag: :get) { "Getting message data (Stream Name: #{stream_name}, Position: #{position.inspect}, Batch Size: #{batch_size.inspect})" }
55
+ def call(position=nil, stream_name: nil)
56
+ position ||= self.class::Defaults.position
57
+
58
+ stream_name ||= self.stream_name
59
+
60
+ assure
50
61
 
51
- position ||= Defaults.position
62
+ logger.trace(tag: :get) { "Getting message data (#{log_text(stream_name, position)})" }
52
63
 
53
64
  result = get_result(stream_name, position)
54
65
 
55
66
  message_data = convert(result)
56
67
 
57
- logger.info(tag: :get) { "Finished getting message data (Count: #{message_data.length}, Stream Name: #{stream_name}, Position: #{position.inspect}, Batch Size: #{batch_size.inspect})" }
68
+ logger.info(tag: :get) { "Finished getting message data (Count: #{message_data.length}, #{log_text(stream_name, position)})" }
58
69
  logger.info(tags: [:data, :message_data]) { message_data.pretty_inspect }
59
70
 
60
71
  message_data
@@ -62,53 +73,60 @@ module MessageStore
62
73
  end
63
74
 
64
75
  def get_result(stream_name, position)
65
- logger.trace(tag: :get) { "Getting result (Stream Name: #{stream_name}, Position: #{position.inspect}, Batch Size: #{batch_size.inspect}, Condition: #{condition || '(none)'})" }
76
+ logger.trace(tag: :get) { "Getting result (#{log_text(stream_name, position)})" }
66
77
 
67
- sql_command = self.class.sql_command(stream_name, position, batch_size, condition)
78
+ parameter_values = parameter_values(stream_name, position)
68
79
 
69
- cond = Get.constrain_condition(condition)
70
-
71
- params = [
72
- stream_name,
73
- position,
74
- batch_size,
75
- cond
76
- ]
77
-
78
- result = session.execute(sql_command, params)
80
+ begin
81
+ result = session.execute(sql_command, parameter_values)
82
+ rescue PG::RaiseException => e
83
+ raise_error(e)
84
+ end
79
85
 
80
- logger.debug(tag: :get) { "Finished getting result (Count: #{result.ntuples}, Stream Name: #{stream_name}, Position: #{position.inspect}, Batch Size: #{batch_size.inspect}, Condition: #{condition || '(none)'})" }
86
+ logger.debug(tag: :get) { "Finished getting result (Count: #{result.ntuples}, #{log_text(stream_name, position)})" }
81
87
 
82
88
  result
83
89
  end
84
90
 
85
- def self.constrain_condition(condition)
86
- return nil if condition.nil?
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
87
97
 
88
- "(#{condition})"
98
+ logger.debug(tag: :get) { "Converted result to message data (Message Data Count: #{message_data.length})" }
99
+
100
+ message_data
89
101
  end
90
102
 
91
- module SQLCommand
92
- def sql_command(stream_name, position, batch_size, condition)
93
- parameters = '$1::varchar, $2::bigint, $3::bigint, $4::varchar'
94
- command_text(parameters)
95
- end
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)
96
109
  end
97
110
 
98
- def convert(result)
99
- logger.trace(tag: :get) { "Converting result to message data (Result Count: #{result.ntuples})" }
111
+ def raise_error(pg_error)
112
+ error_message = Get.error_message(pg_error)
100
113
 
101
- message_data = result.map do |record|
102
- record['data'] = Deserialize.data(record['data'])
103
- record['metadata'] = Deserialize.metadata(record['metadata'])
104
- record['time'] = Time.utc_coerced(record['time'])
114
+ error = Condition.error(error_message)
105
115
 
106
- MessageData::Read.build(record)
116
+ if error.nil?
117
+ error = specialize_error(error_message)
107
118
  end
108
119
 
109
- logger.debug(tag: :get) { "Converted result to message data (Message Data Count: #{message_data.length})" }
120
+ if not error.nil?
121
+ logger.error { error_message }
122
+ raise error
123
+ end
110
124
 
111
- message_data
125
+ raise pg_error
126
+ end
127
+
128
+ def self.error_message(pg_error)
129
+ pg_error.message.gsub('ERROR:', '').strip
112
130
  end
113
131
 
114
132
  def self.specialization(stream_name)
@@ -138,10 +156,6 @@ module MessageStore
138
156
  end
139
157
 
140
158
  module Defaults
141
- def self.position
142
- 0
143
- end
144
-
145
159
  def self.batch_size
146
160
  1000
147
161
  end
@@ -2,15 +2,79 @@ module MessageStore
2
2
  module Postgres
3
3
  module Get
4
4
  class Category
5
+ Error = Class.new(RuntimeError)
6
+
5
7
  include Get
6
8
 
7
- def self.command_text(parameters)
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
8
30
  "SELECT * FROM get_category_messages(#{parameters});"
9
31
  end
10
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
+
11
49
  def last_position(batch)
12
50
  batch.last.global_position
13
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
14
78
  end
15
79
  end
16
80
  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
@@ -2,15 +2,65 @@ module MessageStore
2
2
  module Postgres
3
3
  module Get
4
4
  class Stream
5
+ Error = Class.new(RuntimeError)
6
+
5
7
  include Get
6
8
 
7
- def self.command_text(parameters)
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
8
29
  "SELECT * FROM get_stream_messages(#{parameters});"
9
30
  end
10
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
+
11
45
  def last_position(batch)
12
46
  batch.last.position
13
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
14
64
  end
15
65
  end
16
66
  end
@@ -31,11 +31,11 @@ module MessageStore
31
31
 
32
32
  sql_command = self.class.sql_command(stream_name)
33
33
 
34
- params = [
34
+ parameter_values = [
35
35
  stream_name
36
36
  ]
37
37
 
38
- result = session.execute(sql_command, params)
38
+ result = session.execute(sql_command, parameter_values)
39
39
 
40
40
  logger.debug(tag: :get) { "Finished getting result (Count: #{result.ntuples}, Stream: #{stream_name}" }
41
41
 
@@ -47,40 +47,18 @@ module MessageStore
47
47
  def self.sql_command(stream_name)
48
48
  parameters = '$1::varchar'
49
49
 
50
- "SELECT * FROM get_last_message(#{parameters});"
50
+ "SELECT * FROM get_last_stream_message(#{parameters});"
51
51
  end
52
52
 
53
53
  def convert(record)
54
54
  logger.trace(tag: :get) { "Converting record to message data" }
55
55
 
56
- record['data'] = Deserialize.data(record['data'])
57
- record['metadata'] = Deserialize.metadata(record['metadata'])
58
- record['time'] = Time.utc_coerced(record['time'])
59
-
60
- message_data = MessageData::Read.build(record)
56
+ message_data = Get.message_data(record)
61
57
 
62
58
  logger.debug(tag: :get) { "Converted record to message data" }
63
59
 
64
60
  message_data
65
61
  end
66
-
67
- module Deserialize
68
- def self.data(serialized_data)
69
- return nil if serialized_data.nil?
70
- Transform::Read.(serialized_data, :json, MessageData::Hash)
71
- end
72
-
73
- def self.metadata(serialized_metadata)
74
- return nil if serialized_metadata.nil?
75
- Transform::Read.(serialized_metadata, :json, MessageData::Hash)
76
- end
77
- end
78
-
79
- module Time
80
- def self.utc_coerced(local_time)
81
- Clock::UTC.coerce(local_time)
82
- end
83
- end
84
62
  end
85
63
  end
86
64
  end
@@ -5,7 +5,7 @@ module MessageStore
5
5
  include Log::Dependency
6
6
 
7
7
  dependency :session, Session
8
- dependency :identifier, Session
8
+ dependency :identifier, Identifier::UUID::RandomIdentifier dependency is corrected
9
9
 
10
10
  def self.build(session: nil)
11
11
  new.tap do |instance|
@@ -6,7 +6,7 @@ module MessageStore
6
6
  end
7
7
 
8
8
  def self.data_source
9
- Defaults.data_source || 'settings/message_store_postgres.json'
9
+ Defaults.data_source
10
10
  end
11
11
 
12
12
  def self.names
@@ -28,7 +28,7 @@ module MessageStore
28
28
 
29
29
  class Defaults
30
30
  def self.data_source
31
- ENV['MESSAGE_STORE_SETTINGS_PATH']
31
+ ENV['MESSAGE_STORE_SETTINGS_PATH'] || 'settings/message_store_postgres.json'
32
32
  end
33
33
  end
34
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evt-message_store-postgres
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0.0
4
+ version: 2.4.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Eventide Project
8
8
  autorequire:
9
9
  bindir: scripts
10
10
  cert_chain: []
11
- date: 2019-11-01 00:00:00.000000000 Z
11
+ date: 2020-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: evt-message_store
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: evt-message_store-postgres-database
28
+ name: evt-log
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: evt-log
42
+ name: evt-settings
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: evt-settings
56
+ name: message-db
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -137,6 +137,9 @@ files:
137
137
  - lib/message_store/postgres/controls/stream_name.rb
138
138
  - lib/message_store/postgres/get.rb
139
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
140
143
  - lib/message_store/postgres/get/stream.rb
141
144
  - lib/message_store/postgres/get/stream/last.rb
142
145
  - lib/message_store/postgres/log.rb
@@ -164,7 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
167
  - !ruby/object:Gem::Version
165
168
  version: '0'
166
169
  requirements: []
167
- rubygems_version: 3.0.1
170
+ rubygems_version: 3.1.2
168
171
  signing_key:
169
172
  specification_version: 4
170
173
  summary: Message store implementation for PostgreSQL