pg 1.3.3-x86-mingw32 → 1.4.0-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
@@ -344,10 +344,12 @@ record_isspace(char ch)
344
344
  * oids = conn.exec( "SELECT (NULL::complex).*" )
345
345
  * # Build a type map (PG::TypeMapByColumn) for decoding the "complex" type
346
346
  * dtm = PG::BasicTypeMapForResults.new(conn).build_column_map( oids )
347
- * # Register a record decoder for decoding our type "complex"
348
- * PG::BasicTypeRegistry.register_coder(PG::TextDecoder::Record.new(type_map: dtm, name: "complex"))
349
- * # Apply the basic type registry to all results retrieved from the server
350
- * conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn)
347
+ * # Build a type map and populate with basic types
348
+ * btr = PG::BasicTypeRegistry.new.register_default_types
349
+ * # Register a new record decoder for decoding our type "complex"
350
+ * btr.register_coder(PG::TextDecoder::Record.new(type_map: dtm, name: "complex"))
351
+ * # Apply our basic type registry to all results retrieved from the server
352
+ * conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn, registry: btr)
351
353
  * # Now queries decode the "complex" type (and many basic types) automatically
352
354
  * conn.exec("SELECT * FROM my_table").to_a
353
355
  * # => [{"v1"=>[2.0, 3.0], "v2"=>[4.0, 5.0]}, {"v1"=>[6.0, 7.0], "v2"=>[8.0, 9.0]}]
data/ext/pg_result.c CHANGED
@@ -1383,7 +1383,7 @@ pgresult_type_map_get(VALUE self)
1383
1383
 
1384
1384
 
1385
1385
  static void
1386
- yield_hash(VALUE self, int ntuples, int nfields)
1386
+ yield_hash(VALUE self, int ntuples, int nfields, void *data)
1387
1387
  {
1388
1388
  int tuple_num;
1389
1389
  t_pg_result *this = pgresult_get_this(self);
@@ -1397,7 +1397,7 @@ yield_hash(VALUE self, int ntuples, int nfields)
1397
1397
  }
1398
1398
 
1399
1399
  static void
1400
- yield_array(VALUE self, int ntuples, int nfields)
1400
+ yield_array(VALUE self, int ntuples, int nfields, void *data)
1401
1401
  {
1402
1402
  int row;
1403
1403
  t_pg_result *this = pgresult_get_this(self);
@@ -1417,7 +1417,7 @@ yield_array(VALUE self, int ntuples, int nfields)
1417
1417
  }
1418
1418
 
1419
1419
  static void
1420
- yield_tuple(VALUE self, int ntuples, int nfields)
1420
+ yield_tuple(VALUE self, int ntuples, int nfields, void *data)
1421
1421
  {
1422
1422
  int tuple_num;
1423
1423
  t_pg_result *this = pgresult_get_this(self);
@@ -1436,8 +1436,9 @@ yield_tuple(VALUE self, int ntuples, int nfields)
1436
1436
  }
1437
1437
  }
1438
1438
 
