cipherstash-pg 1.0.0.beta.1-arm64-darwin-22 → 1.0.0.beta.4-arm64-darwin-22
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 +4 -4
- data/.appveyor.yml +42 -0
- data/.gems +6 -0
- data/.gemtest +0 -0
- data/.github/workflows/binary-gems.yml +117 -0
- data/.github/workflows/source-gem.yml +137 -0
- data/.gitignore +19 -0
- data/.hgsigs +34 -0
- data/.hgtags +41 -0
- data/.irbrc +23 -0
- data/.pryrc +23 -0
- data/.tm_properties +21 -0
- data/.travis.yml +49 -0
- data/Gemfile +3 -3
- data/Gemfile.lock +45 -0
- data/{History.rdoc → History.md} +168 -153
- data/README.ja.md +266 -0
- data/README.md +272 -0
- data/Rakefile +65 -104
- data/Rakefile.cross +298 -0
- data/certs/larskanis-2023.pem +24 -0
- data/cipherstash-pg.gemspec +0 -0
- data/lib/2.7/pg_ext.bundle +0 -0
- data/lib/3.0/pg_ext.bundle +0 -0
- data/lib/3.1/pg_ext.bundle +0 -0
- data/lib/3.2/pg_ext.bundle +0 -0
- data/lib/cipherstash-pg/basic_type_map_based_on_result.rb +11 -0
- data/lib/cipherstash-pg/basic_type_map_for_queries.rb +113 -0
- data/lib/cipherstash-pg/basic_type_map_for_results.rb +30 -0
- data/lib/cipherstash-pg/basic_type_registry.rb +206 -0
- data/lib/cipherstash-pg/binary_decoder.rb +21 -0
- data/lib/cipherstash-pg/coder.rb +82 -0
- data/lib/cipherstash-pg/connection.rb +467 -0
- data/lib/cipherstash-pg/constants.rb +3 -0
- data/lib/cipherstash-pg/exceptions.rb +19 -0
- data/lib/cipherstash-pg/result.rb +22 -0
- data/lib/cipherstash-pg/text_decoder.rb +43 -0
- data/lib/cipherstash-pg/text_encoder.rb +67 -0
- data/lib/cipherstash-pg/tuple.rb +24 -0
- data/lib/cipherstash-pg/type_map_by_column.rb +11 -0
- data/lib/cipherstash-pg/version.rb +3 -0
- data/lib/cipherstash-pg.rb +56 -11
- data/lib/libpq.5.dylib +0 -0
- data/misc/openssl-pg-segfault.rb +15 -25
- data/misc/postgres/Rakefile +13 -20
- data/misc/postgres/lib/postgres.rb +10 -14
- data/misc/ruby-pg/Rakefile +13 -20
- data/misc/ruby-pg/lib/ruby/pg.rb +10 -14
- data/rakelib/task_extension.rb +17 -31
- data/sample/array_insert.rb +7 -20
- data/sample/async_api.rb +54 -96
- data/sample/async_copyto.rb +20 -35
- data/sample/async_mixed.rb +22 -50
- data/sample/check_conn.rb +8 -20
- data/sample/copydata.rb +18 -68
- data/sample/copyfrom.rb +26 -78
- data/sample/copyto.rb +10 -16
- data/sample/cursor.rb +9 -19
- data/sample/disk_usage_report.rb +89 -174
- data/sample/issue-119.rb +45 -93
- data/sample/losample.rb +48 -66
- data/sample/minimal-testcase.rb +6 -17
- data/sample/notify_wait.rb +21 -67
- data/sample/pg_statistics.rb +100 -281
- data/sample/replication_monitor.rb +119 -218
- data/sample/test_binary_values.rb +14 -30
- data/sample/wal_shipper.rb +199 -431
- data/sample/warehouse_partitions.rb +157 -307
- data/translation/.po4a-version +7 -0
- data/translation/po/all.pot +875 -0
- data/translation/po/ja.po +868 -0
- data/translation/po4a.cfg +9 -0
- metadata +50 -28
- data/README.ja.rdoc +0 -13
- data/README.rdoc +0 -233
- data/lib/pg/basic_type_map_based_on_result.rb +0 -47
- data/lib/pg/basic_type_map_for_queries.rb +0 -193
- data/lib/pg/basic_type_map_for_results.rb +0 -81
- data/lib/pg/basic_type_registry.rb +0 -301
- data/lib/pg/binary_decoder.rb +0 -23
- data/lib/pg/coder.rb +0 -104
- data/lib/pg/connection.rb +0 -878
- data/lib/pg/constants.rb +0 -12
- data/lib/pg/exceptions.rb +0 -18
- data/lib/pg/result.rb +0 -43
- data/lib/pg/text_decoder.rb +0 -46
- data/lib/pg/text_encoder.rb +0 -59
- data/lib/pg/tuple.rb +0 -30
- data/lib/pg/type_map_by_column.rb +0 -16
- data/lib/pg/version.rb +0 -4
- data/lib/pg.rb +0 -55
@@ -0,0 +1,467 @@
|
|
1
|
+
require("cipherstash-pg") unless defined? CipherStashPG
|
2
|
+
require("uri")
|
3
|
+
require("io/wait")
|
4
|
+
require("socket")
|
5
|
+
class CipherStashPG::Connection
|
6
|
+
CONNECT_ARGUMENT_ORDER = ["host", "port", "options", "tty", "dbname", "user", "password"]
|
7
|
+
|
8
|
+
def self.quote_connstr(value)
|
9
|
+
return (("'" + value.to_s.gsub(/[\\']/) { |m| ("\\" + m) }) + "'")
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.connect_hash_to_string(hash)
|
13
|
+
hash.map { |k, v| "#{k}=#{quote_connstr(v)}" }.join(" ")
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse_connect_args(*args)
|
17
|
+
hash_arg = args.last.is_a?(Hash) ? (args.pop.transform_keys(&:to_sym)) : ({})
|
18
|
+
iopts = {}
|
19
|
+
if (args.length == 1) then
|
20
|
+
case args.first
|
21
|
+
when URI, /=/, /:\/\// then
|
22
|
+
conn_string = args.first.to_s
|
23
|
+
iopts = CipherStashPG::Connection.conninfo_parse(conn_string).each_with_object({}) do |h, o|
|
24
|
+
o[h[:keyword].to_sym] = h[:val] if h[:val]
|
25
|
+
end
|
26
|
+
else
|
27
|
+
iopts[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
|
28
|
+
end
|
29
|
+
else
|
30
|
+
max = CONNECT_ARGUMENT_ORDER.length
|
31
|
+
if (args.length > max) then
|
32
|
+
raise(ArgumentError, ("Extra positional parameter %d: %p" % [(max + 1), args[max]]))
|
33
|
+
end
|
34
|
+
CONNECT_ARGUMENT_ORDER.zip(args) { |(k, v)| iopts[k.to_sym] = v if v }
|
35
|
+
iopts.delete(:tty)
|
36
|
+
end
|
37
|
+
iopts.merge!(hash_arg)
|
38
|
+
unless iopts[:fallback_application_name] then
|
39
|
+
iopts[:fallback_application_name] = $0.sub(/^(.{30}).{4,}(.{30})$/) { (($1 + "...") + $2) }
|
40
|
+
end
|
41
|
+
return connect_hash_to_string(iopts)
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
str = self.to_s
|
46
|
+
str[-1, 0] = if finished? then
|
47
|
+
" finished"
|
48
|
+
else
|
49
|
+
stats = []
|
50
|
+
if (status != CONNECTION_OK) then
|
51
|
+
(stats << " status=#{CipherStashPG.constants.grep(/CONNECTION_/).find do |c|
|
52
|
+
(CipherStashPG.const_get(c) == status)
|
53
|
+
end}")
|
54
|
+
end
|
55
|
+
if (transaction_status != CipherStashPG::PQTRANS_IDLE) then
|
56
|
+
(stats << " transaction_status=#{CipherStashPG.constants.grep(/PQTRANS_/).find do |c|
|
57
|
+
(CipherStashPG.const_get(c) == transaction_status)
|
58
|
+
end}")
|
59
|
+
end
|
60
|
+
(stats << " nonblocking=#{isnonblocking}") if isnonblocking
|
61
|
+
if respond_to?(:pipeline_status) and (pipeline_status != CipherStashPG::PQ_PIPELINE_OFF) then
|
62
|
+
(stats << " pipeline_status=#{CipherStashPG.constants.grep(/PQ_PIPELINE_/).find do |c|
|
63
|
+
(CipherStashPG.const_get(c) == pipeline_status)
|
64
|
+
end}")
|
65
|
+
end
|
66
|
+
if (get_client_encoding != "UTF8") then
|
67
|
+
(stats << " client_encoding=#{get_client_encoding}")
|
68
|
+
end
|
69
|
+
unless type_map_for_results.is_a?(CipherStashPG::TypeMapAllStrings) then
|
70
|
+
(stats << " type_map_for_results=#{type_map_for_results.to_s}")
|
71
|
+
end
|
72
|
+
unless type_map_for_queries.is_a?(CipherStashPG::TypeMapAllStrings) then
|
73
|
+
(stats << " type_map_for_queries=#{type_map_for_queries.to_s}")
|
74
|
+
end
|
75
|
+
if encoder_for_put_copy_data then
|
76
|
+
(stats << " encoder_for_put_copy_data=#{encoder_for_put_copy_data.to_s}")
|
77
|
+
end
|
78
|
+
if decoder_for_get_copy_data then
|
79
|
+
(stats << " decoder_for_get_copy_data=#{decoder_for_get_copy_data.to_s}")
|
80
|
+
end
|
81
|
+
" host=#{host} port=#{port} user=#{user}#{stats.join}"
|
82
|
+
end
|
83
|
+
return str
|
84
|
+
end
|
85
|
+
|
86
|
+
def copy_data(sql, coder = nil)
|
87
|
+
if nonblocking? then
|
88
|
+
raise(CipherStashPG::NotInBlockingMode.new("copy_data can not be used in nonblocking mode", :connection => (self)))
|
89
|
+
end
|
90
|
+
res = exec(sql)
|
91
|
+
case res.result_status
|
92
|
+
when PGRES_COPY_IN then
|
93
|
+
begin
|
94
|
+
(if coder then
|
95
|
+
old_coder = self.encoder_for_put_copy_data
|
96
|
+
self.encoder_for_put_copy_data = coder
|
97
|
+
end
|
98
|
+
yield(res))
|
99
|
+
rescue Exception => err
|
100
|
+
errmsg = ("%s while copy data: %s" % [err.class.name, err.message])
|
101
|
+
begin
|
102
|
+
put_copy_end(errmsg)
|
103
|
+
rescue CipherStashPG::Error
|
104
|
+
# do nothing
|
105
|
+
end
|
106
|
+
discard_results
|
107
|
+
raise(err)
|
108
|
+
else
|
109
|
+
(begin
|
110
|
+
put_copy_end
|
111
|
+
rescue CipherStashPG::Error => err
|
112
|
+
raise(CipherStashPG::LostCopyState.new("#{err} (probably by executing another SQL query while running a COPY command)", :connection => (self)))
|
113
|
+
end
|
114
|
+
get_last_result)
|
115
|
+
ensure
|
116
|
+
self.encoder_for_put_copy_data = old_coder if coder
|
117
|
+
end
|
118
|
+
when PGRES_COPY_OUT then
|
119
|
+
begin
|
120
|
+
(if coder then
|
121
|
+
old_coder = self.decoder_for_get_copy_data
|
122
|
+
self.decoder_for_get_copy_data = coder
|
123
|
+
end
|
124
|
+
yield(res))
|
125
|
+
rescue Exception
|
126
|
+
cancel
|
127
|
+
discard_results
|
128
|
+
raise
|
129
|
+
else
|
130
|
+
(res = get_last_result
|
131
|
+
if res then
|
132
|
+
if (res.result_status != PGRES_COMMAND_OK) then
|
133
|
+
discard_results
|
134
|
+
raise(CipherStashPG::NotAllCopyDataRetrieved.new("Not all COPY data retrieved", :connection => (self)))
|
135
|
+
end
|
136
|
+
else
|
137
|
+
discard_results
|
138
|
+
raise(CipherStashPG::LostCopyState.new("Lost COPY state (probably by executing another SQL query while running a COPY command)", :connection => (self)))
|
139
|
+
end
|
140
|
+
res)
|
141
|
+
ensure
|
142
|
+
self.decoder_for_get_copy_data = old_coder if coder
|
143
|
+
end
|
144
|
+
else
|
145
|
+
raise(ArgumentError, "SQL command is no COPY statement: #{sql}")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class << self
|
150
|
+
define_method(:isthreadsafe, &CipherStashPG.method(:isthreadsafe))
|
151
|
+
end
|
152
|
+
|
153
|
+
def transaction
|
154
|
+
(rollback = false
|
155
|
+
exec("BEGIN")
|
156
|
+
yield(self))
|
157
|
+
rescue Exception
|
158
|
+
rollback = true
|
159
|
+
cancel if (transaction_status == CipherStashPG::PQTRANS_ACTIVE)
|
160
|
+
block
|
161
|
+
exec("ROLLBACK")
|
162
|
+
raise
|
163
|
+
ensure
|
164
|
+
exec("COMMIT") unless rollback
|
165
|
+
end
|
166
|
+
|
167
|
+
def conndefaults
|
168
|
+
return self.class.conndefaults
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.conndefaults_hash
|
172
|
+
return self.conndefaults.each_with_object({}) do |info, hash|
|
173
|
+
hash[info[:keyword].to_sym] = info[:val]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def conndefaults_hash
|
178
|
+
return self.class.conndefaults_hash
|
179
|
+
end
|
180
|
+
|
181
|
+
def conninfo_hash
|
182
|
+
return self.conninfo.each_with_object({}) do |info, hash|
|
183
|
+
hash[info[:keyword].to_sym] = info[:val]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
if self.instance_methods.find { |m| (m.to_sym == :ssl_attribute) } then
|
188
|
+
def ssl_attributes
|
189
|
+
ssl_attribute_names.each.with_object({}) { |n, h| h[n] = ssl_attribute(n) }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def get_result
|
194
|
+
block
|
195
|
+
sync_get_result
|
196
|
+
end
|
197
|
+
|
198
|
+
alias :async_get_result :get_result
|
199
|
+
|
200
|
+
def get_copy_data(async = false, decoder = nil)
|
201
|
+
if async then
|
202
|
+
return sync_get_copy_data(async, decoder)
|
203
|
+
else
|
204
|
+
while ((res = sync_get_copy_data(true, decoder)) == false) do
|
205
|
+
socket_io.wait_readable
|
206
|
+
consume_input
|
207
|
+
end
|
208
|
+
return res
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
alias :async_get_copy_data :get_copy_data
|
213
|
+
|
214
|
+
def setnonblocking(enabled)
|
215
|
+
singleton_class.async_send_api = (not enabled)
|
216
|
+
self.flush_data = (not enabled)
|
217
|
+
sync_setnonblocking(true)
|
218
|
+
end
|
219
|
+
|
220
|
+
alias :async_setnonblocking :setnonblocking
|
221
|
+
|
222
|
+
def isnonblocking
|
223
|
+
false
|
224
|
+
end
|
225
|
+
|
226
|
+
alias :async_isnonblocking :isnonblocking
|
227
|
+
|
228
|
+
alias :nonblocking? :isnonblocking
|
229
|
+
|
230
|
+
def put_copy_data(buffer, encoder = nil)
|
231
|
+
until res = sync_put_copy_data(buffer, encoder) do
|
232
|
+
res = flush
|
233
|
+
end
|
234
|
+
if (@calls_to_put_copy_data = (@calls_to_put_copy_data + 1) > 100) then
|
235
|
+
@calls_to_put_copy_data = 0
|
236
|
+
res = flush
|
237
|
+
end
|
238
|
+
res
|
239
|
+
end
|
240
|
+
|
241
|
+
alias :async_put_copy_data :put_copy_data
|
242
|
+
|
243
|
+
def put_copy_end(*args)
|
244
|
+
until sync_put_copy_end(*args) do
|
245
|
+
flush
|
246
|
+
end
|
247
|
+
@calls_to_put_copy_data = 0
|
248
|
+
flush
|
249
|
+
end
|
250
|
+
|
251
|
+
alias :async_put_copy_end :put_copy_end
|
252
|
+
|
253
|
+
if method_defined?(:sync_encrypt_password) then
|
254
|
+
def encrypt_password(password, username, algorithm = nil)
|
255
|
+
algorithm ||= exec("SHOW password_encryption").getvalue(0, 0)
|
256
|
+
sync_encrypt_password(password, username, algorithm)
|
257
|
+
end
|
258
|
+
(alias :async_encrypt_password :encrypt_password)
|
259
|
+
end
|
260
|
+
|
261
|
+
def reset
|
262
|
+
reset_start
|
263
|
+
async_connect_or_reset(:reset_poll)
|
264
|
+
self
|
265
|
+
end
|
266
|
+
|
267
|
+
alias :async_reset :reset
|
268
|
+
|
269
|
+
def cancel
|
270
|
+
(be_pid = backend_pid
|
271
|
+
be_key = backend_key
|
272
|
+
cancel_request = [16, 1234, 5678, be_pid, be_key].pack("NnnNN")
|
273
|
+
if Fiber.respond_to?(:scheduler) and (Fiber.scheduler and RUBY_PLATFORM =~ /mingw|mswin/) then
|
274
|
+
cl = Thread.new(socket_io.remote_address) { |ra| ra.connect }.value
|
275
|
+
begin
|
276
|
+
cl.write_nonblock(cancel_request)
|
277
|
+
rescue IO::WaitReadable, Errno::EINTR
|
278
|
+
cl.wait_writable
|
279
|
+
retry
|
280
|
+
end
|
281
|
+
begin
|
282
|
+
cl.read_nonblock(1)
|
283
|
+
rescue IO::WaitReadable, Errno::EINTR
|
284
|
+
cl.wait_readable
|
285
|
+
retry
|
286
|
+
rescue EOFError
|
287
|
+
# do nothing
|
288
|
+
end
|
289
|
+
else
|
290
|
+
if (RUBY_ENGINE == "truffleruby") then
|
291
|
+
begin
|
292
|
+
cl = socket_io.remote_address.connect
|
293
|
+
rescue NotImplementedError
|
294
|
+
cl2 = Socket.for_fd(socket_io.fileno)
|
295
|
+
cl2.autoclose = false
|
296
|
+
adr = cl2.remote_address
|
297
|
+
if adr.ip? then
|
298
|
+
cl = TCPSocket.new(adr.ip_address, adr.ip_port)
|
299
|
+
cl.autoclose = false
|
300
|
+
else
|
301
|
+
cl = UNIXSocket.new(adr.unix_path)
|
302
|
+
cl.autoclose = false
|
303
|
+
end
|
304
|
+
end
|
305
|
+
cl.write(cancel_request)
|
306
|
+
cl.read(1)
|
307
|
+
else
|
308
|
+
cl = socket_io.remote_address.connect
|
309
|
+
cl.write(cancel_request)
|
310
|
+
cl.read(1)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
cl.close
|
314
|
+
nil)
|
315
|
+
rescue SystemCallError => err
|
316
|
+
err.to_s
|
317
|
+
end
|
318
|
+
|
319
|
+
alias :async_cancel :cancel
|
320
|
+
|
321
|
+
private(def async_connect_or_reset(poll_meth)
|
322
|
+
if timeo = conninfo_hash[:connect_timeout].to_i and (timeo > 0) then
|
323
|
+
timeo = [timeo, 2].max
|
324
|
+
host_count = (conninfo_hash[:host].to_s.count(",") + 1)
|
325
|
+
stop_time = ((timeo * host_count) + Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
326
|
+
end
|
327
|
+
poll_status = CipherStashPG::PGRES_POLLING_WRITING
|
328
|
+
until ((poll_status == CipherStashPG::PGRES_POLLING_OK) or (poll_status == CipherStashPG::PGRES_POLLING_FAILED)) do
|
329
|
+
(if stop_time then
|
330
|
+
timeout = [timeo, (stop_time - Process.clock_gettime(Process::CLOCK_MONOTONIC))].min
|
331
|
+
end
|
332
|
+
event = if ((not timeout) or (timeout >= 0)) then
|
333
|
+
case poll_status
|
334
|
+
when CipherStashPG::PGRES_POLLING_READING then
|
335
|
+
if defined? IO::READABLE then
|
336
|
+
socket_io.wait((IO::READABLE | IO::PRIORITY), timeout)
|
337
|
+
else
|
338
|
+
IO.select([socket_io], nil, [socket_io], timeout)
|
339
|
+
end
|
340
|
+
when CipherStashPG::PGRES_POLLING_WRITING then
|
341
|
+
if defined? IO::WRITABLE then
|
342
|
+
socket_io.wait((IO::WRITABLE | IO::PRIORITY), timeout)
|
343
|
+
else
|
344
|
+
IO.select(nil, [socket_io], [socket_io], timeout)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
unless event then
|
349
|
+
if self.class.send(:host_is_named_pipe?, host) then
|
350
|
+
connhost = "on socket \"#{host}\""
|
351
|
+
else
|
352
|
+
if respond_to?(:hostaddr) then
|
353
|
+
connhost = "at \"#{host}\" (#{hostaddr}), port #{port}"
|
354
|
+
else
|
355
|
+
connhost = "at \"#{host}\", port #{port}"
|
356
|
+
end
|
357
|
+
end
|
358
|
+
raise(CipherStashPG::ConnectionBad.new("connection to server #{connhost} failed: timeout expired", :connection => (self)))
|
359
|
+
end
|
360
|
+
poll_status = send(poll_meth))
|
361
|
+
end
|
362
|
+
unless (status == CipherStashPG::CONNECTION_OK) then
|
363
|
+
msg = error_message
|
364
|
+
finish
|
365
|
+
raise(CipherStashPG::ConnectionBad.new(msg, :connection => (self)))
|
366
|
+
end
|
367
|
+
sync_setnonblocking(true)
|
368
|
+
self.flush_data = true
|
369
|
+
set_default_encoding
|
370
|
+
end)
|
371
|
+
|
372
|
+
class << self
|
373
|
+
def new(*args)
|
374
|
+
conn = connect_to_hosts(*args)
|
375
|
+
if block_given? then
|
376
|
+
begin
|
377
|
+
return yield(conn)
|
378
|
+
ensure
|
379
|
+
conn.finish
|
380
|
+
end
|
381
|
+
end
|
382
|
+
conn
|
383
|
+
end
|
384
|
+
alias :async_connect :new
|
385
|
+
alias :connect :new
|
386
|
+
alias :open :new
|
387
|
+
alias :setdb :new
|
388
|
+
alias :setdblogin :new
|
389
|
+
private(def connect_to_hosts(*args)
|
390
|
+
option_string = parse_connect_args(*args)
|
391
|
+
iopts = CipherStashPG::Connection.conninfo_parse(option_string).each_with_object({}) do |h, o|
|
392
|
+
o[h[:keyword].to_sym] = h[:val] if h[:val]
|
393
|
+
end
|
394
|
+
iopts = CipherStashPG::Connection.conndefaults.each_with_object({}) do |h, o|
|
395
|
+
o[h[:keyword].to_sym] = h[:val] if h[:val]
|
396
|
+
end.merge(iopts)
|
397
|
+
unless iopts[:hostaddr] then
|
398
|
+
if iopts[:host] and ((not iopts[:host].empty?) and (CipherStashPG.library_version >= 100000)) then
|
399
|
+
ihosts = iopts[:host].split(",", -1)
|
400
|
+
iports = iopts[:port].split(",", -1)
|
401
|
+
iports = [nil] if (iports.size == 0)
|
402
|
+
iports = (iports * ihosts.size) if (iports.size == 1)
|
403
|
+
if (iports.size != ihosts.size) then
|
404
|
+
raise(CipherStashPG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts")
|
405
|
+
end
|
406
|
+
dests = ihosts.each_with_index.flat_map do |mhost, idx|
|
407
|
+
if host_is_named_pipe?(mhost) then
|
408
|
+
hostaddrs = [nil]
|
409
|
+
else
|
410
|
+
if Fiber.respond_to?(:scheduler) and (Fiber.scheduler and (RUBY_VERSION < "3.1.")) then
|
411
|
+
hostaddrs = Thread.new do
|
412
|
+
Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [""]
|
413
|
+
end.value
|
414
|
+
else
|
415
|
+
hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [""]
|
416
|
+
end
|
417
|
+
end
|
418
|
+
hostaddrs.map { |hostaddr| [hostaddr, mhost, iports[idx]] }
|
419
|
+
end
|
420
|
+
iopts.merge!(:hostaddr => dests.map { |d| d[0] }.join(","), :host => dests.map { |d| d[1] }.join(","), :port => dests.map { |d| d[2] }.join(","))
|
421
|
+
end
|
422
|
+
end
|
423
|
+
(conn = self.connect_start(iopts) or raise(CipherStashPG::Error, "Unable to create a new connection"))
|
424
|
+
if (conn.status == CipherStashPG::CONNECTION_BAD) then
|
425
|
+
raise(CipherStashPG::ConnectionBad, conn.error_message)
|
426
|
+
end
|
427
|
+
conn.send(:async_connect_or_reset, :connect_poll)
|
428
|
+
conn
|
429
|
+
end)
|
430
|
+
private(def host_is_named_pipe?(host_string)
|
431
|
+
(host_string.empty? or (host_string.start_with?("/") or (host_string.start_with?("@") or (RUBY_PLATFORM =~ /mingw|mswin/ and host_string =~ /\A([\/\\]|\w:[\/\\])/))))
|
432
|
+
end)
|
433
|
+
def ping(*args)
|
434
|
+
if Fiber.respond_to?(:scheduler) and Fiber.scheduler then
|
435
|
+
Thread.new { sync_ping(*args) }.value
|
436
|
+
else
|
437
|
+
sync_ping(*args)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
alias :async_ping :ping
|
441
|
+
REDIRECT_CLASS_METHODS = { :new => ([:async_connect, :sync_connect]), :connect => ([:async_connect, :sync_connect]), :open => ([:async_connect, :sync_connect]), :setdb => ([:async_connect, :sync_connect]), :setdblogin => ([:async_connect, :sync_connect]), :ping => ([:async_ping, :sync_ping]) }
|
442
|
+
REDIRECT_SEND_METHODS = { :isnonblocking => ([:async_isnonblocking, :sync_isnonblocking]), :nonblocking? => ([:async_isnonblocking, :sync_isnonblocking]), :put_copy_data => ([:async_put_copy_data, :sync_put_copy_data]), :put_copy_end => ([:async_put_copy_end, :sync_put_copy_end]), :flush => ([:async_flush, :sync_flush]) }
|
443
|
+
REDIRECT_METHODS = { :exec => ([:async_exec, :sync_exec]), :query => ([:async_exec, :sync_exec]), :exec_params => ([:async_exec_params, :sync_exec_params]), :prepare => ([:async_prepare, :sync_prepare]), :exec_prepared => ([:async_exec_prepared, :sync_exec_prepared]), :describe_portal => ([:async_describe_portal, :sync_describe_portal]), :describe_prepared => ([:async_describe_prepared, :sync_describe_prepared]), :setnonblocking => ([:async_setnonblocking, :sync_setnonblocking]), :get_result => ([:async_get_result, :sync_get_result]), :get_last_result => ([:async_get_last_result, :sync_get_last_result]), :get_copy_data => ([:async_get_copy_data, :sync_get_copy_data]), :reset => ([:async_reset, :sync_reset]), :set_client_encoding => ([:async_set_client_encoding, :sync_set_client_encoding]), :client_encoding= => ([:async_set_client_encoding, :sync_set_client_encoding]), :cancel => ([:async_cancel, :sync_cancel]) }
|
444
|
+
if CipherStashPG::Connection.instance_methods.include?(:async_encrypt_password) then
|
445
|
+
REDIRECT_METHODS.merge!(:encrypt_password => ([:async_encrypt_password, :sync_encrypt_password]))
|
446
|
+
end
|
447
|
+
def async_send_api=(enable)
|
448
|
+
REDIRECT_SEND_METHODS.each do |ali, (async, sync)|
|
449
|
+
undef_method(ali) if method_defined?(ali)
|
450
|
+
alias_method(ali, (enable ? (async) : (sync)))
|
451
|
+
end
|
452
|
+
end
|
453
|
+
def async_api=(enable)
|
454
|
+
self.async_send_api = enable
|
455
|
+
REDIRECT_METHODS.each do |ali, (async, sync)|
|
456
|
+
remove_method(ali) if method_defined?(ali)
|
457
|
+
alias_method(ali, (enable ? (async) : (sync)))
|
458
|
+
end
|
459
|
+
REDIRECT_CLASS_METHODS.each do |ali, (async, sync)|
|
460
|
+
singleton_class.remove_method(ali) if method_defined?(ali)
|
461
|
+
singleton_class.alias_method(ali, (enable ? (async) : (sync)))
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
self.async_api = true
|
467
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require("cipherstash-pg") unless defined? CipherStashPG
|
2
|
+
module CipherStashPG
|
3
|
+
class Error < StandardError
|
4
|
+
def initialize(msg = nil, connection: nil, result: nil)
|
5
|
+
@connection = connection
|
6
|
+
@result = result
|
7
|
+
super(msg)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class NotAllCopyDataRetrieved < CipherStashPG::Error
|
12
|
+
end
|
13
|
+
|
14
|
+
class LostCopyState < CipherStashPG::Error
|
15
|
+
end
|
16
|
+
|
17
|
+
class NotInBlockingMode < CipherStashPG::Error
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require("cipherstash-pg") unless defined? CipherStashPG
|
2
|
+
class CipherStashPG::Result
|
3
|
+
def map_types!(type_map)
|
4
|
+
self.type_map = type_map
|
5
|
+
return self
|
6
|
+
end
|
7
|
+
|
8
|
+
def field_names_as(type)
|
9
|
+
self.field_name_type = type
|
10
|
+
return self
|
11
|
+
end
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
str = self.to_s
|
15
|
+
str[-1, 0] = if cleared? then
|
16
|
+
" cleared"
|
17
|
+
else
|
18
|
+
" status=#{res_status(result_status)} ntuples=#{ntuples} nfields=#{nfields} cmd_tuples=#{cmd_tuples}"
|
19
|
+
end
|
20
|
+
return str
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require("date")
|
2
|
+
require("json")
|
3
|
+
module CipherStashPG
|
4
|
+
module TextDecoder
|
5
|
+
class Date < SimpleDecoder
|
6
|
+
def decode(string, tuple = nil, field = nil)
|
7
|
+
if string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/ then
|
8
|
+
::Date.new($1.to_i, $2.to_i, $3.to_i)
|
9
|
+
else
|
10
|
+
string
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class JSON < SimpleDecoder
|
16
|
+
def decode(string, tuple = nil, field = nil)
|
17
|
+
::JSON.parse(string, :quirks_mode => true)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class TimestampUtc < Timestamp
|
22
|
+
def initialize(params = {})
|
23
|
+
super(params.merge(:flags => (CipherStashPG::Coder::TIMESTAMP_DB_UTC | CipherStashPG::Coder::TIMESTAMP_APP_UTC)))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class TimestampUtcToLocal < Timestamp
|
28
|
+
def initialize(params = {})
|
29
|
+
super(params.merge(:flags => (CipherStashPG::Coder::TIMESTAMP_DB_UTC | CipherStashPG::Coder::TIMESTAMP_APP_LOCAL)))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class TimestampLocal < Timestamp
|
34
|
+
def initialize(params = {})
|
35
|
+
super(params.merge(:flags => (CipherStashPG::Coder::TIMESTAMP_DB_LOCAL | CipherStashPG::Coder::TIMESTAMP_APP_LOCAL)))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
TimestampWithoutTimeZone = TimestampLocal
|
40
|
+
|
41
|
+
TimestampWithTimeZone = Timestamp
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require("json")
|
2
|
+
require("ipaddr")
|
3
|
+
module CipherStashPG
|
4
|
+
module TextEncoder
|
5
|
+
class Date < SimpleEncoder
|
6
|
+
def encode(value)
|
7
|
+
value.respond_to?(:strftime) ? (value.strftime("%Y-%m-%d")) : (value)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class TimestampWithoutTimeZone < SimpleEncoder
|
12
|
+
def encode(value)
|
13
|
+
if value.respond_to?(:strftime) then
|
14
|
+
value.strftime("%Y-%m-%d %H:%M:%S.%N")
|
15
|
+
else
|
16
|
+
value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class TimestampUtc < SimpleEncoder
|
22
|
+
def encode(value)
|
23
|
+
if value.respond_to?(:utc) then
|
24
|
+
value.utc.strftime("%Y-%m-%d %H:%M:%S.%N")
|
25
|
+
else
|
26
|
+
value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class TimestampWithTimeZone < SimpleEncoder
|
32
|
+
def encode(value)
|
33
|
+
if value.respond_to?(:strftime) then
|
34
|
+
value.strftime("%Y-%m-%d %H:%M:%S.%N %:z")
|
35
|
+
else
|
36
|
+
value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class JSON < SimpleEncoder
|
42
|
+
def encode(value)
|
43
|
+
::JSON.generate(value, :quirks_mode => true)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Inet < SimpleEncoder
|
48
|
+
def encode(value)
|
49
|
+
case value
|
50
|
+
when IPAddr then
|
51
|
+
default_prefix = (value.family == Socket::AF_INET) ? (32) : (128)
|
52
|
+
s = value.to_s
|
53
|
+
if value.respond_to?(:prefix) then
|
54
|
+
prefix = value.prefix
|
55
|
+
else
|
56
|
+
range = value.to_range
|
57
|
+
prefix = (default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i)
|
58
|
+
end
|
59
|
+
((s << "/") << prefix.to_s) if (prefix != default_prefix)
|
60
|
+
s
|
61
|
+
else
|
62
|
+
value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require("cipherstash-pg") unless defined? CipherStashPG
|
2
|
+
class CipherStashPG::Tuple
|
3
|
+
def inspect
|
4
|
+
"#<#{self.class} #{self.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")}>"
|
5
|
+
end
|
6
|
+
|
7
|
+
def has_key?(key)
|
8
|
+
field_map.has_key?(key)
|
9
|
+
end
|
10
|
+
|
11
|
+
alias :key? :has_key?
|
12
|
+
|
13
|
+
def keys
|
14
|
+
(field_names or field_map.keys.freeze)
|
15
|
+
end
|
16
|
+
|
17
|
+
def each_key(&block)
|
18
|
+
if fn = field_names then
|
19
|
+
fn.each(&block)
|
20
|
+
else
|
21
|
+
field_map.each_key(&block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require("cipherstash-pg") unless defined? CipherStashPG
|
2
|
+
class CipherStashPG::TypeMapByColumn
|
3
|
+
def oids
|
4
|
+
coders.map { |c| c.oid if c }
|
5
|
+
end
|
6
|
+
|
7
|
+
def inspect
|
8
|
+
type_strings = coders.map { |c| c ? (c.inspect_short) : ("nil") }
|
9
|
+
"#<#{self.class} #{type_strings.join(" ")}>"
|
10
|
+
end
|
11
|
+
end
|