pgx 1.0.1
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 +7 -0
- data/lib/pgx.rb +464 -0
- metadata +47 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7795026fac56ac284b9a0d68eedb9428c6484b03
|
4
|
+
data.tar.gz: 570fbd7c002ad55e4b7f06dacad93a4f399d8afe
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 099637afbd1eaa729e890ee47b8d03545a7bcb62bb6d1e7db898a00614da7b6311b3341217eae48cb7aed475d21a57bda42f1657e5a16367c6a11e53ace26e90
|
7
|
+
data.tar.gz: f3d5e351210936ad2fadd526e8cc2333f4447b7013aef23719a3eeb85450914792d87112db6bb651cb19342d7ac0c26ac7a5f17a34ec41c384524d53f80a1185
|
data/lib/pgx.rb
ADDED
@@ -0,0 +1,464 @@
|
|
1
|
+
module PGx
|
2
|
+
require 'pg'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
# report all these error fieldcodes on errors
|
6
|
+
# these are all the fieldcodes error_field() can report
|
7
|
+
# see http://deveiate.org/code/pg/PG/Result.html#method-i-error_field
|
8
|
+
PGx.const_set('ERROR_FIELDCODES',
|
9
|
+
SEVERITY: PG::Result::PG_DIAG_SEVERITY,
|
10
|
+
SQLSTATE: PG::Result::PG_DIAG_SQLSTATE,
|
11
|
+
MESSAGE_PRIMARY: PG::Result::PG_DIAG_MESSAGE_PRIMARY,
|
12
|
+
MESSAGE_DETAIL: PG::Result::PG_DIAG_MESSAGE_DETAIL,
|
13
|
+
MESSAGE_HINT: PG::Result::PG_DIAG_MESSAGE_HINT,
|
14
|
+
STATEMENT_POSITION: PG::Result::PG_DIAG_STATEMENT_POSITION,
|
15
|
+
INTERNAL_POSITION: PG::Result::PG_DIAG_INTERNAL_POSITION,
|
16
|
+
INTERNAL_QUERY: PG::Result::PG_DIAG_INTERNAL_QUERY,
|
17
|
+
CONTEXT: PG::Result::PG_DIAG_CONTEXT,
|
18
|
+
SOURCE_FILE: PG::Result::PG_DIAG_SOURCE_FILE,
|
19
|
+
SOURCE_LINE: PG::Result::PG_DIAG_SOURCE_LINE,
|
20
|
+
SOURCE_FUNCTION: PG::Result::PG_DIAG_SOURCE_FUNCTION
|
21
|
+
)
|
22
|
+
|
23
|
+
# all of our connection parameters and their defaults
|
24
|
+
PGx.const_set('CONNECT_ARGS',
|
25
|
+
logger: lambda {
|
26
|
+
log = Logger.new(STDOUT)
|
27
|
+
log.level = Logger::DEBUG
|
28
|
+
return log
|
29
|
+
},
|
30
|
+
connect_init: lambda { |con| }, # noop
|
31
|
+
connect_retry_sleep: 60,
|
32
|
+
method_try_count_max: 3,
|
33
|
+
method_retriable_error_states: [
|
34
|
+
'40001', # serialization_failure
|
35
|
+
'40P01' # deadlock_detected
|
36
|
+
]
|
37
|
+
)
|
38
|
+
|
39
|
+
# @author Kiriakos Georgiou, http://www.mockbites.com/about
|
40
|
+
# Extends the base class PG::Connection methods by adding logging and
|
41
|
+
# retrying capabilities (synchronous methods only.)
|
42
|
+
# The arguments and method behaviors are
|
43
|
+
# identical, except for the 'new' method.
|
44
|
+
# @see http://www.mockbites.com/articles/tech/pgx pgx documentation
|
45
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html Base class methods
|
46
|
+
class PGx::Connection < PG::Connection
|
47
|
+
|
48
|
+
# @param [Logger] logger: a logger object, or a lambda returning the
|
49
|
+
# logger object. Note that a lambda returning the logger is
|
50
|
+
# preferable because it makes logging of the log object itself
|
51
|
+
# easier on the eyes. This parameter can be a single logger
|
52
|
+
# object, or an array of logger objects in case you want to log to
|
53
|
+
# multiple logs (eg: stdout and a log file.)
|
54
|
+
# @param [Proc] connect_init: a Proc that contains code that will
|
55
|
+
# always be executed every time a new connection is established.
|
56
|
+
# It is also executed after a reset().
|
57
|
+
# May be useful for things such as setting the search_path.
|
58
|
+
# @param [Proc, Integer] connect_retry_sleep: a Proc or Integer.
|
59
|
+
# If a Proc is passed, the Proc will be called with one argument,
|
60
|
+
# try_count, which starts at 1 and increments by one with every
|
61
|
+
# unccesseful connection attempt. The Proc should implement the
|
62
|
+
# algorithm for sleeping between connection attempts.
|
63
|
+
# If an Integer is passed, it indicates how long to sleep between
|
64
|
+
# connection attempts, in seconds.
|
65
|
+
# If nil is passed, no attempts to reconnect will be made, it
|
66
|
+
# simply raises an exception.
|
67
|
+
# @param [Integer] method_try_count_max: the maximum number of times a
|
68
|
+
# single method, or transaction block, can be tried
|
69
|
+
# @param [Array<String>] method_retriable_error_states: the set of
|
70
|
+
# error states for which a single method, or transaction block,
|
71
|
+
# should be retried
|
72
|
+
# @param ... The rest of the arguments are the same as the base
|
73
|
+
# class method arguments
|
74
|
+
# @see http://deveiate.org/code/pg/PG/Connection.html#method-c-new
|
75
|
+
# Base class method.
|
76
|
+
# @example
|
77
|
+
# log = Logger.new('/tmp/debug.log');
|
78
|
+
# log.level = Logger::DEBUG
|
79
|
+
# connect_sleeper = lambda { |try_count|
|
80
|
+
# sleep [try_count ** 2, 120].min
|
81
|
+
# }
|
82
|
+
# con = PGx::Connection.new(
|
83
|
+
# logger: log,
|
84
|
+
# connect_retry_sleep: connect_sleeper,
|
85
|
+
# dbname: 'test'
|
86
|
+
# )
|
87
|
+
def initialize(*args)
|
88
|
+
connect_args = PGx.const_get('CONNECT_ARGS')
|
89
|
+
(our_args, parent_args) = extract_our_args(connect_args, args)
|
90
|
+
|
91
|
+
log = arg_with_default(connect_args, our_args, :logger)
|
92
|
+
@logger = [ log.is_a?(Proc) ? log.call : log ].flatten
|
93
|
+
@connect_init = arg_with_default(connect_args, our_args, :connect_init)
|
94
|
+
@connect_retry_sleep = arg_with_default(connect_args, our_args, :connect_retry_sleep)
|
95
|
+
@method_try_count_max = arg_with_default(connect_args, our_args, :method_try_count_max)
|
96
|
+
@method_retriable_error_states = arg_with_default(connect_args, our_args, :method_retriable_error_states)
|
97
|
+
|
98
|
+
# gets set in connect_log_and_try() to the connection handle
|
99
|
+
# needed by reset() to be able to execute the connect_init proc
|
100
|
+
@connection = nil
|
101
|
+
|
102
|
+
# always include invalid_sql_statement_name (required for prepared statements to work after re-connects)
|
103
|
+
@method_retriable_error_states.push('26000') unless @method_retriable_error_states.include?('26000')
|
104
|
+
|
105
|
+
@in_transaction_block = false
|
106
|
+
|
107
|
+
@prepared_statements = {} # hash of all statements ever prepared, key = statement name, value = sql
|
108
|
+
@prepared_live = {} # keeps track which prepared statements are currently prepared
|
109
|
+
@transaction_reprepare = nil # placeholder for statement to be prepared before transaction retry
|
110
|
+
|
111
|
+
@connect_proc = lambda {
|
112
|
+
@prepared_live = {} # reset on connect
|
113
|
+
connect_log_and_try(
|
114
|
+
lambda { super(*parent_args) },
|
115
|
+
our_args,
|
116
|
+
parent_args
|
117
|
+
)
|
118
|
+
}
|
119
|
+
connect_loop()
|
120
|
+
end
|
121
|
+
|
122
|
+
# @example
|
123
|
+
# con.transaction do
|
124
|
+
# con.exec "create temp table mytable(x text)"
|
125
|
+
# con.exec "insert into mytable(x) values('a')"
|
126
|
+
# con.exec "insert into mytable(x) values('b')"
|
127
|
+
# end
|
128
|
+
def transaction(*args)
|
129
|
+
@in_transaction_block = true
|
130
|
+
sql_log_and_try(lambda { super }, __method__, args)
|
131
|
+
@in_transaction_block = false
|
132
|
+
end
|
133
|
+
|
134
|
+
# @example
|
135
|
+
# con.exec "insert into mytable(x) values('test value')"
|
136
|
+
# con.exec 'insert into mytable(x) values($1)', ['test value']
|
137
|
+
def exec(*args)
|
138
|
+
sql_log_and_try(lambda { super }, __method__, args)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Alias for exec
|
142
|
+
def query(*args)
|
143
|
+
sql_log_and_try(lambda { super }, __method__, args)
|
144
|
+
end
|
145
|
+
|
146
|
+
# @example
|
147
|
+
# con.exec_params 'select * from mytable where x = $1 limit $2', [1, 100] do |rs|
|
148
|
+
# puts rs.fields.collect { |fname| "%-15s" % [fname] }.join('')
|
149
|
+
# rs.values.collect { |row|
|
150
|
+
# puts row.collect { |col| "%-15s" % [col] }.join('')
|
151
|
+
# }
|
152
|
+
# end
|
153
|
+
def exec_params(*args)
|
154
|
+
sql_log_and_try(lambda { super }, __method__, args)
|
155
|
+
end
|
156
|
+
|
157
|
+
# @example
|
158
|
+
# con.prepare 'sql1', 'select * from mytable limit $1'
|
159
|
+
# con.exec_prepared 'sql1', [100] do |rs|
|
160
|
+
# puts rs.fields.collect { |fname| "%-15s" % [fname] }.join('')
|
161
|
+
# rs.values.collect { |row|
|
162
|
+
# puts row.collect { |col| "%-15s" % [col] }.join('')
|
163
|
+
# }
|
164
|
+
# end
|
165
|
+
def prepare(*args)
|
166
|
+
# it's possible to call prepare() on an statement that is already prepared
|
167
|
+
# if you have a prepare() inside a transaction, and the transaction is retried
|
168
|
+
# thus we check if it's already live
|
169
|
+
s = args[0]
|
170
|
+
if @prepared_live.has_key?(s)
|
171
|
+
log_debug %Q{prepared statement #{s} is already live, skipping re-preparing it}
|
172
|
+
else
|
173
|
+
sql_log_and_try(lambda { super }, __method__, args)
|
174
|
+
@prepared_statements[s] = args[1]
|
175
|
+
@prepared_live[s] = true
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# @example
|
180
|
+
# con.prepare 'sql1', 'select * from mytable limit $1'
|
181
|
+
# con.exec_prepared 'sql1', [100] do |rs|
|
182
|
+
# puts rs.fields.collect { |fname| "%-15s" % [fname] }.join('')
|
183
|
+
# rs.values.collect { |row|
|
184
|
+
# puts row.collect { |col| "%-15s" % [col] }.join('')
|
185
|
+
# }
|
186
|
+
# end
|
187
|
+
def exec_prepared(*args)
|
188
|
+
sql_log_and_try(lambda { super }, __method__, args)
|
189
|
+
end
|
190
|
+
|
191
|
+
def cancel(*args)
|
192
|
+
sql_log_and_try(lambda { super }, __method__, args)
|
193
|
+
end
|
194
|
+
|
195
|
+
def close(*args)
|
196
|
+
sql_log_and_try(lambda { super }, __method__, args)
|
197
|
+
end
|
198
|
+
|
199
|
+
def finish(*args)
|
200
|
+
sql_log_and_try(lambda { super }, __method__, args)
|
201
|
+
end
|
202
|
+
|
203
|
+
def reset(*args)
|
204
|
+
sql_log_and_try(lambda { super }, __method__, args)
|
205
|
+
@prepared_live = {} # reset on connect
|
206
|
+
@connect_init.call(@connection)
|
207
|
+
end
|
208
|
+
|
209
|
+
def wait_for_notify(*args)
|
210
|
+
sql_log_and_try(lambda { super }, __method__, args)
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
# returns the value of an argument (symbol) from our_args,
|
216
|
+
# or if it's not defined, it returns the default value
|
217
|
+
def arg_with_default(all_our_possible_args, our_args, symbol)
|
218
|
+
return our_args.has_key?(symbol) ? our_args[symbol] : all_our_possible_args[symbol]
|
219
|
+
end
|
220
|
+
|
221
|
+
# extract our arguments defined in all_our_possible_args from args
|
222
|
+
# returns an array containing our arguments (hash) and the aguments
|
223
|
+
# for the parent method
|
224
|
+
def extract_our_args(all_our_possible_args, args)
|
225
|
+
our_args = {}
|
226
|
+
if args[0].is_a?(Hash)
|
227
|
+
all_our_possible_args.each_key do |x|
|
228
|
+
our_args[x] = args[0].delete(x) if args[0].has_key?(x)
|
229
|
+
end
|
230
|
+
args.shift if args[0].empty?
|
231
|
+
end
|
232
|
+
return [our_args, args]
|
233
|
+
end
|
234
|
+
|
235
|
+
# Returns true or false depending on whether the connection is OK or
|
236
|
+
# not. This has to be executed right after one of the above public
|
237
|
+
# methods, since it doesn't really ping the server on its own.
|
238
|
+
def connected?
|
239
|
+
begin
|
240
|
+
return(status() == PG::CONNECTION_OK ? true : false)
|
241
|
+
rescue PGError => e
|
242
|
+
return false
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Will not return until a database connection has been established.
|
247
|
+
def connect_loop
|
248
|
+
@connect_proc.call
|
249
|
+
end
|
250
|
+
|
251
|
+
# Given a function name string and an arguments array, it returns a
|
252
|
+
# string representing the function call. This is used in the logging
|
253
|
+
# functions.
|
254
|
+
def function_call_string(func, args)
|
255
|
+
sprintf "%s.%s(%s)",
|
256
|
+
self.class,
|
257
|
+
func.to_s,
|
258
|
+
( args.is_a?(Array) ? args : [args] ).map { |x|
|
259
|
+
if x.is_a?(Hash) and x.empty?
|
260
|
+
nil
|
261
|
+
else
|
262
|
+
# Inspect does not expand \n, \r and \t, so we expand them
|
263
|
+
# ourselves via gsubs in order to get more readable logging output.
|
264
|
+
x.inspect.gsub('\n', "\n").gsub('\r', "\r").gsub('\t', "\t")
|
265
|
+
end
|
266
|
+
}.join(', ')
|
267
|
+
end
|
268
|
+
|
269
|
+
# sanitize the values for certain pgx-specific arguments so they don't clog the logs
|
270
|
+
# returns the sanitized arg hash
|
271
|
+
def sanitize_our_connect_args(arg_hash)
|
272
|
+
arg_hash.merge(arg_hash) do |k, ov|
|
273
|
+
case k
|
274
|
+
when :logger
|
275
|
+
ov.is_a?(Proc) ? ov : '...'
|
276
|
+
else
|
277
|
+
ov
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# sanitize parent arguments, eg: blank the password so it's not in the logs
|
283
|
+
def sanitize_parent_connect_args(parent_args)
|
284
|
+
if parent_args.empty?
|
285
|
+
return parent_args
|
286
|
+
elsif parent_args.length == 1
|
287
|
+
if parent_args[0].is_a?(Hash) # hash method
|
288
|
+
return [ parent_args[0].merge(parent_args[0]) { |k, ov| k == :password ? '...' : ov } ]
|
289
|
+
end
|
290
|
+
if parent_args[0].is_a?(String) # string method
|
291
|
+
return [ parent_args[0].gsub(/(password\s*=\s*)\S+/, '\1...') ]
|
292
|
+
end
|
293
|
+
else # positional arguments method
|
294
|
+
return [ parent_args.map.with_index { |x, i| i == 6 ? '...' : x } ]
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# merge as follows:
|
299
|
+
# hash and hash -> hash
|
300
|
+
# hash and array -> array
|
301
|
+
def merge_connect_args(our_args, parent_args)
|
302
|
+
if our_args.empty?
|
303
|
+
return parent_args
|
304
|
+
elsif parent_args.empty?
|
305
|
+
return our_args
|
306
|
+
elsif parent_args.length == 1 and parent_args[0].is_a?(Hash)
|
307
|
+
return our_args.merge(parent_args[0]) # connection hash style
|
308
|
+
else
|
309
|
+
return [our_args].concat(parent_args)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# log and retry the connect/new method
|
314
|
+
def connect_log_and_try(caller_super, our_args, parent_args)
|
315
|
+
try_count = 0
|
316
|
+
all_args = merge_connect_args( sanitize_our_connect_args(our_args),
|
317
|
+
sanitize_parent_connect_args(parent_args) )
|
318
|
+
begin
|
319
|
+
log_debug function_call_string('connect', all_args)
|
320
|
+
@connection = caller_super.call # run the parent 'pg' function, new()
|
321
|
+
log_debug 'calling connection initialization proc'
|
322
|
+
@connect_init.call(@connection)
|
323
|
+
rescue PGError => e
|
324
|
+
try_count = try_count + 1
|
325
|
+
log_error %Q{#{e.class}, #{e.message.chomp}}
|
326
|
+
if @connect_retry_sleep.nil? or e.message =~ /^FATAL/
|
327
|
+
raise # just raise the error, do not retry to connect
|
328
|
+
elsif @connect_retry_sleep.is_a?(Proc)
|
329
|
+
@connect_retry_sleep.call(try_count)
|
330
|
+
elsif @connect_retry_sleep.is_a?(Integer)
|
331
|
+
sleep(@connect_retry_sleep)
|
332
|
+
end
|
333
|
+
retry
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# log and retry statement related methods
|
338
|
+
def sql_log_and_try(caller_super, caller_method, args)
|
339
|
+
try_count = 0
|
340
|
+
begin # re-prepare prepared statements after a failed transaction
|
341
|
+
transaction_reprepare(caller_method) # just prior to retrying the transaction
|
342
|
+
log_debug function_call_string(caller_method, args)
|
343
|
+
caller_super.call # run the parent 'pg' function
|
344
|
+
rescue PGError => e
|
345
|
+
if connected? # if we are connected, it's an SQL related error
|
346
|
+
if not method_in_transaction_block(caller_method) # do not log errors and retry
|
347
|
+
try_count = try_count + 1 # methods within a transaction
|
348
|
+
log_error(get_error_fields(e) + "\n" + get_error_message(e)) # block because the transaction
|
349
|
+
error_handlers(e, caller_method, args) # itself will log the error and
|
350
|
+
retry if sql_retriable?(e, try_count) # retry the transaction block
|
351
|
+
elsif caller_method.to_s == 'exec_prepared' # exec_prepared failed within a transaction
|
352
|
+
@transaction_reprepare = args[0] # make a note to re-prepare before retrying
|
353
|
+
end # the transaction
|
354
|
+
else # not connected
|
355
|
+
log_warn 'bad database connection'
|
356
|
+
connect_loop()
|
357
|
+
retry
|
358
|
+
end
|
359
|
+
raise # raise to caller if we can't retry
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# special handling of certain error states (not in a transaction)
|
364
|
+
def error_handlers(pgerr, caller_method, args)
|
365
|
+
begin
|
366
|
+
state = pgerr.result.error_field(PG::Result::PG_DIAG_SQLSTATE)
|
367
|
+
rescue PGError => e
|
368
|
+
return # just return if we can't get the state; lost connection?
|
369
|
+
end
|
370
|
+
|
371
|
+
case state
|
372
|
+
when '26000'
|
373
|
+
reprepare_statement(args[0]) if caller_method.to_s == 'exec_prepared'
|
374
|
+
end # case
|
375
|
+
end
|
376
|
+
|
377
|
+
def transaction_reprepare(caller_method)
|
378
|
+
if caller_method.to_s == 'transaction' and not @transaction_reprepare.nil?
|
379
|
+
reprepare_statement(@transaction_reprepare)
|
380
|
+
@transaction_reprepare = nil
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def reprepare_statement(name)
|
385
|
+
if @prepared_statements.has_key?(name)
|
386
|
+
log_debug "Re-preparing statement '#{name}'"
|
387
|
+
prepare(name, @prepared_statements[name])
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
# return true if any method call, except transaction(), is called
|
392
|
+
# from within a transaction() block
|
393
|
+
def method_in_transaction_block(method)
|
394
|
+
return(method.to_s != 'transaction' and @in_transaction_block)
|
395
|
+
end
|
396
|
+
|
397
|
+
# determine if a single statement or transaction block that resulted
|
398
|
+
# in an error can be retried
|
399
|
+
def sql_retriable?(pgerr, try_count)
|
400
|
+
begin
|
401
|
+
state = pgerr.result.error_field(PG::Result::PG_DIAG_SQLSTATE)
|
402
|
+
rescue PGError => e
|
403
|
+
return true # retry if we can't get the state; lost connection?
|
404
|
+
end
|
405
|
+
|
406
|
+
if @method_retriable_error_states.include?(state)
|
407
|
+
if try_count < @method_try_count_max
|
408
|
+
log_debug(%Q{State #{state} is retriable, retrying...})
|
409
|
+
return true
|
410
|
+
else
|
411
|
+
log_debug(%Q{State #{state} is retriable, but the upper limit (#{
|
412
|
+
@method_try_count_max}) of tries has been reached})
|
413
|
+
return false
|
414
|
+
end
|
415
|
+
else
|
416
|
+
return false
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
# error reporting helper
|
421
|
+
def get_error_fields(pgerr)
|
422
|
+
PGx.const_get('ERROR_FIELDCODES').map { |key, value|
|
423
|
+
begin
|
424
|
+
errval = pgerr.result.error_field(value)
|
425
|
+
rescue PGError => e
|
426
|
+
errval = '?'
|
427
|
+
end
|
428
|
+
%Q{#{key}=#{errval}}
|
429
|
+
}.join(', ')
|
430
|
+
end
|
431
|
+
|
432
|
+
# error reporting helper
|
433
|
+
def get_error_message(pgerr)
|
434
|
+
begin
|
435
|
+
errval = pgerr.result.error_message
|
436
|
+
rescue PGError => e
|
437
|
+
errval = '?'
|
438
|
+
end
|
439
|
+
errval.chomp
|
440
|
+
end
|
441
|
+
|
442
|
+
# logging helper
|
443
|
+
def log_error(str)
|
444
|
+
@logger.each { |log| log.error { str } }
|
445
|
+
end
|
446
|
+
|
447
|
+
# logging helper
|
448
|
+
def log_warn(str)
|
449
|
+
@logger.each { |log| log.warn { str } }
|
450
|
+
end
|
451
|
+
|
452
|
+
# logging helper
|
453
|
+
def log_info(str)
|
454
|
+
@logger.each { |log| log.info { str } }
|
455
|
+
end
|
456
|
+
|
457
|
+
# logging helper
|
458
|
+
def log_debug(str)
|
459
|
+
@logger.each { |log| log.debug { str } }
|
460
|
+
end
|
461
|
+
|
462
|
+
end # PGx::Connection class
|
463
|
+
|
464
|
+
end # module PGx
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pgx
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kiriakos Georgiou
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-10 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: |2
|
14
|
+
A pure ruby "thin" extension of pg that provides logging,
|
15
|
+
transaction retrying, and database reconnecting functionality.
|
16
|
+
email: kg.pgx@olympiakos.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/pgx.rb
|
22
|
+
homepage: http://www.mockbites.com/articles/tech/pgx
|
23
|
+
licenses:
|
24
|
+
- MIT
|
25
|
+
metadata: {}
|
26
|
+
post_install_message:
|
27
|
+
rdoc_options: []
|
28
|
+
require_paths:
|
29
|
+
- lib
|
30
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.9.3
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
requirements: []
|
41
|
+
rubyforge_project:
|
42
|
+
rubygems_version: 2.4.5.1
|
43
|
+
signing_key:
|
44
|
+
specification_version: 4
|
45
|
+
summary: A thin extension of pg
|
46
|
+
test_files: []
|
47
|
+
has_rdoc:
|