pg_eventstore 0.2.2 → 0.2.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: b8c492f46288f9c4c5d3912cad3aba0664bf91693a2273785654e6a96f634668
4
- data.tar.gz: 6cbc3ab33fd7d9ed71ae8664ad95b784fffd07ead7d56a7046f481f36eecc531
3
+ metadata.gz: fd2524ab67ce1fa009de14a032cdc3d922051420bc2c02e933cb63ee245db02d
4
+ data.tar.gz: f49867dab08825770aae44e3e1bec38a8144504e3bea2e6618ce47a0d6e8cb54
5
5
  SHA512:
6
- metadata.gz: efe92ab2610b9bbf0e33fc6ed98a7104737ad9f02a7217651b7f7354a7c3b25008a01033b71f7057279dd5ad21846cab3fbd4408941b4f6a6015532651ba40e6
7
- data.tar.gz: cf31608048782d34203643b02374ff52afbed8760e0dbbff7e5026c6c518d26f489c396412f8ec88a52fbaf1dbd22a5788feb1873bfc1c59b7cfce8080b8e4b2
6
+ metadata.gz: e9925413067ca47159cbd5902f2716bbd9e5fe1e0c63aa7397f077c95878bacc2da57cd65f32160a0e1d70d855e101d16fb6d5aa5b537c9630c4290449db8c16
7
+ data.tar.gz: 65add6ce6e11ebe0634cec3949709ad3c8d42b105ba9325c5d6d45c45bf2339c0e47f9286dbb71edc8e1c99636aa449adc34926f4a6d57c695c50655609d5451
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
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
+
1
5
  ## [0.2.2] - 2023-12-14
2
6
 
3
7
  - Fix `pg_eventstore:drop` rake task to also drop `migrations` table
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgEventstore
4
- VERSION = "0.2.2"
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.2
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