pg 0.18.0 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
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
-