pg 0.18.0 → 1.1.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.
Files changed (80) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/BSDL +2 -2
  4. data/ChangeLog +1221 -4
  5. data/History.rdoc +200 -0
  6. data/Manifest.txt +5 -18
  7. data/README-Windows.rdoc +15 -26
  8. data/README.rdoc +27 -10
  9. data/Rakefile +33 -24
  10. data/Rakefile.cross +57 -39
  11. data/ext/errorcodes.def +37 -0
  12. data/ext/errorcodes.rb +1 -1
  13. data/ext/errorcodes.txt +16 -1
  14. data/ext/extconf.rb +29 -35
  15. data/ext/gvl_wrappers.c +4 -0
  16. data/ext/gvl_wrappers.h +27 -39
  17. data/ext/pg.c +27 -53
  18. data/ext/pg.h +66 -83
  19. data/ext/pg_binary_decoder.c +75 -6
  20. data/ext/pg_binary_encoder.c +14 -12
  21. data/ext/pg_coder.c +83 -13
  22. data/ext/pg_connection.c +627 -351
  23. data/ext/pg_copy_coder.c +44 -9
  24. data/ext/pg_result.c +364 -134
  25. data/ext/pg_text_decoder.c +605 -46
  26. data/ext/pg_text_encoder.c +95 -76
  27. data/ext/pg_tuple.c +541 -0
  28. data/ext/pg_type_map.c +20 -13
  29. data/ext/pg_type_map_by_column.c +7 -7
  30. data/ext/pg_type_map_by_mri_type.c +2 -2
  31. data/ext/pg_type_map_in_ruby.c +4 -7
  32. data/ext/util.c +7 -7
  33. data/ext/util.h +3 -3
  34. data/lib/pg/basic_type_mapping.rb +105 -45
  35. data/lib/pg/binary_decoder.rb +22 -0
  36. data/lib/pg/coder.rb +1 -1
  37. data/lib/pg/connection.rb +109 -39
  38. data/lib/pg/constants.rb +1 -1
  39. data/lib/pg/exceptions.rb +1 -1
  40. data/lib/pg/result.rb +11 -6
  41. data/lib/pg/text_decoder.rb +25 -20
  42. data/lib/pg/text_encoder.rb +43 -1
  43. data/lib/pg/tuple.rb +30 -0
  44. data/lib/pg/type_map_by_column.rb +1 -1
  45. data/lib/pg.rb +21 -11
  46. data/spec/helpers.rb +50 -25
  47. data/spec/pg/basic_type_mapping_spec.rb +287 -30
  48. data/spec/pg/connection_spec.rb +695 -282
  49. data/spec/pg/connection_sync_spec.rb +41 -0
  50. data/spec/pg/result_spec.rb +59 -17
  51. data/spec/pg/tuple_spec.rb +280 -0
  52. data/spec/pg/type_map_by_class_spec.rb +3 -3
  53. data/spec/pg/type_map_by_column_spec.rb +1 -1
  54. data/spec/pg/type_map_by_mri_type_spec.rb +2 -2
  55. data/spec/pg/type_map_by_oid_spec.rb +1 -1
  56. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  57. data/spec/pg/type_map_spec.rb +1 -1
  58. data/spec/pg/type_spec.rb +319 -35
  59. data/spec/pg_spec.rb +2 -2
  60. data.tar.gz.sig +0 -0
  61. metadata +68 -68
  62. metadata.gz.sig +0 -0
  63. data/sample/array_insert.rb +0 -20
  64. data/sample/async_api.rb +0 -106
  65. data/sample/async_copyto.rb +0 -39
  66. data/sample/async_mixed.rb +0 -56
  67. data/sample/check_conn.rb +0 -21
  68. data/sample/copyfrom.rb +0 -81
  69. data/sample/copyto.rb +0 -19
  70. data/sample/cursor.rb +0 -21
  71. data/sample/disk_usage_report.rb +0 -186
  72. data/sample/issue-119.rb +0 -94
  73. data/sample/losample.rb +0 -69
  74. data/sample/minimal-testcase.rb +0 -17
  75. data/sample/notify_wait.rb +0 -72
  76. data/sample/pg_statistics.rb +0 -294
  77. data/sample/replication_monitor.rb +0 -231
  78. data/sample/test_binary_values.rb +0 -33
  79. data/sample/wal_shipper.rb +0 -434
  80. data/sample/warehouse_partitions.rb +0 -320