1439
- static VALUE
1440
- pgresult_stream_any(VALUE self, void (*yielder)(VALUE, int, int))
1439
+ /* Non-static, and data pointer for use by sequel_pg */
1440
+ VALUE
1441
+ pgresult_stream_any(VALUE self, void (*yielder)(VALUE, int, int, void*), void* data)
1441
1442
  {
1442
1443
  t_pg_result *this;
1443
1444
  int nfields;
@@ -1456,6 +1457,7 @@ pgresult_stream_any(VALUE self, void (*yielder)(VALUE, int, int))
1456
1457
 
1457
1458
  switch( PQresultStatus(pgresult) ){
1458
1459
  case PGRES_TUPLES_OK:
1460
+ case PGRES_COMMAND_OK:
1459
1461
  if( ntuples == 0 )
1460
1462
  return self;
1461
1463
  rb_raise( rb_eInvalidResultStatus, "PG::Result is not in single row mode");
@@ -1465,7 +1467,12 @@ pgresult_stream_any(VALUE self, void (*yielder)(VALUE, int, int))
1465
1467
  pg_result_check( self );
1466
1468
  }
1467
1469
 
1468
- yielder( self, ntuples, nfields );
1470
+ yielder( self, ntuples, nfields, data );
1471
+
1472
+ if( gvl_PQisBusy(pgconn) ){
1473
+ /* wait for input (without blocking) before reading each result */
1474
+ pgconn_block( 0, NULL, this->connection );
1475
+ }
1469
1476
 
1470
1477
  pgresult = gvl_PQgetResult(pgconn);
1471
1478
  if( pgresult == NULL )
@@ -1516,7 +1523,7 @@ pgresult_stream_any(VALUE self, void (*yielder)(VALUE, int, int))
1516
1523
  static VALUE
1517
1524
  pgresult_stream_each(VALUE self)
1518
1525
  {
1519
- return pgresult_stream_any(self, yield_hash);
1526
+ return pgresult_stream_any(self, yield_hash, NULL);
1520
1527
  }
1521
1528
 
1522
1529
  /*
@@ -1532,7 +1539,7 @@ pgresult_stream_each(VALUE self)
1532
1539
  static VALUE
1533
1540
  pgresult_stream_each_row(VALUE self)
1534
1541
  {
1535
- return pgresult_stream_any(self, yield_array);
1542
+ return pgresult_stream_any(self, yield_array, NULL);
1536
1543
  }
1537
1544
 
1538
1545
  /*
@@ -1549,7 +1556,7 @@ pgresult_stream_each_tuple(VALUE self)
1549
1556
  /* allocate VALUEs that are shared between all streamed tuples */
1550
1557
  ensure_init_for_tuple(self);
1551
1558
 
1552
- return pgresult_stream_any(self, yield_tuple);
1559
+ return pgresult_stream_any(self, yield_tuple, NULL);
1553
1560
  }
1554
1561
 
