pg_eventstore 0.2.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8d3f73730a86f5a8d8849da2faa8c2e24a937e5d062b51928ae3f4034030f54
4
- data.tar.gz: 47c6381424341a8b5b86e7dd8e66c0f090d8485755b6e7fc79a9d5e33c5cbde1
3
+ metadata.gz: fd2524ab67ce1fa009de14a032cdc3d922051420bc2c02e933cb63ee245db02d
4
+ data.tar.gz: f49867dab08825770aae44e3e1bec38a8144504e3bea2e6618ce47a0d6e8cb54
5
5
  SHA512:
6
- metadata.gz: fc542c87250be7e2c840ae526cca7dcd9dee55bf7069572b7fc669564c4173a0ec4334a5714dfb92f874cf8717a087ba474ae4aa7697c36dc541883346a12383
7
- data.tar.gz: deedd9c173935973dc7330365acd21e4125d5abb9e23975af3a19fc1c6549c3a9944ae0420753034af5d5ed7b60c793d90e90ced0b866ec410a42cc6306400dc
6
+ metadata.gz: e9925413067ca47159cbd5902f2716bbd9e5fe1e0c63aa7397f077c95878bacc2da57cd65f32160a0e1d70d855e101d16fb6d5aa5b537c9630c4290449db8c16
7
+ data.tar.gz: 65add6ce6e11ebe0634cec3949709ad3c8d42b105ba9325c5d6d45c45bf2339c0e47f9286dbb71edc8e1c99636aa449adc34926f4a6d57c695c50655609d5451
data/CHANGELOG.md CHANGED
@@ -1,8 +1,16 @@
1
- ## [0.2.1] - 2023-12-13
1
+ ## [0.2.3] - 2023-12-18
2
+
3
+ - Fix performance when searching by event type only(under certain circumstances PosetgreSQL was picking wrong index).
4
+
5
+ ## [0.2.2] - 2023-12-14
6
+
7
+ - Fix `pg_eventstore:drop` rake task to also drop `migrations` table
8
+
9
+ ## [0.2.1] - 2023-12-14
2
10
 
3
11
  Under certain circumstances `PG::TRSerializationFailure` exception wasn't retried. Adjust connection's states list to fix that.
4
12
 
5
- ## [0.2.0] - 2023-12-13
13
+ ## [0.2.0] - 2023-12-14
6
14
 
7
15
  - Improve performance by reviewing indexes
8
16
  - Implement migrations
@@ -0,0 +1,4 @@
1
+ CREATE INDEX idx_events_global_position_including_type ON public.events USING btree (global_position) INCLUDE (type);
2
+ COMMENT ON INDEX idx_events_global_position_including_type IS 'Usually "type" column has low distinct values. Thus, composit index by "type" and "global_position" columns may not be picked by Query Planner properly. Improve an index by "global_position" by including "type" column which allows Query Planner to perform better by picking the correct index.';
3
+
4
+ DROP INDEX idx_events_global_position;
@@ -27,7 +27,11 @@ module PgEventstore
27
27
  # @raise [PgEventstore::WrongExpectedRevisionError]
28
28
  def append_to_stream(stream, events_or_event, options: {}, middlewares: nil)
29
29
  result =
30
- Commands::Append.new(queries(middlewares(middlewares))).call(stream, *events_or_event, options: options)
30
+ Commands::Append.new(
31
+ Queries.new(
32
+ streams: stream_queries, events: event_queries(middlewares(middlewares)), transactions: transaction_queries
33
+ )
34
+ ).call(stream, *events_or_event, options: options)
31
35
  events_or_event.is_a?(Array) ? result : result.first
32
36
  end
33
37
 
@@ -43,7 +47,7 @@ module PgEventstore
43
47
  #
44
48
  # @return the result of the given block
45
49
  def multiple(&blk)
46
- Commands::Multiple.new(queries(middlewares)).call(&blk)
50
+ Commands::Multiple.new(Queries.new(transactions: transaction_queries)).call(&blk)
47
51
  end
48
52
 
49
53
  # Read events from the specific stream or from "all" stream.