data/ext/util.c CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * util.c - Utils for ruby-pg
3
- * $Id: util.c,v 117fb5c5eed7 2014/10/15 18:36:39 lars $
3
+ * $Id: util.c,v fc1c4deb1398 2018/06/25 12:02:09 kanis $
4
4
  *
5
5
  */
6
6
 
@@ -15,9 +15,9 @@ static const char base64_encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk
15
15
  * in-place (with _out_ == _in_).
16
16
  */
17
17
  void
18
- base64_encode( char *out, char *in, int len)
18
+ base64_encode( char *out, const char *in, int len)
19
19
  {
20
- char *in_ptr = in + len;
20
+ const unsigned char *in_ptr = (const unsigned char *)in + len;
21
21
  char *out_ptr = out + BASE64_ENCODED_SIZE(len);
22
22
  int part_len = len % 3;
23
23
 
@@ -72,12 +72,12 @@ static const unsigned char base64_decode_table[] =
72
72
  * It is possible to decode a string in-place (with _out_ == _in_).
73
73
  */
74
74
  int
75
- base64_decode( char *out, char *in, unsigned int len)
75
+ base64_decode( char *out, const char *in, unsigned int len)
76
76
  {
77
77
  unsigned char a, b, c, d;
78
- unsigned char *in_ptr = (unsigned char *)in;
78
+ const unsigned char *in_ptr = (const unsigned char *)in;
79
79
  unsigned char *out_ptr = (unsigned char *)out;
80
- unsigned char *iend_ptr = (unsigned char *)in + len;
80
+ const unsigned char *iend_ptr = (unsigned char *)in + len;
81
81
 
82
82
  for(;;){
83
83
  if( in_ptr+3 < iend_ptr &&
@@ -124,7 +124,7 @@ base64_decode( char *out, char *in, unsigned int len)
124
124
  * At most n bytes will be examined from each string.
125
125
  */
126
126
  int
127
- pg_strncasecmp(const char *s1, const char *s2, size_t n)
127
+ rbpg_strncasecmp(const char *s1, const char *s2, size_t n)
128
128
  {
129
129
  while (n-- > 0)
130
130
  {
data/ext/util.h CHANGED
@@ -57,9 +57,9 @@
57
57
  #define BASE64_ENCODED_SIZE(strlen) (((strlen) + 2) / 3 * 4)
58
58
  #define BASE64_DECODED_SIZE(base64len) (((base64len) + 3) / 4 * 3)
59
59
 
60
- void base64_encode( char *out, char *in, int len);
61
- int base64_decode( char *out, char *in, unsigned int len);
60
+ void base64_encode( char *out, const char *in, int len);
61
+ int base64_decode( char *out, const char *in, unsigned int len);
62
62
 
63
- int pg_strncasecmp(const char *s1, const char *s2, size_t n);
63
+ int rbpg_strncasecmp(const char *s1, const char *s2, size_t n);
64
64
 
65
65
  #endif /* end __utils_h */
@@ -1,7 +1,27 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'pg' unless defined?( PG )
4
4
 
5
+ # This module defines the mapping between OID and encoder/decoder classes for PG::BasicTypeMapForResults, PG::BasicTypeMapForQueries and PG::BasicTypeMapBasedOnResult.
6
+ #
7
+ # Additional types can be added like so:
8
+ #
9
+ # require 'pg'
10
+ # require 'ipaddr'
11
+ #
12
+ # class InetDecoder < PG::SimpleDecoder
13
+ # def decode(string, tuple=nil, field=nil)
14
+ # IPAddr.new(string)
15
+ # end
16
+ # end
17
+ # class InetEncoder < PG::SimpleEncoder
18
+ # def encode(ip_addr)
19
+ # ip_addr.to_s
20
+ # end
21
+ # end
22
+ #
23
+ # # 0 if for text format, can also be 1 for binary
24
+ # PG::BasicTypeRegistry.register_type(0, 'inet', InetEncoder, InetDecoder)
5
25
  module PG::BasicTypeRegistry
6
26
  # An instance of this class stores the coders that should be used for a given wire format (text or binary)
7
27
  # and type cast direction (encoder or decoder).
@@ -25,9 +45,9 @@ module PG::BasicTypeRegistry
25
45
 
26
46
  # populate the enum types
27
47
  _enums, leaves = leaves.partition { |row| row['typinput'] == 'enum_in' }
28
- # enums.each do |row|
29
- # coder_map[row['oid'].to_i] = OID::Enum.new
30
- # end
48
+ # enums.each do |row|
49
+ # coder_map[row['oid'].to_i] = OID::Enum.new
50
+ # end
31
51
 
32
52
  # populate the base types
33
53
  leaves.find_all { |row| coders_by_name.key?(row['typname']) }.each do |row|
@@ -41,9 +61,9 @@ module PG::BasicTypeRegistry
41
61
  _records_by_oid = result.group_by { |row| row['oid'] }
42
62
 
43
63
  # populate composite types
44
- # nodes.each do |row|
45
- # add_oid row, records_by_oid, coder_map
46
- # end
64
+ # nodes.each do |row|
65
+ # add_oid row, records_by_oid, coder_map
66
+ # end
47
67
 
48
68
  if arraycoder
49
69
  # populate array types
@@ -62,11 +82,11 @@ module PG::BasicTypeRegistry
62
82
  end
63
83
 
64
84
  # populate range types
65
- # ranges.find_all { |row| coder_map.key? row['rngsubtype'].to_i }.each do |row|
66
- # subcoder = coder_map[row['rngsubtype'].to_i]
67
- # range = OID::Range.new subcoder
68
- # coder_map[row['oid'].to_i] = range
69
- # end
85
+ # ranges.find_all { |row| coder_map.key? row['rngsubtype'].to_i }.each do |row|
86
+ # subcoder = coder_map[row['rngsubtype'].to_i]
87
+ # range = OID::Range.new subcoder
88
+ # coder_map[row['oid'].to_i] = range
89
+ # end
70
90
 
71
91
  @coders = coder_map.values
72
92
  @coders_by_name = @coders.inject({}){|h, t| h[t.name] = t; h }
@@ -137,6 +157,7 @@ module PG::BasicTypeRegistry
137
157
  # Register an OID type named +name+ with a typecasting encoder and decoder object in
138
158
  # +type+. +name+ should correspond to the `typname` column in
139
159
  # the `pg_type` table.
160
+ # +format+ can be 0 for text format and 1 for binary.
140
161
  def self.register_type(format, name, encoder_class, decoder_class)
141
162
  CODERS_BY_NAME[format] ||= { encoder: {}, decoder: {} }
142
163
  CODERS_BY_NAME[format][:encoder][name] = encoder_class.new(name: name, format: format) if encoder_class
@@ -145,8 +166,14 @@ module PG::BasicTypeRegistry
145
166
 
146
167
  # Alias the +old+ type to the +new+ type.
147
168
  def self.alias_type(format, new, old)
148
- CODERS_BY_NAME[format][:encoder][new] = CODERS_BY_NAME[format][:encoder][old]
149
- CODERS_BY_NAME[format][:decoder][new] = CODERS_BY_NAME[format][:decoder][old]
169
+ [:encoder, :decoder].each do |ende|
170
+ enc = CODERS_BY_NAME[format][ende][old]
171
+ if enc
172
+ CODERS_BY_NAME[format][ende][new] = enc
173
+ else
174
+ CODERS_BY_NAME[format][ende].delete(new)
175
+ end
176
+ end
150
177
  end
151
178
 
152
179
  register_type 0, 'int2', PG::TextEncoder::Integer, PG::TextDecoder::Integer
@@ -154,46 +181,47 @@ module PG::BasicTypeRegistry
154
181
  alias_type 0, 'int8', 'int2'
155
182
  alias_type 0, 'oid', 'int2'
156
183
 
157
- # register_type 0, 'numeric', OID::Decimal.new
184
+ register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric
158
185
  register_type 0, 'text', PG::TextEncoder::String, PG::TextDecoder::String
159
186
  alias_type 0, 'varchar', 'text'
160
187
  alias_type 0, 'char', 'text'
161
188
  alias_type 0, 'bpchar', 'text'
162
189
  alias_type 0, 'xml', 'text'
163
190
 
164
- # # FIXME: why are we keeping these types as strings?
165
- # alias_type 'tsvector', 'text'
166
- # alias_type 'interval', 'text'
167
- # alias_type 'macaddr', 'text'
168
- # alias_type 'uuid', 'text'
169
- #
170
- # register_type 'money', OID::Money.new
191
+ # FIXME: why are we keeping these types as strings?
192
+ # alias_type 'tsvector', 'text'
193
+ # alias_type 'interval', 'text'
194
+ # alias_type 'macaddr', 'text'
195
+ # alias_type 'uuid', 'text'
196
+ #
197
+ # register_type 'money', OID::Money.new
171
198
  # There is no PG::TextEncoder::Bytea, because it's simple and more efficient to send bytea-data
172
199
  # in binary format, either with PG::BinaryEncoder::Bytea or in Hash param format.
173
200
  register_type 0, 'bytea', nil, PG::TextDecoder::Bytea
174
201
  register_type 0, 'bool', PG::TextEncoder::Boolean, PG::TextDecoder::Boolean
175
- # register_type 'bit', OID::Bit.new
176
- # register_type 'varbit', OID::Bit.new
177
- #
202
+ # register_type 'bit', OID::Bit.new
203
+ # register_type 'varbit', OID::Bit.new
204
+
178
205
  register_type 0, 'float4', PG::TextEncoder::Float, PG::TextDecoder::Float
179
206
  alias_type 0, 'float8', 'float4'
180
207
 
181
208
  register_type 0, 'timestamp', PG::TextEncoder::TimestampWithoutTimeZone, PG::TextDecoder::TimestampWithoutTimeZone
182
209
  register_type 0, 'timestamptz', PG::TextEncoder::TimestampWithTimeZone, PG::TextDecoder::TimestampWithTimeZone
183
210
  register_type 0, 'date', PG::TextEncoder::Date, PG::TextDecoder::Date
184
- # register_type 'time', OID::Time.new
185
- #
186
- # register_type 'path', OID::Text.new
187
- # register_type 'point', OID::Point.new
188
- # register_type 'polygon', OID::Text.new
189
- # register_type 'circle', OID::Text.new
190
- # register_type 'hstore', OID::Hstore.new
191
- # register_type 'json', OID::Json.new
192
- # register_type 'citext', OID::Text.new
193
- # register_type 'ltree', OID::Text.new
194
- #
195
- # register_type 'cidr', OID::Cidr.new
196
- # alias_type 'inet', 'cidr'
211
+ # register_type 'time', OID::Time.new
212
+ #
213
+ # register_type 'path', OID::Text.new
214
+ # register_type 'point', OID::Point.new
215
+ # register_type 'polygon', OID::Text.new
216
+ # register_type 'circle', OID::Text.new
217
+ # register_type 'hstore', OID::Hstore.new
218
+ register_type 0, 'json', PG::TextEncoder::JSON, PG::TextDecoder::JSON
219
+ alias_type 0, 'jsonb', 'json'
220
+ # register_type 'citext', OID::Text.new
221
+ # register_type 'ltree', OID::Text.new
222
+ #
223
+ register_type 0, 'inet', PG::TextEncoder::Inet, PG::TextDecoder::Inet
224
+ alias_type 0, 'cidr', 'inet'
197
225
 
198
226
 
199
227
 
@@ -212,6 +240,8 @@ module PG::BasicTypeRegistry
212
240
  register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
213
241
  register_type 1, 'float4', nil, PG::BinaryDecoder::Float
214
242
  register_type 1, 'float8', nil, PG::BinaryDecoder::Float
243
+ register_type 1, 'timestamp', nil, PG::BinaryDecoder::TimestampUtc
244
+ register_type 1, 'timestamptz', nil, PG::BinaryDecoder::TimestampUtcToLocal
215
245
  end
216
246
 
217
247
  # Simple set of rules for type casting common PostgreSQL types to Ruby.
@@ -226,8 +256,8 @@ end
226
256
  #
227
257
  # Example:
228
258
  # conn = PG::Connection.new
229
- # # Assign a default ruleset for type casts of input and output values.
230
- # conn.type_mapping = PG::BasicTypeMapping.new(conn)
259
+ # # Assign a default ruleset for type casts of output values.
260
+ # conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn)
231
261
  # # Execute a query.
232
262
  # res = conn.exec_params( "SELECT $1::INT", ['5'] )
233
263
  # # Retrieve and cast the result value. Value format is 0 (text) and OID is 20. Therefore typecasting
@@ -236,8 +266,28 @@ end
236
266
  #
237
267
  # PG::TypeMapByOid#fit_to_result(result, false) can be used to generate
238
268
  # a result independent PG::TypeMapByColumn type map, which can subsequently be used
239
- # to cast #get_copy_data fields. See also PG::BasicTypeMapBasedOnResult .
269
+ # to cast #get_copy_data fields:
270
+ #
271
+ # For the following table:
272
+ # conn.exec( "CREATE TABLE copytable AS VALUES('a', 123, '{5,4,3}'::INT[])" )
240
273
  #
274
+ # # Retrieve table OIDs per empty result set.
275
+ # res = conn.exec( "SELECT * FROM copytable LIMIT 0" )
276
+ # # Build a type map for common database to ruby type decoders.
277
+ # btm = PG::BasicTypeMapForResults.new(conn)
278
+ # # Build a PG::TypeMapByColumn with decoders suitable for copytable.
279
+ # tm = btm.build_column_map( res )
280
+ # row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
281
+ #
282
+ # conn.copy_data( "COPY copytable TO STDOUT", row_decoder ) do |res|
283
+ # while row=conn.get_copy_data
284
+ # p row
285
+ # end
286
+ # end
287
+ # This prints the rows with type casted columns:
288
+ # ["a", 123, [5, 4, 3]]
289
+ #
290
+ # See also PG::BasicTypeMapBasedOnResult for the encoder direction and PG::BasicTypeRegistry for the definition of additional types.
241
291
  class PG::BasicTypeMapForResults < PG::TypeMapByOid
242
292
  include PG::BasicTypeRegistry
243
293
 
@@ -290,12 +340,17 @@ end
290
340
  #
291
341
  # # Retrieve table OIDs per empty result set.
292
342
  # res = conn.exec( "SELECT * FROM copytable LIMIT 0" )
293
- # tm = basic_type_mapping.build_column_map( res )
343
+ # # Build a type map for common ruby to database type encoders.
344
+ # btm = PG::BasicTypeMapBasedOnResult.new(conn)
345
+ # # Build a PG::TypeMapByColumn with encoders suitable for copytable.
346
+ # tm = btm.build_column_map( res )
294
347
  # row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
295
348
  #
296
349
  # conn.copy_data( "COPY copytable FROM STDIN", row_encoder ) do |res|
297
350
  # conn.put_copy_data ['a', 123, [5,4,3]]
298
351
  # end
352
+ # This inserts a single row into copytable with type casts from ruby to
353
+ # database types.
299
354
  class PG::BasicTypeMapBasedOnResult < PG::TypeMapByOid
300
355
  include PG::BasicTypeRegistry
301
356
 
@@ -314,15 +369,16 @@ end
314
369
  # OIDs of supported type casts are not hard-coded in the sources, but are retrieved from the
315
370
  # PostgreSQL's pg_type table in PG::BasicTypeMapForQueries.new .
316
371
  #
317
- # Query params are type casted based on the MRI internal type of the given value.
372
+ # Query params are type casted based on the class of the given value.
318
373
  #
319
374
  # Higher level libraries will most likely not make use of this class, but use their
320
- # own set of rules to choose suitable encoders and decoders.
375
+ # own derivation of PG::TypeMapByClass or another set of rules to choose suitable
376
+ # encoders and decoders for the values to be sent.
321
377
  #
322
378
  # Example:
323
379
  # conn = PG::Connection.new
324
380
  # # Assign a default ruleset for type casts of input and output values.
325
- # conn.type_mapping_for_queries = PG::BasicTypeMapForQueries.new(conn)
381
+ # conn.type_map_for_queries = PG::BasicTypeMapForQueries.new(conn)
326
382
  # # Execute a query. The Integer param value is typecasted internally by PG::BinaryEncoder::Int8.
327
383
  # # The format of the parameter is set to 1 (binary) and the OID of this parameter is set to 20 (int8).
328
384
  # res = conn.exec_params( "SELECT $1", [5] )
@@ -385,6 +441,10 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
385
441
  # to unnecessary type conversions on server side.
386
442
  Integer => [0, 'int8'],
387
443
  Float => [0, 'float8'],
444
+ BigDecimal => [0, 'numeric'],
445
+ # We use text format and no type OID for IPAddr, because setting the OID can lead
446
+ # to unnecessary inet/cidr conversions on the server side.
447
+ IPAddr => [0, 'inet'],
388
448
  Array => :get_array_type,
389
449
  }
390
450
 
@@ -0,0 +1,22 @@
1
+ # -*- ruby -*-
2
+
3
+ module PG
4
+ module BinaryDecoder
5
+ # Convenience classes for timezone options
6
+ class TimestampUtc < Timestamp
7
+ def initialize(params={})
8
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC))
9
+ end
10
+ end
11
+ class TimestampUtcToLocal < Timestamp
12
+ def initialize(params={})
13
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL))
14
+ end
15
+ end
16
+ class TimestampLocal < Timestamp
17
+ def initialize(params={})
18
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL))
19
+ end
20
+ end
21
+ end
22
+ end # module PG
data/lib/pg/coder.rb CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  module PG
4
4
 
