pg_pipeline 0.3.2 → 0.3.4
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/CHANGELOG.md +45 -0
- data/README.md +21 -0
- data/lib/pg_pipeline/bounded_queue.rb +8 -6
- data/lib/pg_pipeline/client.rb +65 -41
- data/lib/pg_pipeline/connection_driver.rb +390 -286
- data/lib/pg_pipeline/pool.rb +325 -206
- data/lib/pg_pipeline/prepared_statement.rb +11 -8
- data/lib/pg_pipeline/request.rb +21 -18
- data/lib/pg_pipeline/runtime/notification.rb +2 -1
- data/lib/pg_pipeline/runtime/semaphore.rb +3 -3
- data/lib/pg_pipeline/runtime/task.rb +14 -8
- data/lib/pg_pipeline/runtime.rb +17 -18
- data/lib/pg_pipeline/server_caps.rb +3 -3
- data/lib/pg_pipeline/session.rb +9 -7
- data/lib/pg_pipeline/session_guard.rb +108 -73
- data/lib/pg_pipeline/transaction.rb +33 -34
- data/lib/pg_pipeline/type_maps.rb +104 -0
- data/lib/pg_pipeline/version.rb +1 -1
- data/lib/pg_pipeline.rb +1 -0
- metadata +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative 'errors'
|
|
4
4
|
|
|
5
5
|
module PgPipeline
|
|
6
6
|
module SessionGuard
|
|
@@ -9,27 +9,27 @@ module PgPipeline
|
|
|
9
9
|
VALID_MODES = %i[default strict].freeze
|
|
10
10
|
ALLOWED_LEADING = %w[select insert update delete merge values with].freeze
|
|
11
11
|
FORBIDDEN_PATTERNS = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
'set_config' => /\bset_config\s*\(/i,
|
|
13
|
+
'setseed' => /\bsetseed\s*\(/i,
|
|
14
|
+
'currval' => /\bcurrval\s*\(/i,
|
|
15
|
+
'lastval' => /\blastval\s*\(/i,
|
|
16
|
+
'session-advisory-lock' => /\bpg_(?:try_)?advisory_lock(?:_shared)?\s*\(/i,
|
|
17
|
+
'session-advisory-unlock' => /\bpg_advisory_unlock(?:_shared|_all)?\s*\(/i,
|
|
18
|
+
'select-into-temp' => /\binto\s+(?:(?:global|local)\s+)?temp(?:orary)?\b/i,
|
|
19
|
+
'select-into-pg-temp' => /\binto\s+(?:table\s+)?pg_temp(?:_\d+)?\s*\./i
|
|
20
20
|
}.freeze
|
|
21
21
|
STRICT_FORBIDDEN = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
'nextval' => /\bnextval\s*\(/i,
|
|
23
|
+
'setval' => /\bsetval\s*\(/i,
|
|
24
|
+
'setseed' => /\bsetseed\s*\(/i,
|
|
25
|
+
'set_config' => /\bset_config\s*\(/i,
|
|
26
|
+
'session-advisory-lock' => /\bpg_(?:try_)?advisory_lock(?:_shared)?\s*\(/i,
|
|
27
|
+
'session-advisory-unlock' => /\bpg_advisory_unlock(?:_shared|_all)?\s*\(/i,
|
|
28
|
+
'pg_export_snapshot' => /\bpg_export_snapshot\s*\(/i
|
|
29
29
|
}.freeze
|
|
30
30
|
PATTERN_PREFILTER = /\binto\b/i
|
|
31
31
|
DOLLAR_QUOTE_TAG = /\A\$(?:(?:[A-Za-z_]|[^\x00-\x7F])(?:[A-Za-z0-9_]|[^\x00-\x7F])*)?\$/
|
|
32
|
-
NEEDS_MASK =
|
|
32
|
+
NEEDS_MASK = %r{['"]|--|/\*|\$(?:(?:[A-Za-z_]|[^\x00-\x7F])(?:[A-Za-z0-9_]|[^\x00-\x7F])*)?\$}
|
|
33
33
|
LEADING_KEYWORD = /\A\s*([a-zA-Z_]+)/
|
|
34
34
|
WHITESPACE_BYTES = [9, 10, 11, 12, 13, 32].freeze
|
|
35
35
|
|
|
@@ -43,7 +43,7 @@ module PgPipeline
|
|
|
43
43
|
|
|
44
44
|
raise UnsafeMultiplexError,
|
|
45
45
|
"refusing to multiplex SQL (#{reason}); the shared path only accepts " \
|
|
46
|
-
|
|
46
|
+
'session-neutral operations. Use Client#session for session work or ' \
|
|
47
47
|
"Client#transaction for an explicit transaction.\n" \
|
|
48
48
|
" offending SQL: #{sql.to_s.strip[0, 160]}"
|
|
49
49
|
end
|
|
@@ -75,20 +75,29 @@ module PgPipeline
|
|
|
75
75
|
code = NEEDS_MASK.match?(sql) ? code_only(sql) : sql
|
|
76
76
|
lead = code[LEADING_KEYWORD, 1]&.downcase
|
|
77
77
|
|
|
78
|
-
return
|
|
78
|
+
return 'empty' unless lead
|
|
79
79
|
return "leading:#{lead}" unless ALLOWED_LEADING.include?(lead)
|
|
80
|
-
return
|
|
81
|
-
return nil unless code.include?(
|
|
80
|
+
return 'multiple-statements' if multiple_statements?(code)
|
|
81
|
+
return nil unless code.include?('(') || code.match?(PATTERN_PREFILTER)
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
forbidden_reason(code) || strict_forbidden_reason(code, mode)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def forbidden_reason(code)
|
|
87
|
+
pattern_reason(code, FORBIDDEN_PATTERNS)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def strict_forbidden_reason(code, mode)
|
|
91
|
+
return unless mode == :strict
|
|
92
|
+
|
|
93
|
+
reason = pattern_reason(code, STRICT_FORBIDDEN)
|
|
94
|
+
"strict:#{reason}" if reason
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def pattern_reason(code, patterns)
|
|
98
|
+
patterns.each do |name, pattern|
|
|
84
99
|
return name if code.match?(pattern)
|
|
85
100
|
end
|
|
86
|
-
if mode == :strict
|
|
87
|
-
STRICT_FORBIDDEN.each do |name, pattern|
|
|
88
|
-
return "strict:#{name}" if code.match?(pattern)
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
101
|
nil
|
|
93
102
|
end
|
|
94
103
|
|
|
@@ -96,7 +105,7 @@ module PgPipeline
|
|
|
96
105
|
normalized = mode.respond_to?(:to_sym) ? mode.to_sym : mode
|
|
97
106
|
return normalized if VALID_MODES.include?(normalized)
|
|
98
107
|
|
|
99
|
-
raise ArgumentError, "guard must be one of: #{VALID_MODES.map(&:inspect).join(
|
|
108
|
+
raise ArgumentError, "guard must be one of: #{VALID_MODES.map(&:inspect).join(', ')}"
|
|
100
109
|
end
|
|
101
110
|
|
|
102
111
|
def code_only(sql)
|
|
@@ -104,43 +113,19 @@ module PgPipeline
|
|
|
104
113
|
size = source.bytesize
|
|
105
114
|
output = String.new(capacity: size, encoding: Encoding::BINARY)
|
|
106
115
|
index = 0
|
|
107
|
-
block_depth = 0
|
|
108
116
|
|
|
109
117
|
while index < size
|
|
110
118
|
byte = source.getbyte(index)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if byte == 47 && nxt == 42
|
|
114
|
-
block_depth += 1
|
|
115
|
-
output << " "
|
|
116
|
-
index += 2
|
|
117
|
-
elsif byte == 42 && nxt == 47
|
|
118
|
-
block_depth -= 1
|
|
119
|
-
output << " "
|
|
120
|
-
index += 2
|
|
121
|
-
else
|
|
122
|
-
output << (byte == 10 ? 10 : 32)
|
|
123
|
-
index += 1
|
|
124
|
-
end
|
|
125
|
-
next
|
|
126
|
-
end
|
|
119
|
+
next_index = index + 1
|
|
120
|
+
nxt = next_index < size ? source.getbyte(next_index) : nil
|
|
127
121
|
|
|
128
122
|
if byte == 45 && nxt == 45
|
|
129
|
-
|
|
130
|
-
if newline
|
|
131
|
-
output << (" " * (newline - index)) << "\n"
|
|
132
|
-
index = newline + 1
|
|
133
|
-
else
|
|
134
|
-
output << (" " * (size - index))
|
|
135
|
-
break
|
|
136
|
-
end
|
|
123
|
+
index = mask_line_comment(source, output, index)
|
|
137
124
|
next
|
|
138
125
|
end
|
|
139
126
|
|
|
140
127
|
if byte == 47 && nxt == 42
|
|
141
|
-
|
|
142
|
-
output << " "
|
|
143
|
-
index += 2
|
|
128
|
+
index = mask_block_comment(source, output, index)
|
|
144
129
|
next
|
|
145
130
|
end
|
|
146
131
|
|
|
@@ -155,12 +140,8 @@ module PgPipeline
|
|
|
155
140
|
end
|
|
156
141
|
|
|
157
142
|
if byte == 36
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if tag
|
|
161
|
-
closing = source.index(tag, index + tag.bytesize)
|
|
162
|
-
finish = closing ? closing + tag.bytesize : size
|
|
163
|
-
output << (" " * (finish - index))
|
|
143
|
+
finish = mask_dollar_quote(source, output, index, size)
|
|
144
|
+
if finish
|
|
164
145
|
index = finish
|
|
165
146
|
next
|
|
166
147
|
end
|
|
@@ -173,29 +154,79 @@ module PgPipeline
|
|
|
173
154
|
output
|
|
174
155
|
end
|
|
175
156
|
|
|
157
|
+
def mask_line_comment(source, output, index)
|
|
158
|
+
newline = source.index("\n", index + 2)
|
|
159
|
+
if newline
|
|
160
|
+
output << (' ' * (newline - index)) << "\n"
|
|
161
|
+
newline + 1
|
|
162
|
+
else
|
|
163
|
+
output << (' ' * (source.bytesize - index))
|
|
164
|
+
source.bytesize
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def mask_block_comment(source, output, index)
|
|
169
|
+
depth = 1
|
|
170
|
+
index = append_comment_delimiter(output, index)
|
|
171
|
+
|
|
172
|
+
while index < source.bytesize
|
|
173
|
+
byte = source.getbyte(index)
|
|
174
|
+
nxt = source.getbyte(index + 1)
|
|
175
|
+
if byte == 47 && nxt == 42
|
|
176
|
+
depth += 1
|
|
177
|
+
index = append_comment_delimiter(output, index)
|
|
178
|
+
elsif byte == 42 && nxt == 47
|
|
179
|
+
depth -= 1
|
|
180
|
+
index = append_comment_delimiter(output, index)
|
|
181
|
+
break if depth.zero?
|
|
182
|
+
else
|
|
183
|
+
output << (byte == 10 ? 10 : 32)
|
|
184
|
+
index += 1
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
index
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def append_comment_delimiter(output, index)
|
|
192
|
+
output << ' '
|
|
193
|
+
index + 2
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def mask_dollar_quote(source, output, index, size)
|
|
197
|
+
remainder = source.byteslice(index, size - index)
|
|
198
|
+
tag = DOLLAR_QUOTE_TAG.match(remainder)&.[](0)
|
|
199
|
+
return unless tag
|
|
200
|
+
|
|
201
|
+
closing = source.index(tag, index + tag.bytesize)
|
|
202
|
+
finish = closing ? closing + tag.bytesize : size
|
|
203
|
+
output << (' ' * (finish - index))
|
|
204
|
+
finish
|
|
205
|
+
end
|
|
206
|
+
|
|
176
207
|
def mask_quoted(source, output, index, quote_byte, escape_backslash:)
|
|
177
|
-
output <<
|
|
208
|
+
output << ' '
|
|
178
209
|
index += 1
|
|
179
210
|
|
|
180
211
|
while index < source.bytesize
|
|
181
212
|
byte = source.getbyte(index)
|
|
182
213
|
if escape_backslash && byte == 92
|
|
183
|
-
output <<
|
|
214
|
+
output << ' '
|
|
184
215
|
index += 1
|
|
185
216
|
if index < source.bytesize
|
|
186
|
-
output << (source.getbyte(index) == 10 ? "\n" :
|
|
217
|
+
output << (source.getbyte(index) == 10 ? "\n" : ' ')
|
|
187
218
|
index += 1
|
|
188
219
|
end
|
|
189
220
|
elsif byte == quote_byte
|
|
190
221
|
if source.getbyte(index + 1) == quote_byte
|
|
191
|
-
output <<
|
|
222
|
+
output << ' '
|
|
192
223
|
index += 2
|
|
193
224
|
else
|
|
194
|
-
output <<
|
|
225
|
+
output << ' '
|
|
195
226
|
return index + 1
|
|
196
227
|
end
|
|
197
228
|
else
|
|
198
|
-
output << (byte == 10 ? "\n" :
|
|
229
|
+
output << (byte == 10 ? "\n" : ' ')
|
|
199
230
|
index += 1
|
|
200
231
|
end
|
|
201
232
|
end
|
|
@@ -207,15 +238,19 @@ module PgPipeline
|
|
|
207
238
|
return false if quote_index.zero?
|
|
208
239
|
|
|
209
240
|
marker = source.getbyte(quote_index - 1)
|
|
210
|
-
return false unless
|
|
241
|
+
return false unless [69, 101].include?(marker)
|
|
211
242
|
|
|
212
243
|
before = quote_index >= 2 ? source.getbyte(quote_index - 2) : nil
|
|
213
|
-
|
|
244
|
+
!sql_identifier_byte?(before)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def sql_identifier_byte?(byte)
|
|
248
|
+
byte && (byte.between?(48, 57) || byte.between?(65, 90) || byte.between?(97, 122) || byte == 95)
|
|
214
249
|
end
|
|
215
|
-
private_class_method :escape_string_prefix?
|
|
250
|
+
private_class_method :escape_string_prefix?, :sql_identifier_byte?
|
|
216
251
|
|
|
217
252
|
def multiple_statements?(code)
|
|
218
|
-
first = code.index(
|
|
253
|
+
first = code.index(';')
|
|
219
254
|
return false unless first
|
|
220
255
|
|
|
221
256
|
index = first + 1
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'pg'
|
|
4
4
|
|
|
5
|
-
require_relative
|
|
5
|
+
require_relative 'session'
|
|
6
6
|
|
|
7
7
|
module PgPipeline
|
|
8
8
|
class Transaction < Session
|
|
@@ -30,68 +30,67 @@ module PgPipeline
|
|
|
30
30
|
module TransactionOps
|
|
31
31
|
module_function
|
|
32
32
|
|
|
33
|
-
def run(
|
|
34
|
-
SessionOps.ensure_active!(
|
|
35
|
-
raise Error,
|
|
33
|
+
def run(transaction)
|
|
34
|
+
SessionOps.ensure_active!(transaction)
|
|
35
|
+
raise Error, 'transaction is already open' if transaction.open?
|
|
36
36
|
|
|
37
|
-
conn = SessionOps.connection(
|
|
38
|
-
conn.exec(
|
|
39
|
-
|
|
37
|
+
conn = SessionOps.connection(transaction)
|
|
38
|
+
conn.exec('BEGIN')
|
|
39
|
+
transaction.__send__(:open=, true)
|
|
40
40
|
begin
|
|
41
|
-
result = yield
|
|
41
|
+
result = yield transaction
|
|
42
42
|
commit(conn)
|
|
43
|
-
|
|
43
|
+
transaction.__send__(:open=, false)
|
|
44
44
|
result
|
|
45
45
|
rescue Exception
|
|
46
|
-
rollback_quietly(
|
|
46
|
+
rollback_quietly(transaction)
|
|
47
47
|
raise
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def commit(conn)
|
|
52
|
-
conn.exec(
|
|
53
|
-
rescue PG::Error =>
|
|
54
|
-
raise unless indeterminate_commit_failure?(conn,
|
|
52
|
+
conn.exec('COMMIT')
|
|
53
|
+
rescue PG::Error => e
|
|
54
|
+
raise unless indeterminate_commit_failure?(conn, e)
|
|
55
55
|
|
|
56
56
|
raise IndeterminateCommitError.new(
|
|
57
|
-
|
|
58
|
-
"and must not be retried blindly (#{
|
|
59
|
-
), cause:
|
|
57
|
+
'COMMIT acknowledgement was not received; the transaction may have committed ' \
|
|
58
|
+
"and must not be retried blindly (#{e.class}: #{e.message})"
|
|
59
|
+
), cause: e
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def indeterminate_commit_failure?(conn, error)
|
|
63
63
|
return true if defined?(PG::ConnectionBad) && error.is_a?(PG::ConnectionBad)
|
|
64
|
+
return false unless conn.respond_to?(:finished?) && conn.respond_to?(:status)
|
|
64
65
|
return true if conn.finished?
|
|
65
66
|
|
|
66
67
|
conn.status != PG::CONNECTION_OK
|
|
67
68
|
rescue PG::Error
|
|
68
69
|
true
|
|
69
|
-
rescue NoMethodError
|
|
70
|
-
false
|
|
71
70
|
end
|
|
72
71
|
|
|
73
|
-
def savepoint(
|
|
74
|
-
SessionOps.ensure_active!(
|
|
75
|
-
raise Error,
|
|
72
|
+
def savepoint(transaction, name)
|
|
73
|
+
SessionOps.ensure_active!(transaction)
|
|
74
|
+
raise Error, 'savepoint requires an open transaction' unless transaction.open?
|
|
76
75
|
|
|
77
|
-
conn = SessionOps.connection(
|
|
78
|
-
seq =
|
|
79
|
-
|
|
76
|
+
conn = SessionOps.connection(transaction)
|
|
77
|
+
seq = transaction.__send__(:savepoint_seq) + 1
|
|
78
|
+
transaction.__send__(:savepoint_seq=, seq)
|
|
80
79
|
point = name || "pgp_sp_#{seq}"
|
|
81
80
|
ident = conn.quote_ident(point)
|
|
82
81
|
conn.exec("SAVEPOINT #{ident}")
|
|
83
82
|
begin
|
|
84
|
-
result = yield
|
|
83
|
+
result = yield transaction
|
|
85
84
|
conn.exec("RELEASE SAVEPOINT #{ident}")
|
|
86
85
|
result
|
|
87
86
|
rescue Exception
|
|
88
|
-
rollback_to_savepoint(
|
|
87
|
+
rollback_to_savepoint(transaction, ident)
|
|
89
88
|
raise
|
|
90
89
|
end
|
|
91
90
|
end
|
|
92
91
|
|
|
93
|
-
def rollback_to_savepoint(
|
|
94
|
-
conn = SessionOps.connection(
|
|
92
|
+
def rollback_to_savepoint(transaction, ident)
|
|
93
|
+
conn = SessionOps.connection(transaction)
|
|
95
94
|
return unless conn
|
|
96
95
|
|
|
97
96
|
conn.exec("ROLLBACK TO SAVEPOINT #{ident}")
|
|
@@ -100,15 +99,15 @@ module PgPipeline
|
|
|
100
99
|
nil
|
|
101
100
|
end
|
|
102
101
|
|
|
103
|
-
def rollback_quietly(
|
|
104
|
-
return unless
|
|
102
|
+
def rollback_quietly(transaction)
|
|
103
|
+
return unless transaction.open?
|
|
105
104
|
|
|
106
|
-
conn = SessionOps.connection(
|
|
107
|
-
conn&.exec(
|
|
105
|
+
conn = SessionOps.connection(transaction)
|
|
106
|
+
conn&.exec('ROLLBACK')
|
|
108
107
|
rescue PG::Error
|
|
109
108
|
nil
|
|
110
109
|
ensure
|
|
111
|
-
|
|
110
|
+
transaction.__send__(:open=, false)
|
|
112
111
|
end
|
|
113
112
|
end
|
|
114
113
|
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pg'
|
|
4
|
+
require_relative 'errors'
|
|
5
|
+
|
|
6
|
+
module PgPipeline
|
|
7
|
+
class TypeMaps
|
|
8
|
+
PENDING = Object.new.freeze
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@cache = {}
|
|
12
|
+
@wants_typed = {}
|
|
13
|
+
@bundle = nil
|
|
14
|
+
@text_map_for_results = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def register(statement)
|
|
18
|
+
@wants_typed[statement.physical_name] = true if statement.typed?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def unregister(physical_name)
|
|
22
|
+
@cache.delete(physical_name)
|
|
23
|
+
@wants_typed.delete(physical_name)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def typed?(physical_name)
|
|
27
|
+
@wants_typed[physical_name]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def cached(physical_name)
|
|
31
|
+
@cache[physical_name]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def bind(request, statement)
|
|
35
|
+
return unless statement.typed?
|
|
36
|
+
|
|
37
|
+
request.type_map = @cache[statement.physical_name] || PENDING
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ensure_bundle!(connection_args)
|
|
41
|
+
return if @bundle && @bundle != :failed
|
|
42
|
+
|
|
43
|
+
build_bundle(connection_args)
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
@bundle = :failed
|
|
46
|
+
@text_map_for_results = nil
|
|
47
|
+
raise Error, "failed to build CoderMapsBundle (#{e.class}: #{e.message})"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def build_bundle(connection_args)
|
|
53
|
+
conn = open_connection(connection_args)
|
|
54
|
+
@bundle = PG::BasicTypeRegistry::CoderMapsBundle.new(conn)
|
|
55
|
+
@text_map_for_results = PG::BasicTypeMapForResults.new(@bundle)
|
|
56
|
+
ensure
|
|
57
|
+
close_connection(conn)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def open_connection(connection_args)
|
|
61
|
+
return PG::Connection.new if connection_args.nil?
|
|
62
|
+
|
|
63
|
+
PG::Connection.new(connection_args)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def close_connection(conn)
|
|
67
|
+
conn&.close
|
|
68
|
+
rescue StandardError
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
public
|
|
73
|
+
|
|
74
|
+
def apply!(request, result, _conn)
|
|
75
|
+
map = request.type_map
|
|
76
|
+
return if map.nil?
|
|
77
|
+
|
|
78
|
+
map = resolve_type_map(request, result, map)
|
|
79
|
+
request.type_map = map if request.type_map.equal?(PENDING)
|
|
80
|
+
|
|
81
|
+
result.type_map = map
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def resolve_type_map(request, result, map)
|
|
87
|
+
return map unless map.equal?(PENDING)
|
|
88
|
+
|
|
89
|
+
name = request.statement_name
|
|
90
|
+
@cache[name] ||= build_column_map!(result)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def build_column_map!(result)
|
|
94
|
+
unless result.result_status == PG::PGRES_TUPLES_OK && result.nfields.positive?
|
|
95
|
+
raise Error, 'typed mapping requires a TUPLES_OK result with at least one column'
|
|
96
|
+
end
|
|
97
|
+
if @bundle == :failed || @text_map_for_results.nil?
|
|
98
|
+
raise Error, 'typed result mapping is unavailable (CoderMapsBundle failed)'
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
@text_map_for_results.build_column_map(result)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
data/lib/pg_pipeline/version.rb
CHANGED
data/lib/pg_pipeline.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative "pg_pipeline/session"
|
|
|
9
9
|
require_relative "pg_pipeline/transaction"
|
|
10
10
|
require_relative "pg_pipeline/request"
|
|
11
11
|
require_relative "pg_pipeline/prepared_statement"
|
|
12
|
+
require_relative "pg_pipeline/type_maps"
|
|
12
13
|
require_relative "pg_pipeline/connection_driver"
|
|
13
14
|
require_relative "pg_pipeline/pool"
|
|
14
15
|
require_relative "pg_pipeline/client"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pg_pipeline
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roman Hajdarov
|
|
@@ -134,6 +134,7 @@ files:
|
|
|
134
134
|
- lib/pg_pipeline/session.rb
|
|
135
135
|
- lib/pg_pipeline/session_guard.rb
|
|
136
136
|
- lib/pg_pipeline/transaction.rb
|
|
137
|
+
- lib/pg_pipeline/type_maps.rb
|
|
137
138
|
- lib/pg_pipeline/version.rb
|
|
138
139
|
homepage: https://github.com/roman-haidarov/pg_pipeline
|
|
139
140
|
licenses:
|