Repository navigation
Expand file tree
/
Copy pathconnection.rb
More file actions
181 lines (145 loc) · 5.74 KB
/
Copy pathconnection.rb
File metadata and controls
181 lines (145 loc) · 5.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# frozen_string_literal: true
# auto-generated by sqlc - do not edit
module PgCodegen
# State kept on every PG::Connection the first time it is used. The connection's own settings
# (type_map_for_results / _for_queries, field_name_type) are left to the application and other libraries
# sharing it: the generated queries pass their type maps with every call.
module ConnectionState
def self.extended(client) = client.instance_variable_set(:@owner_pid, ::Process.pid)
# A forked child (Puma / Sidekiq / Resque worker) lets go of every inherited session the generated code has
# used, also ones another thread had checked out at fork time and ones it never uses: its exit would end
# them for the parent.
def self.after_fork = ::ObjectSpace.each_object(self) { |client| client.release_parent_socket if client.inherited? }
module ForkHook
def _fork
pid = super
ConnectionState.after_fork if pid.zero?
pid
end
end
::Process.singleton_class.prepend(ForkHook)
attr_accessor :query_types, :result_types
def typed(result)
result.type_map = result_types
result.field_name_type = :string
result
end
# Opened by the parent of a forked process (Puma / Sidekiq workers): its socket is the parent's session.
def inherited? = @owner_pid != ::Process.pid
# Points the inherited socket at /dev/null, so closing or resetting it here does not end the parent's session.
def detach_from_parent
release_parent_socket
ensure
@owner_pid = ::Process.pid
end
def release_parent_socket
socket_io.reopen(::IO::NULL)
rescue ::StandardError # already closed
nil
end
# connection_pool closes the connections a forked child inherits, also ones closed before (which would raise).
def close
return if finished?
detach_from_parent if inherited?
super
end
def finish
return if finished?
detach_from_parent if inherited?
super
end
# Names of the statements prepared on this connection; forgotten when the server session changes
# (PG::Connection#reset after a failover / terminated backend).
def prepared_statements
if @prepared_statements_pid != backend_pid
@prepared_statements = ::Set[]
@stale_statements = ::Set[]
@prepared_statements_pid = backend_pid
end
@prepared_statements
end
# Statements whose result type changed under them (ALTER TABLE ... TYPE in a migration): deallocated before
# they are prepared again.
def stale_statements
prepared_statements
@stale_statements
end
# Set inside #transaction: a lost connection is then not reconnected until the transaction block ends.
attr_accessor :in_transaction
end
# Raised when the query object yielded by #transaction is used after the transaction ended.
class TransactionEnded < ::StandardError; end
# The connection of one transaction, for the query object #transaction yields: its queries stay in the
# transaction from any fiber or thread (an Enumerator, an Async task, a Thread), one at a time, and raise
# TransactionEnded once the transaction is over.
class TransactionConnection
def initialize(client)
@client = client
@mutex = ::Thread::Mutex.new
end
def with
return yield(client) if @mutex.owned?
@mutex.synchronize { yield client }
end
def synchronize(&block) = with { block.call }
# While the block of a nested transaction runs, other fibers / threads can use the connection too.
def unlocked
return yield unless @mutex.owned?
@mutex.unlock
begin
yield
ensure
@mutex.lock
end
end
# Waits for a query of another fiber / thread to finish.
def close
return @client = nil if @mutex.owned?
@mutex.synchronize { @client = nil }
end
private
def client = @client || raise(TransactionEnded, 'the transaction that yielded this query object has ended')
end
# Result decoder for types created after a connection was set up (a migration run while the application is
# up): looked up once per connection, their arrays become Arrays of Strings, other types Strings.
# A module, so connection.rb loads before `pg` does.
module NewTypes
def self.map(client, known) = (@map ||= ::Class.new(::PG::TypeMapInRuby) { include NewTypes }).new(client, known)
def self.decoder(oid, category, delimiter)
return ::PG::TextDecoder::String.new(oid: oid) unless category == 'A'
::PG::TextDecoder::Array.new(oid: oid, delimiter: delimiter, elements_type: ::PG::TextDecoder::String.new)
end
def initialize(client, known)
super()
@client = client
@known = known
@decoders = {}
end
def typecast_result_value(result, tuple, field)
value = super
return value if value.nil? || result.fformat(field) != 0
decoder = decoder(result.ftype(field))
decoder ? decoder.decode(value) : value
end
private
def decoder(oid)
@decoders.fetch(oid) do
row = @client.exec("SELECT typcategory, typdelim FROM pg_type WHERE oid = #{Integer(oid)}").tap do |types|
types.type_map = ::PG::TypeMapAllStrings.new
types.field_name_type = :string
end.first
decoder = row && NewTypes.decoder(oid, row['typcategory'], row['typdelim'])
@known.add_coder(decoder) if decoder # later results no longer come here
@decoders[oid] = decoder
end
rescue ::PG::Error # the connection is busy (e.g. COPY): the value stays a String
nil
end
end
ISOLATION_LEVELS = {
read_uncommitted: 'READ UNCOMMITTED',
read_committed: 'READ COMMITTED',
repeatable_read: 'REPEATABLE READ',
serializable: 'SERIALIZABLE'
}.freeze
end