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.
@@ -1,26 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "errors"
4
- require_relative "request"
3
+ require_relative 'errors'
4
+ require_relative 'request'
5
5
 
6
6
  module PgPipeline
7
7
  class PreparedStatement
8
- attr_reader :name, :sql, :param_types, :physical_name
8
+ attr_reader :name, :sql, :param_types, :physical_name, :typed
9
9
 
10
- def initialize(client:, name:, physical_name:, sql:, param_types: nil)
10
+ def initialize(client:, name:, physical_name:, sql:, param_types: nil, typed: false)
11
11
  @client = client
12
12
  @name = PreparedStatementOps.snapshot_name(name)
13
13
  @physical_name = PreparedStatementOps.snapshot_name(physical_name)
14
14
  @sql = RequestOps.snapshot_sql(sql)
15
15
  @param_types = RequestOps.snapshot_param_types(param_types)
16
+ @typed = !!typed
16
17
  freeze
17
18
  end
18
19
 
20
+ def typed? = @typed
21
+
19
22
  def query(params = RequestOps::EMPTY_PARAMS) = PreparedStatementOps.query(self, params)
20
23
  alias call query
21
24
 
22
25
  def inspect
23
- "#<#{self.class} name=#{@name.inspect} sql=#{@sql.inspect}>"
26
+ flag = @typed ? ' typed' : ''
27
+ "#<#{self.class} name=#{@name.inspect} sql=#{@sql.inspect}#{flag}>"
24
28
  end
25
29
 
26
30
  private
@@ -37,11 +41,10 @@ module PgPipeline
37
41
 
38
42
  def snapshot_name(name)
39
43
  value = name.to_s
40
- raise ArgumentError, "prepared statement name must not be empty" if value.empty?
41
- raise ArgumentError, "prepared statement name must not contain NUL" if value.include?("\0")
44
+ raise ArgumentError, 'prepared statement name must not be empty' if value.empty?
45
+ raise ArgumentError, 'prepared statement name must not contain NUL' if value.include?("\0")
42
46
 
43
47
  value.frozen? ? value : value.dup.freeze
44
48
  end
45
-
46
49
  end
47
50
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "errors"
3
+ require_relative 'errors'
4
4
 
5
5
  module PgPipeline
6
6
  class Request
7
7
  attr_reader :sql, :params
8
8
  attr_accessor :state, :cancelled, :settled, :result_seen, :query_boundary_seen,
9
- :result, :error, :waiter, :waiter_scheduler
9
+ :result, :error, :waiter, :waiter_scheduler, :type_map
10
10
 
11
11
  def initialize(sql:, params: nil)
12
12
  @sql = RequestOps.snapshot_sql(sql)
@@ -20,6 +20,7 @@ module PgPipeline
20
20
  @error = nil
21
21
  @waiter = nil
22
22
  @waiter_scheduler = nil
23
+ @type_map = nil
23
24
  end
24
25
 
25
26
  def self.build(sql, params)
@@ -64,6 +65,7 @@ module PgPipeline
64
65
  @error = nil
65
66
  @waiter = nil
66
67
  @waiter_scheduler = nil
68
+ @type_map = nil
67
69
  self
68
70
  end
69
71
  end
@@ -117,7 +119,7 @@ module PgPipeline
117
119
 
118
120
  def snapshot_params(params)
119
121
  return EMPTY_PARAMS if params.nil?
120
- raise ArgumentError, "params must be an Array" unless params.is_a?(Array)
122
+ raise ArgumentError, 'params must be an Array' unless params.is_a?(Array)
121
123
  return EMPTY_PARAMS if params.empty?
122
124
 
123
125
  return params if params.frozen? && immutable_values?(params)
@@ -158,24 +160,23 @@ module PgPipeline
158
160
 
159
161
  def snapshot_name(name)
160
162
  value = name.to_s
161
- raise ArgumentError, "statement_name must not be empty" if value.empty?
163
+ raise ArgumentError, 'statement_name must not be empty' if value.empty?
162
164
 
163
165
  value.frozen? ? value : value.dup.freeze
164
166
  end
165
167
 
166
168
  def snapshot_param_types(param_types)
167
169
  return nil if param_types.nil?
168
- raise ArgumentError, "param_types must be an Array or nil" unless param_types.is_a?(Array)
170
+ raise ArgumentError, 'param_types must be an Array or nil' unless param_types.is_a?(Array)
169
171
 
170
172
  param_types.map { |oid| oid.nil? ? nil : Integer(oid) }.freeze
171
173
  rescue ArgumentError, TypeError
172
- raise ArgumentError, "param_types must contain only integer OIDs or nil"
174
+ raise ArgumentError, 'param_types must contain only integer OIDs or nil'
173
175
  end
