evt-event_source-postgres 0.10.0.2

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
+ SHA1:
3
+ metadata.gz: cbb3a9b61a7f4a379cd86bc1fca7462a767dce24
4
+ data.tar.gz: d47872d6c1d73a06db41da3bc77cbf06ac652e16
5
+ SHA512:
6
+ metadata.gz: 96ab2094cec2f21a4b38967f76ca90c7b97cf11050b496d3aee119878d4d7b5550921126b6693e1cee9367580ed82709767f379a17cc1ea3a41e7403b51e39e5
7
+ data.tar.gz: 579b568775c5a3ed2b9542d5c10f0208c07f3896996fb2b2aacb70e172434b6d9b531c17bbe0470a0c6bddcfe8167746300a003441f35deaf31f0caca2c490bf
@@ -0,0 +1 @@
1
+ postgres.rb
@@ -0,0 +1,20 @@
1
+ require 'pg'
2
+
3
+ require 'event_source'
4
+ require 'cycle'
5
+
6
+ require 'log'
7
+ require 'telemetry'
8
+ require 'settings'; Settings.activate
9
+
10
+ require 'event_source/postgres/log'
11
+
12
+ require 'event_source/postgres/settings'
13
+ require 'event_source/postgres/session'
14
+
15
+ require 'event_source/postgres/put'
16
+ require 'event_source/postgres/write'
17
+
18
+ require 'event_source/postgres/get/select_statement'
19
+ require 'event_source/postgres/get'
20
+ require 'event_source/postgres/read'
@@ -0,0 +1,7 @@
1
+ require 'event_source/controls'
2
+
3
+ require 'event_source/postgres/controls/category'
4
+ require 'event_source/postgres/controls/stream_name'
5
+ require 'event_source/postgres/controls/stream'
6
+ require 'event_source/postgres/controls/event_data'
7
+ require 'event_source/postgres/controls/put'
@@ -0,0 +1,7 @@
1
+ module EventSource
2
+ module Postgres
3
+ module Controls
4
+ Category = EventSource::Controls::Category
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module EventSource
2
+ module Postgres
3
+ module Controls
4
+ EventData = EventSource::Controls::EventData
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module EventSource
2
+ module Postgres
3
+ module Controls
4
+ module Put
5
+ def self.call(instances: nil, stream_name: nil, event: nil, category: nil)
6
+ instances ||= 1
7
+ stream_name ||= StreamName.example(category: category)
8
+ event ||= EventData::Write.example
9
+
10
+ instances.times do
11
+ EventSource::Postgres::Put.(event, stream_name)
12
+ end
13
+
14
+ stream_name
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ module EventSource
2
+ module Postgres
3
+ module Controls
4
+ Stream = EventSource::Controls::Stream
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module EventSource
2
+ module Postgres
3
+ module Controls
4
+ StreamName = EventSource::Controls::StreamName
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,91 @@
1
+ module EventSource
2
+ module Postgres
3
+ class Get
4
+ include Log::Dependency
5
+
6
+ initializer :batch_size, :precedence
7
+
8
+ dependency :session, Session
9
+
10
+ def self.build(batch_size: nil, precedence: nil, session: nil)
11
+ new(batch_size, precedence).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, precedence: nil, session: nil)
17
+ attr_name ||= :get
18
+ instance = build(batch_size: batch_size, precedence: precedence, 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, precedence: nil, session: nil)
27
+ instance = build(batch_size: batch_size, precedence: precedence, session: session)
28
+ instance.(stream_name, position: position)
29
+ end
30
+
31
+ def call(stream_name, position: nil)
32
+ logger.trace { "Getting event data (Position: #{position.inspect}, Stream Name: #{stream_name}, Batch Size: #{batch_size.inspect}, Precedence: #{precedence.inspect})" }
33
+
34
+ records = get_records(stream_name, position)
35
+
36
+ events = convert(records)
37
+
38
+ logger.info { "Finished getting event data (Count: #{events.length}, Position: #{position.inspect}, Stream Name: #{stream_name}, Batch Size: #{batch_size.inspect}, Precedence: #{precedence.inspect})" }
39
+ logger.info(tags: [:data, :event_data]) { events.pretty_inspect }
40
+
41
+ events
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}, Precedence: #{precedence.inspect})" }
46
+
47
+ select_statement = SelectStatement.build(stream_name, offset: position, batch_size: batch_size, precedence: precedence)
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}, Precedence: #{precedence.inspect})" }
52
+
53
+ records
54
+ end
55
+
56
+ def convert(records)
57
+ logger.trace { "Converting records to event data (Records Count: #{records.ntuples})" }
58
+
59
+ events = 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
+ EventData::Read.build record
65
+ end
66
+
67
+ logger.debug { "Converted records to event data (Event Data Count: #{events.length})" }
68
+
69
+ events
70
+ end
71
+
72
+ module Deserialize
73
+ def self.data(serialized_data)
74
+ return nil if serialized_data.nil?
75
+ Transform::Read.(serialized_data, EventData::Hash, :json)
76
+ end
77
+
78
+ def self.metadata(serialized_metadata)
79
+ return nil if serialized_metadata.nil?
80
+ Transform::Read.(serialized_metadata, EventData::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,89 @@
1
+ module EventSource
2
+ module Postgres
3
+ class Get
4
+ class SelectStatement
5
+ include Log::Dependency
6
+
7
+ initializer :stream, w(:offset), w(:batch_size), w(:precedence)
8
+
9
+ def offset
10
+ @offset ||= Defaults.offset
11
+ end
12
+
13
+ def batch_size
14
+ @batch_size ||= Defaults.batch_size
15
+ end
16
+
17
+ def precedence
18
+ @precedence ||= Defaults.precedence
19
+ end
20
+
21
+ def stream_name
22
+ stream.name
23
+ end
24
+
25
+ def stream_type
26
+ stream.type
27
+ end
28
+
29
+ def self.build(stream_name, offset: nil, batch_size: nil, precedence: nil)
30
+ stream = Stream.new(stream_name)
31
+ new(stream, offset, batch_size, precedence)
32
+ end
33
+
34
+ def sql
35
+ logger.trace(tag: :sql) { "Composing select statement (Stream: #{stream_name}, Category: #{stream.category?}, Type: #{stream_type}, Position: #{offset}, Batch Size: #{batch_size}, Precedence: #{precedence})" }
36
+
37
+ statement = <<-SQL
38
+ SELECT
39
+ stream_name::varchar,
40
+ position::int,
41
+ type::varchar,
42
+ global_position::bigint,
43
+ data::varchar,
44
+ metadata::varchar,
45
+ time::timestamp
46
+ FROM
47
+ events
48
+ WHERE
49
+ #{where_clause_field} = '#{stream_name}'
50
+ ORDER BY
51
+ global_position #{precedence.to_s.upcase}
52
+ LIMIT
53
+ #{batch_size}
54
+ OFFSET
55
+ #{offset}
56
+ ;
57
+ SQL
58
+
59
+ logger.debug(tag: :sql) { "Composed select statement (Stream: #{stream_name}, Category: #{stream.category?}, Type: #{stream_type}, Position: #{offset}, Batch Size: #{batch_size}, Precedence: #{precedence})" }
60
+ logger.debug(tags: [:data, :sql]) { "Statement: #{statement}" }
61
+
62
+ statement
63
+ end
64
+
65
+ def where_clause_field
66
+ unless stream.category?
67
+ 'stream_name'
68
+ else
69
+ 'category(stream_name)'
70
+ end
71
+ end
72
+
73
+ module Defaults
74
+ def self.offset
75
+ 0
76
+ end
77
+
78
+ def self.batch_size
79
+ 1000
80
+ end
81
+
82
+ def self.precedence
83
+ :asc
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,11 @@
1
+ module EventSource
2
+ module Postgres
3
+ class Log < ::Log
4
+ def tag!(tags)
5
+ tags << :event_source_postgres
6
+ tags << :library
7
+ tags << :verbose
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,137 @@
1
+ module EventSource
2
+ module Postgres
3
+ class Put
4
+ include Log::Dependency
5
+
6
+ dependency :session, Session
7
+
8
+ def self.build(session: nil)
9
+ new.tap do |instance|
10
+ instance.configure(session: session)
11
+ end
12
+ end
13
+
14
+ def configure(session: nil)
15
+ Session.configure(self, session: session)
16
+ end
17
+
18
+ def self.configure(receiver, session: nil, attr_name: nil)
19
+ attr_name ||= :put
20
+ instance = build(session: session)
21
+ receiver.public_send "#{attr_name}=", instance
22
+ end
23
+
24
+ def self.call(write_event, stream_name, expected_version: nil, session: nil)
25
+ instance = build(session: session)
26
+ instance.(write_event, stream_name, expected_version: expected_version)
27
+ end
28
+
29
+ def call(write_event, stream_name, expected_version: nil)
30
+ logger.trace { "Putting event data (Stream Name: #{stream_name}, Type: #{write_event.type}, Expected Version: #{expected_version.inspect})" }
31
+ logger.trace(tags: [:data, :event_data]) { write_event.pretty_inspect }
32
+
33
+ type, data, metadata = destructure_event(write_event)
34
+ expected_version = ExpectedVersion.canonize(expected_version)
35
+
36
+ insert_event(stream_name, type, data, metadata, expected_version).tap do |position|
37
+ logger.info { "Put event data (Position: #{position}, Stream Name: #{stream_name}, Type: #{write_event.type}, Expected Version: #{expected_version.inspect})" }
38
+ logger.info(tags: [:data, :event_data]) { write_event.pretty_inspect }
39
+ end
40
+ end
41
+
42
+ def destructure_event(write_event)
43
+ type = write_event.type
44
+ data = write_event.data
45
+ metadata = write_event.metadata
46
+
47
+ logger.debug(tags: [:data, :event_data]) { "Data: #{data.pretty_inspect}" }
48
+ logger.debug(tags: [:data, :event_data]) { "Metadata: #{metadata.pretty_inspect}" }
49
+
50
+ return type, data, metadata
51
+ end
52
+
53
+ def insert_event(stream_name, type, data, metadata, expected_version)
54
+ serialized_data = serialized_data(data)
55
+ serialized_metadata = serialized_metadata(metadata)
56
+ records = execute_query(stream_name, type, serialized_data, serialized_metadata, expected_version)
57
+ position(records)
58
+ end
59
+
60
+ def execute_query(stream_name, type, serialized_data, serialized_metadata, expected_version)
61
+ logger.trace { "Executing insert (Stream Name: #{stream_name}, Type: #{type}, Expected Version: #{expected_version.inspect})" }
62
+
63
+ params = [
64
+ stream_name,
65
+ type,
66
+ serialized_data,
67
+ serialized_metadata,
68
+ expected_version
69
+ ]
70
+
71
+ begin
72
+ records = session.execute(self.class.statement, params)
73
+ rescue PG::RaiseException => e
74
+ raise_error e
75
+ end
76
+
77
+ logger.debug { "Executed insert (Stream Name: #{stream_name}, Type: #{type}, Expected Version: #{expected_version.inspect})" }
78
+
79
+ records
80
+ end
81
+
82
+ def self.statement
83
+ @statement ||= "SELECT write_event($1::varchar, $2::varchar, $3::jsonb, $4::jsonb, $5::int);"
84
+ end
85
+
86
+ def serialized_data(data)
87
+ serialized_data = nil
88
+
89
+ if data.is_a?(Hash) && data.empty?
90
+ data = nil
91
+ end
92
+
93
+ unless data.nil?
94
+ serializable_data = EventData::Hash[data]
95
+ serialized_data = Transform::Write.(serializable_data, :json)
96
+ end
97
+
98
+ logger.debug(tags: [:data, :serialize]) { "Serialized Data: #{serialized_data.inspect}" }
99
+ serialized_data
100
+ end
101
+
102
+ def serialized_metadata(metadata)
103
+ serialized_metadata = nil
104
+
105
+ if metadata.is_a?(Hash) && metadata.empty?
106
+ metadata = nil
107
+ end
108
+
109
+ unless metadata.nil?
110
+ serializable_metadata = EventData::Hash[metadata]
111
+ serialized_metadata = Transform::Write.(serializable_metadata, :json)
112
+ end
113
+
114
+ logger.debug(tags: [:data, :serialize]) { "Serialized Metadata: #{serialized_metadata.inspect}" }
115
+ serialized_metadata
116
+ end
117
+
118
+ def position(records)
119
+ position = nil
120
+ unless records[0].nil?
121
+ position = records[0].values[0]
122
+ end
123
+ position
124
+ end
125
+
126
+ def raise_error(pg_error)
127
+ error_message = pg_error.message
128
+ if error_message.include? 'Wrong expected version'
129
+ error_message.gsub!('ERROR:', '').strip!
130
+ logger.error { error_message }
131
+ raise ExpectedVersion::Error, error_message
132
+ end
133
+ raise pg_error
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,11 @@
1
+ module EventSource
2
+ module Postgres
3
+ class Read
4
+ include EventSource::Read
5
+
6
+ def configure(batch_size: nil, precedence: nil, session: nil)
7
+ Get.configure(self, batch_size: batch_size, precedence: precedence, session: session)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,120 @@
1
+ module EventSource
2
+ module Postgres
3
+ class Session
4
+ include Log::Dependency
5
+
6
+ def self.settings
7
+ Settings.names
8
+ end
9
+
10
+ settings.each do |s|
11
+ setting s
12
+ end
13
+
14
+ attr_accessor :connection
15
+
16
+ def self.build(settings: nil)
17
+ new.tap do |instance|
18
+ settings ||= Settings.instance
19
+ settings.set(instance)
20
+ end
21
+ end
22
+
23
+ def self.configure(receiver, session: nil, attr_name: nil)
24
+ attr_name ||= :session
25
+
26
+ instance = session || build
27
+ receiver.public_send "#{attr_name}=", instance
28
+ instance
29
+ end
30
+
31
+ def connect
32
+ logger.trace "Connecting to database"
33
+
34
+ if connected?
35
+ logger.debug { "Already connected. A new connection will not be built." }
36
+ return
37
+ end
38
+
39
+ logger.debug { "Not connected. A new connection will be built." }
40
+ connection = self.class.build_connection(self)
41
+ self.connection = connection
42
+
43
+ logger.debug { "Connected to database" }
44
+
45
+ connection
46
+ end
47
+
48
+ def self.build_connection(instance)
49
+ settings = instance.settings
50
+ logger.trace { "Building new connection to database (Settings: #{LogText.settings(settings).inspect})" }
51
+
52
+ connection = PG::Connection.open(settings)
53
+ connection.type_map_for_results = PG::BasicTypeMapForResults.new(connection)
54
+
55
+ logger.trace { "Built new connection to database (Settings: #{LogText.settings(settings).inspect})" }
56
+
57
+ connection
58
+ end
59
+
60
+ def connected?
61
+ !connection.nil? && connection.status == PG::CONNECTION_OK
62
+ end
63
+ alias :open? :connected?
64
+
65
+ def close
66
+ connection.close
67
+ connection = nil
68
+ end
69
+
70
+ def reset
71
+ connection.reset
72
+ end
73
+
74
+ def settings
75
+ settings = {}
76
+ self.class.settings.each do |s|
77
+ val = public_send(s)
78
+ settings[s] = val unless val.nil?
79
+ end
80
+ settings
81
+ end
82
+
83
+ def execute(statement, params=nil)
84
+ unless connected?
85
+ connect
86
+ end
87
+
88
+ if params.nil?
89
+ connection.exec(statement)
90
+ else
91
+ connection.exec_params(statement, params)
92
+ end
93
+ end
94
+
95
+ def transaction(&blk)
96
+ unless connected?
97
+ connect
98
+ end
99
+
100
+ connection.transaction(&blk)
101
+ end
102
+
103
+ def self.logger
104
+ @logger ||= Log.get self
105
+ end
106
+
107
+ module LogText
108
+ def self.settings(settings)
109
+ s = settings.dup
110
+
111
+ if s.has_key?(:password)
112
+ s[:password] = '(hidden)'
113
+ end
114
+
115
+ s
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,30 @@
1
+ module EventSource
2
+ module Postgres
3
+ class Settings < ::Settings
4
+ def self.instance
5
+ @instance ||= build
6
+ end
7
+
8
+ def self.data_source
9
+ 'settings/event_source_postgres.json'
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
+ end
29
+ end
30
+ end
@@ -0,0 +1,11 @@
1
+ module EventSource
2
+ module Postgres
3
+ class Write
4
+ include EventSource::Write
5
+
6
+ def configure(session: nil)
7
+ Put.configure(self, session: session)
8
+ end
9
+ end
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: evt-event_source-postgres
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.10.0.2
5
+ platform: ruby
6
+ authors:
7
+ - The Eventide Project
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-12-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: evt-event_source
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-cycle
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: evt-settings
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
+ description: " "
98
+ email: opensource@eventide-project.org
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - lib/event_source/loader.rb
104
+ - lib/event_source/postgres.rb
105
+ - lib/event_source/postgres/controls.rb
106
+ - lib/event_source/postgres/controls/category.rb
107
+ - lib/event_source/postgres/controls/event_data.rb
108
+ - lib/event_source/postgres/controls/put.rb
109
+ - lib/event_source/postgres/controls/stream.rb
110
+ - lib/event_source/postgres/controls/stream_name.rb
111
+ - lib/event_source/postgres/get.rb
112
+ - lib/event_source/postgres/get/select_statement.rb
113
+ - lib/event_source/postgres/log.rb
114
+ - lib/event_source/postgres/put.rb
115
+ - lib/event_source/postgres/read.rb
116
+ - lib/event_source/postgres/session.rb
117
+ - lib/event_source/postgres/settings.rb
118
+ - lib/event_source/postgres/write.rb
119
+ homepage: https://github.com/eventide-project/event-source-postgres
120
+ licenses:
121
+ - MIT
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: 2.3.3
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 2.5.2
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: Event source client for PostgreSQL
143
+ test_files: []