1555
1562
  /*
data/ext/pg_tuple.c CHANGED
@@ -471,10 +471,7 @@ pg_tuple_dump(VALUE self)
471
471
  values = rb_ary_new4(this->num_fields, &this->values[0]);
472
472
  a = rb_ary_new3(2, field_names, values);
473
473
 
474
- if (FL_TEST(self, FL_EXIVAR)) {
475
- rb_copy_generic_ivar(a, self);
476
- FL_SET(a, FL_EXIVAR);
477
- }
474
+ rb_copy_generic_ivar(a, self);
478
475
 
479
476
  return a;
480
477
  }
@@ -542,10 +539,7 @@ pg_tuple_load(VALUE self, VALUE a)
542
539
 
543
540
  RTYPEDDATA_DATA(self) = this;
544
541
 
545
- if (FL_TEST(a, FL_EXIVAR)) {
546
- rb_copy_generic_ivar(self, a);
547
- FL_SET(self, FL_EXIVAR);
548
- }
542
+ rb_copy_generic_ivar(self, a);
549
543
 
550
544
  return self;
551
545
  }
data/lib/2.5/pg_ext.so CHANGED
Binary file
data/lib/2.6/pg_ext.so CHANGED
Binary file
data/lib/2.7/pg_ext.so CHANGED
Binary file
data/lib/3.0/pg_ext.so CHANGED
Binary file
data/lib/3.1/pg_ext.so CHANGED
Binary file
@@ -22,7 +22,7 @@ require 'pg' unless defined?( PG )
22
22
  # end
23
23
  #
24
24
  # conn = PG.connect
25
- # regi = PG::BasicTypeRegistry.new.define_default_types
25
+ # regi = PG::BasicTypeRegistry.new.register_default_types
26
26
  # regi.register_type(0, 'inet', InetEncoder, InetDecoder)
27
27
  # conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn, registry: regi)
28
28
  class PG::BasicTypeRegistry
@@ -184,6 +184,7 @@ class PG::BasicTypeRegistry
184
184
  name = coder.name || raise(ArgumentError, "name of #{coder.inspect} must be defined")
185
185
  h[:encoder][name] = coder if coder.respond_to?(:encode)
186
186
  h[:decoder][name] = coder if coder.respond_to?(:decode)
187
+ self
187
188
  end
188
189
 
189
190
  # Register the given +encoder_class+ and/or +decoder_class+ for casting a PostgreSQL type.
@@ -193,6 +194,7 @@ class PG::BasicTypeRegistry
193
194
  def register_type(format, name, encoder_class, decoder_class)
194
195
  register_coder(encoder_class.new(name: name, format: format)) if encoder_class
195
196
  register_coder(decoder_class.new(name: name, format: format)) if decoder_class
197
+ self
196
198
  end
197
199
 
198
200
  # Alias the +old+ type to the +new+ type.
@@ -205,10 +207,11 @@ class PG::BasicTypeRegistry
205
207
  @coders_by_name[format][ende].delete(new)
206
208
  end
207
209
  end
210
+ self
208
211
  end
209
212
 
210
213
  # Populate the registry with all builtin types of ruby-pg
211
- def define_default_types
214
+ def register_default_types
212
215
  register_type 0, 'int2', PG::TextEncoder::Integer, PG::TextDecoder::Integer
213
216
  alias_type 0, 'int4', 'int2'
214
217
  alias_type 0, 'int8', 'int2'
@@ -281,8 +284,10 @@ class PG::BasicTypeRegistry
281
284
  self
282
285
  end
283
286
 
287
+ alias define_default_types register_default_types
288
+
284
289
  # @private
285
- DEFAULT_TYPE_REGISTRY = PG::BasicTypeRegistry.new.define_default_types
290
+ DEFAULT_TYPE_REGISTRY = PG::BasicTypeRegistry.new.register_default_types
286
291
 
287
292
  # Delegate class method calls to DEFAULT_TYPE_REGISTRY
288
293
  class << self
data/lib/pg/connection.rb CHANGED
@@ -46,37 +46,6 @@ class PG::Connection
46
46
  hash.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
47
47
  end
48
48
 
49
- # Decode a connection string to Hash options
50
- #
51
- # Value are properly unquoted and unescaped.
52
- def self.connect_string_to_hash( str )
53
- options = {}
54
- key = nil
55
- value = String.new
56
- str.scan(/\G\s*(?>([^\s\\\']+)\s*=\s*|([^\s\\\']+)|'((?:[^\'\\]|\\.)*)'|(\\.?)|(\S))(\s|\z)?/m) do
57
- |k, word, sq, esc, garbage, sep|
58
- raise ArgumentError, "unterminated quoted string in connection info string: #{str.inspect}" if garbage
59
- if k
60
- key = k
61
- else
62
- value << (word || (sq || esc).gsub(/\\(.)/, '\\1'))
63
- end
64
- if sep
65
- raise ArgumentError, "missing = after #{value.inspect}" unless key
66
- options[key.to_sym] = value
67
- key = nil
68
- value = String.new
69
- end
70
- end
71
- options
72
- end
73
-
74
- # URI defined in RFC3986
75
- # This regexp is modified to allow host to specify multiple comma separated components captured as <hostports> and to disallow comma in hostnames.
76
- # Taken from: https://github.com/ruby/ruby/blob/be04006c7d2f9aeb7e9d8d09d945b3a9c7850202/lib/uri/rfc3986_parser.rb#L6
77
- HOST_AND_PORT = /(?<hostport>(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[-\.!$&-+0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)/
78
- POSTGRESQL_URI = /\A(?<URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<hostports>#{HOST_AND_PORT}(?:,\g<hostport>)*))(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/
79
-
80
49
  # Parse the connection +args+ into a connection-parameter string.
81
50
  # See PG::Connection.new for valid arguments.
82
51
  #
@@ -87,91 +56,43 @@ class PG::Connection
87
56
  # * URI object
88
57
  # * positional arguments
89
58
  #
90
- # The method adds the option "hostaddr" and "fallback_application_name" if they aren't already set.
91
- # The URI and the options string is passed through and "hostaddr" as well as "fallback_application_name"
92
- # are added to the end.
93
- def self::parse_connect_args( *args )
59
+ # The method adds the option "fallback_application_name" if it isn't already set.
60
+ # It returns a connection string with "key=value" pairs.
61
+ def self.parse_connect_args( *args )
94
62
  hash_arg = args.last.is_a?( Hash ) ? args.pop.transform_keys(&:to_sym) : {}
95
- option_string = ""
96
63
  iopts = {}
97
64
 
98
65
  if args.length == 1
99
66
  case args.first
100
- when URI, POSTGRESQL_URI
101
- uri = args.first.to_s
102
- uri_match = POSTGRESQL_URI.match(uri)
103
- if uri_match['query']
104
- iopts = URI.decode_www_form(uri_match['query']).to_h.transform_keys(&:to_sym)
105
- end
106
- # extract "host1,host2" from "host1:5432,host2:5432"
107
- iopts[:host] = uri_match['hostports'].split(',', -1).map do |hostport|
108
- hostmatch = /\A#{HOST_AND_PORT}\z/.match(hostport)
109
- hostmatch['IPv6address'] || hostmatch['IPv4address'] || hostmatch['reg-name']&.gsub(/%(\h\h)/){ $1.hex.chr }
110
- end.join(',')
111
- oopts = {}
112
- when /=/
113
- # Option string style
114
- option_string = args.first.to_s
115
- iopts = connect_string_to_hash(option_string)
116
- oopts = {}
67
+ when URI, /=/, /:\/\//
68
+ # Option or URL string style
69
+ conn_string = args.first.to_s
70
+ iopts = PG::Connection.conninfo_parse(conn_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
117
71
  else
118
72
  # Positional parameters (only host given)
119
73
  iopts[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
120
- oopts = iopts.dup
121
74
  end
122
75
  else
123
- # Positional parameters
76
+ # Positional parameters with host and more
124
77
  max = CONNECT_ARGUMENT_ORDER.length
125
78
  raise ArgumentError,
126
- "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
79
+ "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
127
80
 
128
81
  CONNECT_ARGUMENT_ORDER.zip( args ) do |(k,v)|
129
82
  iopts[ k.to_sym ] = v if v
130
83
  end
131
84
  iopts.delete(:tty) # ignore obsolete tty parameter
132
- oopts = iopts.dup
133
85
  end
134
86
 
135
87
  iopts.merge!( hash_arg )
136
- oopts.merge!( hash_arg )
137
-
138
- # Resolve DNS in Ruby to avoid blocking state while connecting, when it ...
139
- if (host=iopts[:host]) && !iopts[:hostaddr]
140
- hostaddrs = host.split(",", -1).map do |mhost|
141
- if !mhost.empty? && !mhost.start_with?("/") && # isn't UnixSocket
142
- # isn't a path on Windows
143
- (RUBY_PLATFORM !~ /mingw|mswin/ || mhost !~ /\A\w:[\/\\]/)
144
-
145
- if Fiber.respond_to?(:scheduler) &&
146
- Fiber.scheduler &&
147
- RUBY_VERSION < '3.1.'
148
-
149
- # Use a second thread to avoid blocking of the scheduler.
150
- # `IPSocket.getaddress` isn't fiber aware before ruby-3.1.
151
- Thread.new{ IPSocket.getaddress(mhost) rescue '' }.value
152
- else
153
- IPSocket.getaddress(mhost) rescue ''
154
- end
155
- end
156
- end
157
- oopts[:hostaddr] = hostaddrs.join(",") if hostaddrs.any?
158
- end
159
88
 
160
89
  if !iopts[:fallback_application_name]
161
- oopts[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
90
+ iopts[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
162
91
  end
163
92
 
164
- if uri
165
- uri += uri_match['query'] ? "&" : "?"
166
- uri += URI.encode_www_form( oopts )
167
- return uri
168
- else
169
- option_string += ' ' unless option_string.empty? && oopts.empty?
170
- return option_string + connect_hash_to_string(oopts)
171
- end
93
+ return connect_hash_to_string(iopts)
172
94
  end
173
95
 
174
-
175
96
  # call-seq:
176
97
  # conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
177
98
  #
@@ -241,7 +162,7 @@ class PG::Connection
241
162
  # ["more", "data", "to", "copy"]
242
163
 
243
164
  def copy_data( sql, coder=nil )
244
- raise PG::NotInBlockingMode, "copy_data can not be used in nonblocking mode" if nonblocking?
165
+ raise PG::NotInBlockingMode.new("copy_data can not be used in nonblocking mode", connection: self) if nonblocking?
245
166
  res = exec( sql )
246
167
 
247
168
  case res.result_status
@@ -273,11 +194,15 @@ class PG::Connection
273
194
  yield res
274
195
  rescue Exception => err
275
196
  cancel
276
- while get_copy_data
197
+ begin
198
+ while get_copy_data
199
+ end
200
+ rescue PG::Error
201
+ # Ignore error in cleanup to avoid losing original exception
277
202
  end
278
203
  while get_result
279
204
  end
280
- raise
205
+ raise err
281
206
  else
282
207
  res = get_last_result
283
208
  if !res || res.result_status != PGRES_COMMAND_OK
@@ -285,7 +210,7 @@ class PG::Connection
285
210
  end
286
211
  while get_result
287
212
  end
288
- raise PG::NotAllCopyDataRetrieved, "Not all COPY data retrieved"
213
+ raise PG::NotAllCopyDataRetrieved.new("Not all COPY data retrieved", connection: self)
289
214
  end
290
215
  res
291
216
  ensure
@@ -310,16 +235,17 @@ class PG::Connection
310
235
  # and a +COMMIT+ at the end of the block, or
311
236
  # +ROLLBACK+ if any exception occurs.
312
237
  def transaction
238
+ rollback = false
313
239
  exec "BEGIN"
314
- res = yield(self)
240
+ yield(self)
315
241
  rescue Exception
242
+ rollback = true
316
243
  cancel if transaction_status == PG::PQTRANS_ACTIVE
317
244
  block
318
245
  exec "ROLLBACK"
319
246
  raise
320
- else
321
- exec "COMMIT"
322
- res
247
+ ensure
248
+ exec "COMMIT" unless rollback
323
249
  end
324
250
 
325
251
  ### Returns an array of Hashes with connection defaults. See ::conndefaults
@@ -482,10 +408,10 @@ class PG::Connection
482
408
  # See also #copy_data.
483
409
  #
484
410
  def put_copy_data(buffer, encoder=nil)
485
- until sync_put_copy_data(buffer, encoder)
486
- flush
411
+ until res=sync_put_copy_data(buffer, encoder)
412
+ res = flush
487
413
  end
488
- flush
414
+ res
489
415
  end
490
416
  alias async_put_copy_data put_copy_data
491
417
 
@@ -545,6 +471,7 @@ class PG::Connection
545
471
  def reset
546
472
  reset_start
547
473
  async_connect_or_reset(:reset_poll)
474
+ self
548
475
  end
549
476
  alias async_reset reset
550
477
 
@@ -612,37 +539,70 @@ class PG::Connection
612
539
  alias async_cancel cancel
613
540
 
614
541
  private def async_connect_or_reset(poll_meth)
615
- # Now grab a reference to the underlying socket so we know when the connection is established
616
- socket = socket_io
617
-
618
542
  # Track the progress of the connection, waiting for the socket to become readable/writable before polling it
543
+
544
+ if (timeo = conninfo_hash[:connect_timeout].to_i) && timeo > 0
545
+ # Lowest timeout is 2 seconds - like in libpq
546
+ timeo = [timeo, 2].max
547
+ stop_time = timeo + Process.clock_gettime(Process::CLOCK_MONOTONIC)
548
+ end
549
+
619
550
  poll_status = PG::PGRES_POLLING_WRITING
620
551
  until poll_status == PG::PGRES_POLLING_OK ||
621
552
  poll_status == PG::PGRES_POLLING_FAILED
622
553
 
623
- # If the socket needs to read, wait 'til it becomes readable to poll again
624
- case poll_status
625
- when PG::PGRES_POLLING_READING
626
- socket.wait_readable
554
+ timeout = stop_time&.-(Process.clock_gettime(Process::CLOCK_MONOTONIC))
555
+ event = if !timeout || timeout >= 0
556
+ # If the socket needs to read, wait 'til it becomes readable to poll again
557
+ case poll_status
558
+ when PG::PGRES_POLLING_READING
559
+ if defined?(IO::READABLE) # ruby-3.0+
560
+ socket_io.wait(IO::READABLE | IO::PRIORITY, timeout)
561
+ else
562
+ IO.select([socket_io], nil, [socket_io], timeout)
563
+ end
627
564
 
628
- # ...and the same for when the socket needs to write
629
- when PG::PGRES_POLLING_WRITING
630
- socket.wait_writable
565
+ # ...and the same for when the socket needs to write
566
+ when PG::PGRES_POLLING_WRITING
567
+ if defined?(IO::WRITABLE) # ruby-3.0+
568
+ # Use wait instead of wait_readable, since connection errors are delivered as
569
+ # exceptional/priority events on Windows.
570
+ socket_io.wait(IO::WRITABLE | IO::PRIORITY, timeout)
571
+ else
572
+ # io#wait on ruby-2.x doesn't wait for priority, so fallback to IO.select
573
+ IO.select(nil, [socket_io], [socket_io], timeout)
574
+ end
575
+ end
576
+ end
577
+ # connection to server at "localhost" (127.0.0.1), port 5433 failed: timeout expired (PG::ConnectionBad)
578
+ # connection to server on socket "/var/run/postgresql/.s.PGSQL.5433" failed: No such file or directory
579
+ unless event
580
+ if self.class.send(:host_is_named_pipe?, host)
581
+ connhost = "on socket \"#{host}\""
582
+ elsif respond_to?(:hostaddr)
583
+ connhost = "at \"#{host}\" (#{hostaddr}), port #{port}"
584
+ else
585
+ connhost = "at \"#{host}\", port #{port}"
586
+ end
587
+ raise PG::ConnectionBad.new("connection to server #{connhost} failed: timeout expired", connection: self)
631
588
  end
632
589
 
633
590
  # Check to see if it's finished or failed yet
634
591
  poll_status = send( poll_meth )
592
+ @last_status = status unless [PG::CONNECTION_BAD, PG::CONNECTION_OK].include?(status)
635
593
  end
636
594
 
637
- raise(PG::ConnectionBad, error_message) unless status == PG::CONNECTION_OK
595
+ unless status == PG::CONNECTION_OK
596
+ msg = error_message
597
+ finish
598
+ raise PG::ConnectionBad.new(msg, connection: self)
599
+ end
638
600
 
639
601
  # Set connection to nonblocking to handle all blocking states in ruby.
640
602
  # That way a fiber scheduler is able to handle IO requests.
641
603
  sync_setnonblocking(true)
642
604
  self.flush_data = true
643
605
  set_default_encoding
644
-
645
- self
646
606
  end
647
607
 
648
608
  class << self
@@ -698,12 +658,16 @@ class PG::Connection
698
658
  #
699
659
  # Raises a PG::Error if the connection fails.
700
660
  def new(*args, **kwargs)
701
- conn = self.connect_start(*args, **kwargs ) or
702
- raise(PG::Error, "Unable to create a new connection")
703
-
704
- raise(PG::ConnectionBad, conn.error_message) if conn.status == PG::CONNECTION_BAD
661
+ conn = connect_to_hosts(*args, **kwargs)
705
662
 
706
- conn.send(:async_connect_or_reset, :connect_poll)
663
+ if block_given?
664
+ begin
665
+ return yield conn
666
+ ensure
667
+ conn.finish
668
+ end
669
+ end
670
+ conn
707
671
  end
708
672
  alias async_connect new
709
673
  alias connect new
@@ -711,6 +675,99 @@ class PG::Connection
711
675
  alias setdb new
712
676
  alias setdblogin new
713
677
 
678
+ private def connect_to_hosts(*args)
679
+ option_string = parse_connect_args(*args)
680
+ iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
681
+ iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
682
+
683
+ errors = []
684
+ if iopts[:hostaddr]
685
+ # hostaddr is provided -> no need to resolve hostnames
686
+ ihostaddrs = iopts[:hostaddr].split(",", -1)
687
+
688
+ ihosts = iopts[:host].split(",", -1) if iopts[:host]
689
+ raise PG::ConnectionBad, "could not match #{ihosts.size} host names to #{ihostaddrs.size} hostaddr values" if ihosts && ihosts.size != ihostaddrs.size
690
+
691
+ iports = iopts[:port].split(",", -1)
692
+ iports = iports * ihostaddrs.size if iports.size == 1
693
+ raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihostaddrs.size} hosts" if iports.size != ihostaddrs.size
694
+
695
+ # Try to connect to each hostaddr with separate timeout
696
+ ihostaddrs.each_with_index do |ihostaddr, idx|
697
+ oopts = iopts.merge(hostaddr: ihostaddr, port: iports[idx])
698
+ oopts[:host] = ihosts[idx] if ihosts
699
+ c = connect_internal(oopts, errors)
700
+ return c if c
701
+ end
702
+ elsif iopts[:host]
703
+ # Resolve DNS in Ruby to avoid blocking state while connecting, when it ...
704
+ ihosts = iopts[:host].split(",", -1)
705
+
706
+ iports = iopts[:port].split(",", -1)
707
+ iports = iports * ihosts.size if iports.size == 1
708
+ raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
709
+
710
+ ihosts.each_with_index do |mhost, idx|
711
+ unless host_is_named_pipe?(mhost)
712
+ addrs = if Fiber.respond_to?(:scheduler) &&
713
+ Fiber.scheduler &&
714
+ RUBY_VERSION < '3.1.'
715
+
716
+ # Use a second thread to avoid blocking of the scheduler.
717
+ # `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
718
+ Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
719
+ else
720
+ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
721
+ end
722
+
723
+ # Try to connect to each host with separate timeout
724
+ addrs.each do |addr|
725
+ oopts = iopts.merge(hostaddr: addr, host: mhost, port: iports[idx])
726
+ c = connect_internal(oopts, errors)
727
+ return c if c
728
+ end
729
+ else
730
+ # No hostname to resolve (UnixSocket)
731
+ oopts = iopts.merge(host: mhost, port: iports[idx])
732
+ c = connect_internal(oopts, errors)
733
+ return c if c
734
+ end
735
+ end
736
+ else
737
+ # No host given
738
+ return connect_internal(iopts)
739
+ end
740
+ raise PG::ConnectionBad, errors.join("\n")
741
+ end
742
+
743
+ private def connect_internal(opts, errors=nil)
744
+ begin
745
+ conn = self.connect_start(opts) or
746
+ raise(PG::Error, "Unable to create a new connection")
747
+
748
+ raise PG::ConnectionBad.new(conn.error_message, connection: self) if conn.status == PG::CONNECTION_BAD
749
+
750
+ conn.send(:async_connect_or_reset, :connect_poll)
751
+ rescue PG::ConnectionBad => err
752
+ if errors && !(conn && [PG::CONNECTION_AWAITING_RESPONSE].include?(conn.instance_variable_get(:@last_status)))
753
+ # Seems to be no authentication error -> try next host
754
+ errors << err
755
+ return nil
756
+ else
757
+ # Probably an authentication error
758
+ raise
759
+ end
760
+ end
761
+ conn
762
+ end
763
+
764
+ private def host_is_named_pipe?(host_string)
765
+ host_string.empty? || host_string.start_with?("/") || # it's UnixSocket?
766
+ host_string.start_with?("@") || # it's UnixSocket in the abstract namespace?
767
+ # it's a path on Windows?
768
+ (RUBY_PLATFORM =~ /mingw|mswin/ && host_string =~ /\A([\/\\]|\w:[\/\\])/)
769
+ end
770
+
714
771
  # call-seq:
715
772
  # PG::Connection.ping(connection_hash) -> Integer
716
773
  # PG::Connection.ping(connection_string) -> Integer
data/lib/pg/exceptions.rb CHANGED
@@ -6,7 +6,13 @@ require 'pg' unless defined?( PG )
6
6
 
7
7
  module PG
8
8
 
9
- class Error < StandardError; end
9
+ class Error < StandardError
10
+ def initialize(msg, connection: nil, result: nil)
11
+ @connection = connection
12
+ @result = result
13
+ super(msg)
14
+ end
15
+ end
10
16
 
11
17
  end # module PG
12
18
 
data/lib/pg/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module PG
2
2
  # Library version
3
- VERSION = '1.3.3'
3
+ VERSION = '1.4.0'
4
4
  end
data/lib/pg.rb CHANGED
@@ -59,14 +59,14 @@ module PG
59
59
  # Get the PG library version.
60
60
  #
61
61
  # +include_buildnum+ is no longer used and any value passed will be ignored.
62
- def self::version_string( include_buildnum=nil )
63
- return "%s %s" % [ self.name, VERSION ]
62
+ def self.version_string( include_buildnum=nil )
63
+ "%s %s" % [ self.name, VERSION ]
64
64
  end
65
65
 
66
66
 
67
67
  ### Convenience alias for PG::Connection.new.
68
- def self::connect( *args, **kwargs )
69
- return PG::Connection.new( *args, **kwargs )
68
+ def self.connect( *args, &block )
69
+ Connection.new( *args, &block )
70
70
  end
71
71
 
72
72
 
Binary file
File without changes
File without changes