174
176
 
175
177
  def transition!(req, from, to)
176
- unless req.state == from
177
- raise ProtocolError, "invalid request transition #{req.state.inspect} -> #{to.inspect}"
178
- end
178
+ state = req.state
179
+ raise ProtocolError, "invalid request transition #{state.inspect} -> #{to.inspect}" unless state == from
179
180
 
180
181
  req.state = to
181
182
  end
@@ -203,14 +204,14 @@ module PgPipeline
203
204
  end
204
205
 
205
206
  def query_boundary!(req)
206
- raise ProtocolError, "query boundary before query result" unless req.result_seen
207
- raise ProtocolError, "duplicate query boundary" if req.query_boundary_seen
207
+ raise ProtocolError, 'query boundary before query result' unless req.result_seen
208
+ raise ProtocolError, 'duplicate query boundary' if req.query_boundary_seen
208
209
 
209
210
  req.query_boundary_seen = true
210
211
  end
211
212
 
212
213
  def finish!(req)
213
- raise ProtocolError, "sync arrived before query boundary" unless req.query_boundary_seen
214
+ raise ProtocolError, 'sync arrived before query boundary' unless req.query_boundary_seen
214
215
  return if req.settled
215
216
 
216
217
  req.settled = true
@@ -235,21 +236,23 @@ module PgPipeline
235
236
  req.cancelled = true
236
237
  clear_result(req.result)
237
238
  req.result = nil
238
- req.error.clear_result! if req.error.respond_to?(:clear_result!)
239
+ error = req.error
240
+ error.clear_result! if error.respond_to?(:clear_result!)
239
241
  end
240
242
 
241
243
  def wait(req)
242
244
  park_waiter(req) unless req.settled
243
- raise req.error if req.error
245
+ error = req.error
246
+ raise error if error
244
247
 
245
248
  req.result
246
249
  end
247
250
 
248
251
  def park_waiter(req)
249
- raise ProtocolError, "request already has a waiter" if req.waiter
252
+ raise ProtocolError, 'request already has a waiter' if req.waiter
250
253
 
251
254
  scheduler = Fiber.scheduler
252
- raise Error, "request wait requires an active Fiber scheduler" unless scheduler
255
+ raise Error, 'request wait requires an active Fiber scheduler' unless scheduler
253
256
 
254
257
  waiter = Fiber.current
255
258
  req.waiter = waiter
@@ -274,8 +277,8 @@ module PgPipeline
274
277
  end
275
278
 
276
279
  def assert_result_slot!(req)
277
- raise ProtocolError, "result arrived after query boundary" if req.query_boundary_seen
278
- raise ProtocolError, "multiple results for one pipeline unit" if req.result_seen
280
+ raise ProtocolError, 'result arrived after query boundary' if req.query_boundary_seen
281
+ raise ProtocolError, 'multiple results for one pipeline unit' if req.result_seen
279
282
  end
280
283
 
281
284
  def clear_result(result)
@@ -28,7 +28,8 @@ module PgPipeline
28
28
  end
29
29
 
30
30
  def signal_all
31
- pending, @waiters = @waiters, []
31
+ pending = @waiters
32
+ @waiters = []
32
33
  pending.each { |waiter| Runtime.wake_dequeued(waiter, self) }
33
34
  nil
34
35
  end
@@ -5,7 +5,7 @@ module PgPipeline
5
5
  class Semaphore
6
6
  def initialize(limit)
7
7
  @limit = Integer(limit)
8
- raise ArgumentError, "limit must be >= 0" if @limit.negative?
8
+ raise ArgumentError, 'limit must be >= 0' if @limit.negative?
9
9
 
10
10
  @available = @limit
11
11
  @waiting = WaitList.new
@@ -71,7 +71,7 @@ module PgPipeline
71
71
  def empty? = @head.nil?
72
72
 
73
73
  def push(node)
74
- raise ArgumentError, "node already queued" if node.list
74
+ raise ArgumentError, 'node already queued' if node.list
75
75
 
76
76
  node.list = self
77
77
  node.prev = @tail
@@ -108,7 +108,7 @@ module PgPipeline
108
108
  @list = @prev = @next = nil
109
109
  @granted = false
110
110
  @scheduler = Fiber.scheduler
111
- @waiter = {fiber: fiber, scheduler: @scheduler, ready: false, blocker: blocker, queued: true}
111
+ @waiter = { fiber: fiber, scheduler: @scheduler, ready: false, blocker: blocker, queued: true }
112
112
  end
113
113
 
114
114
  def granted? = @granted
