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 +4 -4
- data/CHANGELOG.md +10 -2
- data/db/migrations/2_adjust_global_position_index.sql +4 -0
- data/lib/pg_eventstore/client.rb +20 -6
- data/lib/pg_eventstore/commands/append.rb +4 -4
- data/lib/pg_eventstore/commands/multiple.rb +1 -1
- data/lib/pg_eventstore/commands/read.rb +2 -2
- data/lib/pg_eventstore/queries/event_queries.rb +67 -0
- data/lib/pg_eventstore/queries/stream_queries.rb +64 -0
- data/lib/pg_eventstore/queries/transaction_queries.rb +32 -0
- data/lib/pg_eventstore/queries.rb +14 -119
- data/lib/pg_eventstore/tasks/setup.rake +1 -0
- data/lib/pg_eventstore/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd2524ab67ce1fa009de14a032cdc3d922051420bc2c02e933cb63ee245db02d
|
4
|
+
data.tar.gz: f49867dab08825770aae44e3e1bec38a8144504e3bea2e6618ce47a0d6e8cb54
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9925413067ca47159cbd5902f2716bbd9e5fe1e0c63aa7397f077c95878bacc2da57cd65f32160a0e1d70d855e101d16fb6d5aa5b537c9630c4290449db8c16
|
7
|
+
data.tar.gz: 65add6ce6e11ebe0634cec3949709ad3c8d42b105ba9325c5d6d45c45bf2339c0e47f9286dbb71edc8e1c99636aa449adc34926f4a6d57c695c50655609d5451
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,16 @@
|
|
1
|
-
## [0.2.
|
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
|
+
## [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;
|
data/lib/pg_eventstore/client.rb
CHANGED
@@ -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(
|
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(
|
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(
|
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::
|
125
|
-
def
|
126
|
-
|
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
|
@@ -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 '
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
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.
|
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-
|
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
|