@@ -101,7 +105,7 @@ module PgEventstore
101
105
  # @raise [PgEventstore::StreamNotFoundError]
102
106
  def read(stream, options: {}, middlewares: nil)
103
107
  Commands::Read.
104
- new(queries(middlewares(middlewares))).
108
+ new(Queries.new(streams: stream_queries, events: event_queries(middlewares(middlewares)))).
105
109
  call(stream, options: { max_count: config.max_count }.merge(options))
106
110
  end
107
111
 
@@ -120,10 +124,20 @@ module PgEventstore
120
124
  PgEventstore.connection(config.name)
121
125
  end
122
126
 
127
+ # @return [PgEventstore::StreamQueries]
128
+ def stream_queries
129
+ StreamQueries.new(connection)
130
+ end
131
+
132
+ # @return [PgEventstore::TransactionQueries]
133
+ def transaction_queries
134
+ TransactionQueries.new(connection)
135
+ end
136
+
123
137
  # @param middlewares [Array<Object<#serialize, #deserialize>>]
124
- # @return [PgEventstore::Queries]
125
- def queries(middlewares)
126
- Queries.new(
138
+ # @return [PgEventstore::EventQueries]
139
+ def event_queries(middlewares)
140
+ EventQueries.new(
127
141
  connection,
128
142
  EventSerializer.new(middlewares),
129
143
  PgResultDeserializer.new(middlewares, config.event_class_resolver)
@@ -14,14 +14,14 @@ module PgEventstore
14
14
  def call(stream, *events, options: {})
15
15
  raise SystemStreamError, stream if stream.system?
16
16
 
17
- queries.transaction do
18
- stream = queries.find_or_create_stream(stream)
17
+ queries.transactions.transaction do
18
+ stream = queries.streams.find_or_create_stream(stream)
19
19
  revision = stream.stream_revision
20
20
  assert_expected_revision!(revision, options[:expected_revision]) if options[:expected_revision]
21
21
  events.map.with_index(1) do |event, index|
22
- queries.insert(stream, prepared_event(event, revision + index))
22
+ queries.events.insert(stream, prepared_event(event, revision + index))
23
23
  end.tap do
24
- queries.update_stream_revision(stream, revision + events.size)
24
+ queries.streams.update_stream_revision(stream, revision + events.size)
25
25
  end
26
26
  end
27
27
  end
@@ -5,7 +5,7 @@ module PgEventstore
5
5
  # @!visibility private
6
6
  class Multiple < AbstractCommand
7
7
  def call(&blk)
8
- queries.transaction do
8
+ queries.transactions.transaction do
9
9
  yield
10
10
  end
11
11
  end
@@ -15,9 +15,9 @@ module PgEventstore
15
15
  # @return [Array<PgEventstore::Event>]
16
16
  # @raise [PgEventstore::StreamNotFoundError]
17
17
  def call(stream, options: {})
18
- stream = queries.find_stream(stream) || raise(StreamNotFoundError, stream) unless stream.all_stream?
18
+ stream = queries.streams.find_stream(stream) || raise(StreamNotFoundError, stream) unless stream.all_stream?
19
19
 
20
- queries.stream_events(stream, options)
20
+ queries.events.stream_events(stream, options)
21
21
  end
22
22
  end
23
23
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pg_eventstore/query_builders/events_filtering_query'
4
+
5
+ module PgEventstore
6
+ # @!visibility private
7
+ class EventQueries
8
+ attr_reader :connection, :serializer, :deserializer
9
+ private :connection, :serializer, :deserializer
10
+
11
+ # @param connection [PgEventstore::Connection]
12
+ # @param serializer [PgEventstore::EventSerializer]
13
+ # @param deserializer [PgEventstore::PgResultDeserializer]
14
+ def initialize(connection, serializer, deserializer)
15
+ @connection = connection
16
+ @serializer = serializer
17
+ @deserializer = deserializer
18
+ end
19
+
20
+ # @see PgEventstore::Client#read for more info
21
+ # @param stream [PgEventstore::Stream]
22
+ # @param options [Hash]
23
+ # @return [Array<PgEventstore::Event>]
24
+ def stream_events(stream, options)
25
+ exec_params = events_filtering(stream, options).to_exec_params
26
+ pg_result = connection.with do |conn|
27
+ conn.exec_params(*exec_params)
28
+ end
29
+ deserializer.deserialize_many(pg_result)
30
+ end
31
+
32
+ # @param stream [PgEventstore::Stream] persisted stream
33
+ # @param event [PgEventstore::Event]
34
+ # @return [PgEventstore::Event]
35
+ def insert(stream, event)
36
+ serializer.serialize(event)
37
+
38
+ attributes = event.options_hash.slice(:id, :type, :data, :metadata, :stream_revision, :link_id).compact
39
+ attributes[:stream_id] = stream.id
40
+
41
+ sql = <<~SQL
42
+ INSERT INTO events (#{attributes.keys.join(', ')})
43
+ VALUES (#{(1..attributes.values.size).map { |n| "$#{n}" }.join(', ')})
44
+ RETURNING *
45
+ SQL
46
+
47
+ pg_result = connection.with do |conn|
48
+ conn.exec_params(sql, attributes.values)
49
+ end
50
+ deserializer.without_middlewares.deserialize_one(pg_result).tap do |persisted_event|
51
+ persisted_event.stream = stream
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ # @param stream [PgEventstore::Stream]
58
+ # @param options [Hash]
59
+ # @param offset [Integer]
60
+ # @return [PgEventstore::EventsFilteringQuery]
61
+ def events_filtering(stream, options, offset: 0)
62
+ return QueryBuilders::EventsFiltering.all_stream_filtering(options, offset: offset) if stream.all_stream?
63
+
64
+ QueryBuilders::EventsFiltering.specific_stream_filtering(stream, options, offset: offset)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # @!visibility private
5
+ class StreamQueries
6
+ attr_reader :connection
7
+ private :connection
8
+
9
+ # @param connection [PgEventstore::Connection]
10
+ def initialize(connection)
11
+ @connection = connection
12
+ end
13
+
14
+ # Finds a stream in the database by the given Stream object
15
+ # @param stream [PgEventstore::Stream]
16
+ # @return [PgEventstore::Stream, nil] persisted stream
17
+ def find_stream(stream)
18
+ builder =
19
+ SQLBuilder.new.
20
+ from('streams').
21
+ where('streams.context = ? AND streams.stream_name = ? AND streams.stream_id = ?', *stream.to_a).
22
+ limit(1)
23
+ pg_result = connection.with do |conn|
24
+ conn.exec_params(*builder.to_exec_params)
25
+ end
26
+ deserialize(pg_result) if pg_result.ntuples == 1
27
+ end
28
+
29
+ # @param stream [PgEventstore::Stream]
30
+ # @return [PgEventstore::RawStream] persisted stream
31
+ def create_stream(stream)
32
+ create_sql = <<~SQL
33
+ INSERT INTO streams (context, stream_name, stream_id) VALUES ($1, $2, $3) RETURNING *
34
+ SQL
35
+ pg_result = connection.with do |conn|
36
+ conn.exec_params(create_sql, stream.to_a)
37
+ end
38
+ deserialize(pg_result)
39
+ end
40
+
41
+ # @return [PgEventstore::Stream] persisted stream
42
+ def find_or_create_stream(stream)
43
+ find_stream(stream) || create_stream(stream)
44
+ end
45
+
46
+ # @param stream [PgEventstore::Stream] persisted stream
47
+ # @return [void]
48
+ def update_stream_revision(stream, revision)
49
+ connection.with do |conn|
50
+ conn.exec_params(<<~SQL, [revision, stream.id])
51
+ UPDATE streams SET stream_revision = $1 WHERE id = $2
52
+ SQL
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ # @param pg_result [PG::Result]
59
+ # @return [PgEventstore::Stream, nil]
60
+ def deserialize(pg_result)
61
+ PgEventstore::Stream.new(**pg_result.to_a.first.transform_keys(&:to_sym))
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # @!visibility private
5
+ class TransactionQueries
6
+ attr_reader :connection
7
+ private :connection
8
+
9
+ # @param connection [PgEventstore::Connection]
10
+ def initialize(connection)
11
+ @connection = connection
12
+ end
13
+
14
+ # @return [void]
15
+ def transaction
16
+ connection.with do |conn|
17
+ # We are inside a transaction already - no need to start another one
18
+ if [PG::PQTRANS_ACTIVE, PG::PQTRANS_INTRANS].include?(conn.transaction_status)
19
+ next yield
20
+ end
21
+
22
+ conn.transaction do
23
+ conn.exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
24
+ yield
25
+ end
26
+ end
27
+ rescue PG::TRSerializationFailure, PG::TRDeadlockDetected => e
28
+ retry if [PG::PQTRANS_IDLE, PG::PQTRANS_UNKNOWN].include?(e.connection.transaction_status)
29
+ raise
30
+ end
31
+ end
32
+ end
@@ -1,127 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'query_builders/events_filtering_query'
3
+ require_relative 'queries/transaction_queries'
4
+ require_relative 'queries/event_queries'
5
+ require_relative 'queries/stream_queries'
4
6
 
5
7
  module PgEventstore
6
8
  # @!visibility private
7
9
  class Queries
8
- attr_reader :connection, :serializer, :deserializer
9
- private :connection, :serializer, :deserializer
10
-
11
- # @param connection [PgEventstore::Connection]
12
- # @param serializer [PgEventstore::EventSerializer]
13
- # @param deserializer [PgEventstore::PgResultDeserializer]
14
- def initialize(connection, serializer, deserializer)
15
- @connection = connection
16
- @serializer = serializer
17
- @deserializer = deserializer
18
- end
19
-
20
- # @return [void]
21
- def transaction
22
- connection.with do |conn|
23
- # We are inside a transaction already - no need to start another one
24
- if [PG::PQTRANS_ACTIVE, PG::PQTRANS_INTRANS].include?(conn.transaction_status)
25
- next yield
26
- end
27
-
28
- conn.transaction do
29
- conn.exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
30
- yield
31
- end
32
- end
33
- rescue PG::TRSerializationFailure, PG::TRDeadlockDetected => e
34
- retry if [PG::PQTRANS_IDLE, PG::PQTRANS_UNKNOWN].include?(e.connection.transaction_status)
35
- raise
36
- end
37
-
38
- # Finds a stream in the database by the given Stream object
39
- # @param stream [PgEventstore::Stream]
40
- # @return [PgEventstore::Stream, nil] persisted stream
41
- def find_stream(stream)
42
- builder =
43
- SQLBuilder.new.
44
- from('streams').
45
- where('streams.context = ? AND streams.stream_name = ? AND streams.stream_id = ?', *stream.to_a).
46
- limit(1)
47
- pg_result = connection.with do |conn|
48
- conn.exec_params(*builder.to_exec_params)
49
- end
50
- PgEventstore::Stream.new(**pg_result.to_a.first.transform_keys(&:to_sym)) if pg_result.ntuples == 1
51
- end
52
-
53
- # @param stream [PgEventstore::Stream]
54
- # @return [PgEventstore::RawStream] persisted stream
55
- def create_stream(stream)
56
- create_sql = <<~SQL
57
- INSERT INTO streams (context, stream_name, stream_id) VALUES ($1, $2, $3) RETURNING *
58
- SQL
59
- pg_result = connection.with do |conn|
60
- conn.exec_params(create_sql, stream.to_a)
61
- end
62
- PgEventstore::Stream.new(**pg_result.to_a.first.transform_keys(&:to_sym))
63
- end
64
-
65
- # @return [PgEventstore::Stream] persisted stream
66
- def find_or_create_stream(stream)
67
- find_stream(stream) || create_stream(stream)
68
- end
69
-
70
- # @see PgEventstore::Client#read for more info
71
- # @param stream [PgEventstore::Stream]
72
- # @param options [Hash]
73
- # @return [Array<PgEventstore::Event>]
74
- def stream_events(stream, options)
75
- exec_params = events_filtering(stream, options).to_exec_params
76
- pg_result = connection.with do |conn|
77
- conn.exec_params(*exec_params)
78
- end
79
- deserializer.deserialize_many(pg_result)
80
- end
81
-
82
- # @param stream [PgEventstore::Stream] persisted stream
83
- # @param event [PgEventstore::Event]
84
- # @return [PgEventstore::Event]
85
- def insert(stream, event)
86
- serializer.serialize(event)
87
-
88
- attributes = event.options_hash.slice(:id, :type, :data, :metadata, :stream_revision, :link_id).compact
89
- attributes[:stream_id] = stream.id
90
-
91
- sql = <<~SQL
92
- INSERT INTO events (#{attributes.keys.join(', ')})
93
- VALUES (#{(1..attributes.values.size).map { |n| "$#{n}" }.join(', ')})
94
- RETURNING *
95
- SQL
96
-
97
- pg_result = connection.with do |conn|
98
- conn.exec_params(sql, attributes.values)
99
- end
100
- deserializer.without_middlewares.deserialize_one(pg_result).tap do |persisted_event|
101
- persisted_event.stream = stream
102
- end
103
- end
104
-
105
- # @param stream [PgEventstore::Stream] persisted stream
106
- # @return [void]
107
- def update_stream_revision(stream, revision)
108
- connection.with do |conn|
109
- conn.exec_params(<<~SQL, [revision, stream.id])
110
- UPDATE streams SET stream_revision = $1 WHERE id = $2
111
- SQL
112
- end
113
- end
114
-
115
- private
116
-
117
- # @param stream [PgEventstore::Stream]
118
- # @param options [Hash]
119
- # @param offset [Integer]
120
- # @return [PgEventstore::EventsFilteringQuery]
121
- def events_filtering(stream, options, offset: 0)
122
- return QueryBuilders::EventsFiltering.all_stream_filtering(options, offset: offset) if stream.all_stream?
123
-
124
- QueryBuilders::EventsFiltering.specific_stream_filtering(stream, options, offset: offset)
125
- end
10
+ include Extensions::OptionsExtension
11
+
12
+ # @!attribute events
13
+ # @return [PgEventstore::EventQueries, nil]
14
+ attribute(:events)
15
+ # @!attribute streams
16
+ # @return [PgEventstore::StreamQueries, nil]
17
+ attribute(:streams)
18
+ # @!attribute transactions
19
+ # @return [PgEventstore::TransactionQueries, nil]
20
+ attribute(:transactions)
126
21
  end
127
22
  end
@@ -53,6 +53,7 @@ namespace :pg_eventstore do
53
53
  conn.exec <<~SQL
54
54
  DROP TABLE IF EXISTS public.events;
55
55
  DROP TABLE IF EXISTS public.streams;
56
+ DROP TABLE IF EXISTS public.migrations;
56
57
  DROP EXTENSION IF EXISTS "uuid-ossp";
57
58
  DROP EXTENSION IF EXISTS pgcrypto;
58
59
  SQL
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgEventstore
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.3"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_eventstore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Dzyzenko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-12-14 00:00:00.000000000 Z
11
+ date: 2023-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -55,6 +55,7 @@ files:
55
55
  - db/initial/tables.sql
56
56
  - db/migrations/0_improve_all_stream_indexes.sql
57
57
  - db/migrations/1_improve_specific_stream_indexes.sql
58
+ - db/migrations/2_adjust_global_position_index.sql
58
59
  - docs/appending_events.md
59
60
  - docs/configuration.md
60
61
  - docs/events_and_streams.md
@@ -78,6 +79,9 @@ files:
78
79
  - lib/pg_eventstore/middleware.rb
79
80
  - lib/pg_eventstore/pg_result_deserializer.rb
80
81
  - lib/pg_eventstore/queries.rb
82
+ - lib/pg_eventstore/queries/event_queries.rb
83
+ - lib/pg_eventstore/queries/stream_queries.rb
84
+ - lib/pg_eventstore/queries/transaction_queries.rb
81
85
  - lib/pg_eventstore/query_builders/events_filtering_query.rb
82
86
  - lib/pg_eventstore/rspec/has_option_matcher.rb
83
87
  - lib/pg_eventstore/sql_builder.rb