@@ -6,7 +6,7 @@ module PgPipeline
6
6
  attr_reader :fiber, :name, :error
7
7
 
8
8
  def self.spawn(name: nil, &block)
9
- raise ArgumentError, "block required" unless block
9
+ raise ArgumentError, 'block required' unless block
10
10
 
11
11
  task = new(name: name)
12
12
  task.__send__(:start, &block)
@@ -14,8 +14,13 @@ module PgPipeline
14
14
  end
15
15
 
16
16
  def initialize(name: nil)
17
- @fiber, @scheduler, @blocker, @result, @error = nil, nil, nil, nil, nil
18
- @done, @cancelled = false, false
17
+ @fiber = nil
18
+ @scheduler = nil
19
+ @blocker = nil
20
+ @result = nil
21
+ @error = nil
22
+ @done = false
23
+ @cancelled = false
19
24
 
20
25
  @name = name
21
26
  @waiters = []
@@ -24,7 +29,7 @@ module PgPipeline
24
29
  def wait(timeout = nil)
25
30
  unless @done
26
31
  deadline = Runtime.deadline_for(timeout)
27
- raise TimeoutError, "task did not finish in time" unless join(deadline)
32
+ raise TimeoutError, 'task did not finish in time' unless join(deadline)
28
33
  end
29
34
  raise @error if @error
30
35
 
@@ -45,7 +50,7 @@ module PgPipeline
45
50
  def cancelled? = @cancelled
46
51
 
47
52
  def raise_if_cancelled!
48
- raise Cancel, "task stopped" if @cancelled
53
+ raise Cancel, 'task stopped' if @cancelled
49
54
  end
50
55
 
51
56
  def enter_block(waiter)
@@ -68,7 +73,7 @@ module PgPipeline
68
73
  this.__send__(:adopt_fiber, Fiber.current)
69
74
  Thread.current[Runtime::CURRENT_TASK_KEY] = this
70
75
  begin
71
- this.__send__(:complete, block.call, nil)
76
+ this.__send__(:complete, yield, nil)
72
77
  rescue Cancel
73
78
  this.__send__(:complete, nil, nil)
74
79
  rescue Exception => e
@@ -89,7 +94,7 @@ module PgPipeline
89
94
  return false unless fiber&.alive?
90
95
  return false unless scheduler&.respond_to?(:fiber_interrupt)
91
96
 
92
- result = scheduler.fiber_interrupt(fiber, Cancel.new("task stopped"))
97
+ result = scheduler.fiber_interrupt(fiber, Cancel.new('task stopped'))
93
98
  result != false
94
99
  rescue FiberError
95
100
  false
@@ -119,7 +124,8 @@ module PgPipeline
119
124
  end
120
125
 
121
126
  def wake_waiters
122
- pending, @waiters = @waiters, []
127
+ pending = @waiters
128
+ @waiters = []
123
129
  pending.each { |waiter| Runtime.wake_dequeued(waiter, self) }
124
130
  end
125
131
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "timeout"
3
+ require 'timeout'
4
4
 
5
- require_relative "errors"
5
+ require_relative 'errors'
6
6
 
7
7
  module PgPipeline
8
8
  module Runtime
@@ -14,7 +14,7 @@ module PgPipeline
14
14
 
15
15
  CURRENT_TASK_KEY = :pg_pipeline_current_task
16
16
  WAITER_KEY = :pg_pipeline_waiter
17
- DEADLINE_MESSAGE = "execution expired"
17
+ DEADLINE_MESSAGE = 'execution expired'
18
18
 
19
19
  MISSING_TIMEOUT_HOOK_WARNING = <<~MESSAGE
20
20
  pg_pipeline: %s does not implement #timeout_after.
@@ -32,7 +32,7 @@ module PgPipeline
32
32
  end
33
33
 
34
34
  def scheduler!
35
- Fiber.scheduler or raise Error, "operation requires an active Fiber scheduler"
35
+ Fiber.scheduler or raise Error, 'operation requires an active Fiber scheduler'
36
36
  end
37
37
 
38
38
  def native_timeouts?(target = Fiber.scheduler)
@@ -48,9 +48,10 @@ module PgPipeline
48
48
  fiber: nil, scheduler: nil, ready: false, blocker: nil
49
49
  }
50
50
 
51
- if waiter[:blocker]
52
- raise Error, "waiter already parked on #{waiter[:blocker].class}; " \
53
- "a fiber may only park in one place at a time"
51
+ current_blocker = waiter[:blocker]
52
+ if current_blocker
53
+ raise Error, "waiter already parked on #{current_blocker.class}; " \
54
+ 'a fiber may only park in one place at a time'
54
55
  end