data/lib/pg/connection.rb CHANGED
@@ -1,6 +1,7 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'pg' unless defined?( PG )
4
+ require 'uri'
4
5
 
5
6
  # The PostgreSQL connection class. The interface for this class is based on
6
7
  # {libpq}[http://www.postgresql.org/docs/9.2/interactive/libpq.html], the C
@@ -34,48 +35,57 @@ class PG::Connection
34
35
  def self::parse_connect_args( *args )
35
36
  return '' if args.empty?
36
37
 
37
- # This will be swapped soon for code that makes options like those required for
38
- # PQconnectdbParams()/PQconnectStartParams(). For now, stick to an options string for
39
- # PQconnectdb()/PQconnectStart().
38
+ hash_arg = args.last.is_a?( Hash ) ? args.pop : {}
39
+ option_string = ''
40
+ options = {}
40
41
 
41
42
  # Parameter 'fallback_application_name' was introduced in PostgreSQL 9.0
42
43
  # together with PQescapeLiteral().
43
- if PG::Connection.instance_methods.find{|m| m.to_sym == :escape_literal }
44
- appname = $0.sub(/^(.{30}).{4,}(.{30})$/){ $1+"..."+$2 }
45
- appname = PG::Connection.quote_connstr( appname )
46
- connopts = ["fallback_application_name=#{appname}"]
47
- else
48
- connopts = []
44
+ if PG::Connection.instance_methods.find {|m| m.to_sym == :escape_literal }
45
+ options[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
49
46
  end
50
47
 
51
- # Handle an options hash first
52
- if args.last.is_a?( Hash )
53
- opthash = args.pop
54
- opthash.each do |key, val|
55
- connopts.push( "%s=%s" % [key, PG::Connection.quote_connstr(val)] )
48
+ if args.length == 1
49
+ case args.first
50
+ when URI, /\A#{URI.regexp}\z/
51
+ uri = URI(args.first)
52
+ options.merge!( Hash[URI.decode_www_form( uri.query )] ) if uri.query
53
+ when /=/
54
+ # Option string style
55
+ option_string = args.first.to_s
56
+ else
57
+ # Positional parameters
58
+ options[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
56
59
  end
57
- end
58
-
59
- # Option string style
60
- if args.length == 1 && args.first.to_s.index( '=' )
61
- connopts.unshift( args.first )
62
-
63
- # Append positional parameters
64
60
  else
65
- args.each_with_index do |val, i|
66
- next unless val # Skip nil placeholders
61
+ max = CONNECT_ARGUMENT_ORDER.length
62
+ raise ArgumentError,
63
+ "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
67
64
 
68
- key = CONNECT_ARGUMENT_ORDER[ i ] or
69
- raise ArgumentError, "Extra positional parameter %d: %p" % [ i+1, val ]
70
- connopts.push( "%s=%s" % [key, PG::Connection.quote_connstr(val.to_s)] )
65
+ CONNECT_ARGUMENT_ORDER.zip( args ) do |(k,v)|
66
+ options[ k.to_sym ] = v if v
71
67
  end
72
68
  end
73
69
 
74
- return connopts.join(' ')
70
+ options.merge!( hash_arg )
71
+
72
+ if uri
73
+ uri.host = nil if options[:host]
74
+ uri.port = nil if options[:port]
75
+ uri.user = nil if options[:user]
76
+ uri.password = nil if options[:password]
77
+ uri.path = '' if options[:dbname]
78
+ uri.query = URI.encode_www_form( options )
79
+ return uri.to_s.sub( /^#{uri.scheme}:(?!\/\/)/, "#{uri.scheme}://" )
80
+ else
81
+ option_string += ' ' unless option_string.empty? && options.empty?
82
+ return option_string + options.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
83
+ end
75
84
  end
76
85
 
86
+
77
87
  # call-seq:
78
- # conn.copy_data( sql ) {|sql_result| ... } -> PG::Result
88
+ # conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
79
89
  #
80
90
  # Execute a copy process for transfering data to or from the server.
81
91
  #
@@ -99,13 +109,26 @@ class PG::Connection
99
109
  # of blocking mode of operation, #copy_data is preferred to raw calls
100
110
  # of #put_copy_data, #get_copy_data and #put_copy_end.
101
111
  #
112
+ # _coder_ can be a PG::Coder derivation
113
+ # (typically PG::TextEncoder::CopyRow or PG::TextDecoder::CopyRow).
114
+ # This enables encoding of data fields given to #put_copy_data
115
+ # or decoding of fields received by #get_copy_data.
116
+ #
102
117
  # Example with CSV input format:
103
- # conn.exec "create table my_table (a text,b text,c text,d text,e text)"
118
+ # conn.exec "create table my_table (a text,b text,c text,d text)"
104
119
  # conn.copy_data "COPY my_table FROM STDIN CSV" do
105
- # conn.put_copy_data "some,csv,data,to,copy\n"
106
- # conn.put_copy_data "more,csv,data,to,copy\n"
120
+ # conn.put_copy_data "some,data,to,copy\n"
121
+ # conn.put_copy_data "more,data,to,copy\n"
122
+ # end
123
+ # This creates +my_table+ and inserts two CSV rows.
124
+ #
125
+ # The same with text format encoder PG::TextEncoder::CopyRow
126
+ # and Array input:
127
+ # enco = PG::TextEncoder::CopyRow.new
128
+ # conn.copy_data "COPY my_table FROM STDIN", enco do
129
+ # conn.put_copy_data ['some', 'data', 'to', 'copy']
130
+ # conn.put_copy_data ['more', 'data', 'to', 'copy']
107
131
  # end
108
- # This creates +my_table+ and inserts two rows.
109
132
  #
110
133
  # Example with CSV output format:
111
134
  # conn.copy_data "COPY my_table TO STDOUT CSV" do
@@ -114,8 +137,21 @@ class PG::Connection
114
137
  # end
115
138
  # end
116
139
  # This prints all rows of +my_table+ to stdout:
117
- # "some,csv,data,to,copy\n"
118
- # "more,csv,data,to,copy\n"
140
+ # "some,data,to,copy\n"
141
+ # "more,data,to,copy\n"
142
+ #
143
+ # The same with text format decoder PG::TextDecoder::CopyRow
144
+ # and Array output:
145
+ # deco = PG::TextDecoder::CopyRow.new
146
+ # conn.copy_data "COPY my_table TO STDOUT", deco do
147
+ # while row=conn.get_copy_data
148
+ # p row
149
+ # end
150
+ # end
151
+ # This receives all rows of +my_table+ as ruby array:
152
+ # ["some", "data", "to", "copy"]
153
+ # ["more", "data", "to", "copy"]
154
+
119
155
  def copy_data( sql, coder=nil )
120
156
  res = exec( sql )
121
157
 
@@ -155,7 +191,7 @@ class PG::Connection
155
191
  raise
156
192
  else
157
193
  res = get_last_result
158
- if res.result_status != PGRES_COMMAND_OK
194
+ if !res || res.result_status != PGRES_COMMAND_OK
159
195
  while get_copy_data
160
196
  end
161
197
  while get_result
@@ -214,8 +250,42 @@ class PG::Connection
214
250
  end
215
251
  end
216
252
 
217
- end # class PG::Connection
253
+ # Method 'ssl_attribute' was introduced in PostgreSQL 9.5.
254
+ if self.instance_methods.find{|m| m.to_sym == :ssl_attribute }
255
+ # call-seq:
256
+ # conn.ssl_attributes -> Hash<String,String>
257
+ #
258
+ # Returns SSL-related information about the connection as key/value pairs
259
+ #
260
+ # The available attributes varies depending on the SSL library being used,
261
+ # and the type of connection.
262
+ #
263
+ # See also #ssl_attribute
264
+ def ssl_attributes
265
+ ssl_attribute_names.each.with_object({}) do |n,h|
266
+ h[n] = ssl_attribute(n)
267
+ end
268
+ end
269
+ end
270
+
271
+ REDIRECT_METHODS = {
272
+ :exec => [:async_exec, :sync_exec],
273
+ :query => [:async_exec, :sync_exec],
274
+ :exec_params => [:async_exec_params, :sync_exec_params],
275
+ :prepare => [:async_prepare, :sync_prepare],
276
+ :exec_prepared => [:async_exec_prepared, :sync_exec_prepared],
277
+ :describe_portal => [:async_describe_portal, :sync_describe_portal],
278
+ :describe_prepared => [:async_describe_prepared, :sync_describe_prepared],
279
+ }
280
+
281
+ def self.async_api=(enable)
282
+ REDIRECT_METHODS.each do |ali, (async, sync)|
283
+ remove_method(ali) if method_defined?(ali)
284
+ alias_method( ali, enable ? async : sync )
285
+ end
286
+ end
218
287
 
219
- # Backward-compatible alias
220
- PGconn = PG::Connection
288
+ # pg-1.1.0+ defaults to libpq's async API for query related blocking methods
289
+ self.async_api = true
290
+ end # class PG::Connection
221
291
 
data/lib/pg/constants.rb CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'pg' unless defined?( PG )
4
4
 
data/lib/pg/exceptions.rb CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'pg' unless defined?( PG )
4
4
 
data/lib/pg/result.rb CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'pg' unless defined?( PG )
4
4
 
@@ -12,15 +12,20 @@ class PG::Result
12
12
  # See PG::BasicTypeMapForResults
13
13
  def map_types!(type_map)
14
14
  self.type_map = type_map
15
- self
15
+ return self
16
16
  end
17
17
 
18
+
19
+ ### Return a String representation of the object suitable for debugging.
18
20
  def inspect
19
21
  str = self.to_s
20
- str[-1,0] = " status=#{res_status(result_status)} ntuples=#{ntuples} nfields=#{nfields} cmd_tuples=#{cmd_tuples}"
21
- str
22
+ str[-1,0] = if cleared?
23
+ " cleared"
24
+ else
25
+ " status=#{res_status(result_status)} ntuples=#{ntuples} nfields=#{nfields} cmd_tuples=#{cmd_tuples}"
26
+ end
27
+ return str
22
28
  end
29
+
23
30
  end # class PG::Result
24
31
 
25
- # Backward-compatible alias
26
- PGresult = PG::Result
@@ -1,4 +1,7 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+
3
+ require 'date'
4
+ require 'json'
2
5
 
3
6
  module PG
4
7
  module TextDecoder
@@ -7,36 +10,38 @@ module PG
7
10
 
8
11
  def decode(string, tuple=nil, field=nil)
9
12
  if string =~ ISO_DATE
10
- Time.new $1.to_i, $2.to_i, $3.to_i
13
+ ::Date.new $1.to_i, $2.to_i, $3.to_i
11
14
  else
12
15
  string
13
16
  end
14
17
  end
15
18
  end
16
19
 
17
- class TimestampWithoutTimeZone < SimpleDecoder
18
- ISO_DATETIME_WITHOUT_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
19
-
20
+ class JSON < SimpleDecoder
20
21
  def decode(string, tuple=nil, field=nil)
21
- if string =~ ISO_DATETIME_WITHOUT_TIMEZONE
22
- Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r
23
- else
24
- string
25
- end
22
+ ::JSON.parse(string, quirks_mode: true)
26
23
  end
27
24
  end
28
25
 
29
- class TimestampWithTimeZone < SimpleDecoder
30
- ISO_DATETIME_WITH_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?([-\+]\d\d)\z/
31
-
32
- def decode(string, tuple=nil, field=nil)
33
- if string =~ ISO_DATETIME_WITH_TIMEZONE
34
- Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r, "#{$8}:00"
35
- else
36
- string
37
- end
26
+ # Convenience classes for timezone options
27
+ class TimestampUtc < Timestamp
28
+ def initialize(params={})
29
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC))
30
+ end
31
+ end
32
+ class TimestampUtcToLocal < Timestamp
33
+ def initialize(params={})
34
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL))
38
35
  end
39
36
  end
37
+ class TimestampLocal < Timestamp
38
+ def initialize(params={})
39
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL))
40
+ end
41
+ end
42
+
43
+ # For backward compatibility:
44
+ TimestampWithoutTimeZone = TimestampLocal
45
+ TimestampWithTimeZone = Timestamp
40
46
  end
41
47
  end # module PG
42
-