55
56
 
56
57
  waiter[:fiber] = Fiber.current
@@ -116,19 +117,19 @@ module PgPipeline
116
117
  return nil if timeout.nil?
117
118
 
118
119
  seconds = Float(timeout)
119
- raise ArgumentError, "timeout must be non-negative and finite" unless seconds.finite? && seconds >= 0
120
+ raise ArgumentError, 'timeout must be non-negative and finite' unless seconds.finite? && seconds >= 0
120
121
 
121
122
  monotonic_now + seconds
122
123
  end
123
124
 
124
- def with_timeout(duration)
125
+ def with_timeout(duration, &block)
125
126
  return yield if duration.nil?
126
127
 
127
128
  seconds = Float(duration)
128
- raise ArgumentError, "timeout must be non-negative and finite" unless seconds.finite? && seconds >= 0
129
+ raise ArgumentError, 'timeout must be non-negative and finite' unless seconds.finite? && seconds >= 0
129
130
 
130
131
  begin
131
- arm_deadline(seconds) { yield }
132
+ arm_deadline(seconds, &block)
132
133
  rescue Deadline => e
133
134
  raise TimeoutError, e.message
134
135
  end
@@ -136,9 +137,7 @@ module PgPipeline
136
137
 
137
138
  def arm_deadline(seconds, &block)
138
139
  target = Fiber.scheduler
139
- if native_timeouts?(target)
140
- return target.timeout_after(seconds, Deadline, DEADLINE_MESSAGE, &block)
141
- end
140
+ return target.timeout_after(seconds, Deadline, DEADLINE_MESSAGE, &block) if native_timeouts?(target)
142
141
 
143
142
  warn_missing_timeout_hook(target) if target
144
143
  ::Timeout.timeout(seconds, Deadline, DEADLINE_MESSAGE, &block)
@@ -154,7 +153,7 @@ module PgPipeline
154
153
  end
155
154
  end
156
155
 
157
- require_relative "runtime/notification"
158
- require_relative "runtime/queue"
159
- require_relative "runtime/semaphore"
160
- require_relative "runtime/task"
156
+ require_relative 'runtime/notification'
157
+ require_relative 'runtime/queue'
158
+ require_relative 'runtime/semaphore'
159
+ require_relative 'runtime/task'
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pg"
3
+ require 'pg'
4
4
 
5
- require_relative "errors"
5
+ require_relative 'errors'
6
6
 
7
7
  module PgPipeline
8
8
  class ServerCaps
@@ -59,7 +59,7 @@ module PgPipeline
59
59
  return true if supported?(caps)
60
60
 
61
61
  raise UnsupportedServerError,
62
- "pg_pipeline requires libpq >= 14, PostgreSQL protocol v3, and " \
62
+ 'pg_pipeline requires libpq >= 14, PostgreSQL protocol v3, and ' \
63
63
  "ruby-pg pipeline bindings; libpq=#{caps.libpq_version}, " \
64
64
  "protocol=#{caps.protocol_version}, pipeline_api=#{caps.pipeline_api}"
65
65
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "errors"
3
+ require_relative 'errors'
4
4
 
5
5
  module PgPipeline
6
6
  class Session
@@ -30,10 +30,10 @@ module PgPipeline
30
30
 
31
31
  def ensure_active!(session)
32
32
  conn = connection(session)
33
- raise Error, "session handle is no longer active" unless session.active? && conn
33
+ raise Error, 'session handle is no longer active' unless session.active? && conn
34
34
  return if Fiber.current.equal?(session.owner_fiber)
35
35
 
36
- raise Error, "session handle is fiber-local and cannot be used from another fiber"
36
+ raise Error, 'session handle is fiber-local and cannot be used from another fiber'
37
37
  end
38
38
 
39
39
  def query(session, sql, params)
@@ -43,19 +43,21 @@ module PgPipeline
43
43
 
44
44
  def exec(session, sql, params = nil)
45
45
  ensure_active!(session)
46
+ conn = connection(session)
46
47
  if params.nil?
47
- connection(session).exec(sql)
48
+ conn.exec(sql)
48
49
  else
49
- connection(session).exec_params(sql, params)
50
+ conn.exec_params(sql, params)
50
51
  end
51
52
  end
52
53
 
53
54
  def prepare(session, name, sql, param_types)
54
55
  ensure_active!(session)
56
+ conn = connection(session)
55
57
  if param_types
56
- connection(session).prepare(name, sql, param_types)
58
+ conn.prepare(name, sql, param_types)
57
59
  else
58
- connection(session).prepare(name, sql)
60
+ conn.prepare(name, sql)
59
61
  end
60
62
  end
61
63