pg 0.21.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 (55) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/History.rdoc +98 -0
  5. data/Manifest.txt +5 -1
  6. data/README.rdoc +14 -4
  7. data/Rakefile +4 -5
  8. data/Rakefile.cross +17 -21
  9. data/ext/errorcodes.def +12 -0
  10. data/ext/errorcodes.rb +1 -1
  11. data/ext/errorcodes.txt +4 -1
  12. data/ext/extconf.rb +14 -32
  13. data/ext/gvl_wrappers.c +4 -0
  14. data/ext/gvl_wrappers.h +23 -39
  15. data/ext/pg.c +23 -50
  16. data/ext/pg.h +51 -81
  17. data/ext/pg_binary_decoder.c +73 -6
  18. data/ext/pg_coder.c +52 -3
  19. data/ext/pg_connection.c +369 -219
  20. data/ext/pg_copy_coder.c +10 -5
  21. data/ext/pg_result.c +343 -119
  22. data/ext/pg_text_decoder.c +597 -37
  23. data/ext/pg_text_encoder.c +6 -7
  24. data/ext/pg_tuple.c +541 -0
  25. data/ext/util.c +6 -6
  26. data/ext/util.h +2 -2
  27. data/lib/pg.rb +5 -7
  28. data/lib/pg/basic_type_mapping.rb +40 -7
  29. data/lib/pg/binary_decoder.rb +22 -0
  30. data/lib/pg/coder.rb +1 -1
  31. data/lib/pg/connection.rb +27 -3
  32. data/lib/pg/constants.rb +1 -1
  33. data/lib/pg/exceptions.rb +1 -1
  34. data/lib/pg/result.rb +1 -1
  35. data/lib/pg/text_decoder.rb +19 -23
  36. data/lib/pg/text_encoder.rb +35 -1
  37. data/lib/pg/tuple.rb +30 -0
  38. data/lib/pg/type_map_by_column.rb +1 -1
  39. data/spec/helpers.rb +49 -21
  40. data/spec/pg/basic_type_mapping_spec.rb +230 -27
  41. data/spec/pg/connection_spec.rb +473 -277
  42. data/spec/pg/connection_sync_spec.rb +41 -0
  43. data/spec/pg/result_spec.rb +48 -13
  44. data/spec/pg/tuple_spec.rb +280 -0
  45. data/spec/pg/type_map_by_class_spec.rb +1 -1
  46. data/spec/pg/type_map_by_column_spec.rb +1 -1
  47. data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
  48. data/spec/pg/type_map_by_oid_spec.rb +1 -1
  49. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  50. data/spec/pg/type_map_spec.rb +1 -1
  51. data/spec/pg/type_spec.rb +184 -12
  52. data/spec/pg_spec.rb +2 -2
  53. metadata +37 -33
  54. metadata.gz.sig +0 -0
  55. data/lib/pg/deprecated_constants.rb +0 -21
data/ext/util.c CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * util.c - Utils for ruby-pg
3
- * $Id: util.c,v 5fb9170f6a7d 2015/06/29 11:15:12 kanis $
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
- unsigned char *in_ptr = (unsigned char *)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 &&
data/ext/util.h CHANGED
@@ -57,8 +57,8 @@
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
63
  int rbpg_strncasecmp(const char *s1, const char *s2, size_t n);
64
64
 
data/lib/pg.rb CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  begin
4
4
  require 'pg_ext'
@@ -35,10 +35,10 @@ end
35
35
  module PG
36
36
 
37
37
  # Library version
38
- VERSION = '0.21.0'
38
+ VERSION = '1.1.4'
39
39
 
40
40
  # VCS revision
41
- REVISION = %q$Revision: f6063a34ae2b $
41
+ REVISION = %q$Revision: 6f611e78845a $
42
42
 
43
43
  class NotAllCopyDataRetrieved < PG::Error
44
44
  end
@@ -60,17 +60,15 @@ module PG
60
60
  require 'pg/exceptions'
61
61
  require 'pg/constants'
62
62
  require 'pg/coder'
63
+ require 'pg/binary_decoder'
63
64
  require 'pg/text_encoder'
64
65
  require 'pg/text_decoder'
65
66
  require 'pg/basic_type_mapping'
66
67
  require 'pg/type_map_by_column'
67
68
  require 'pg/connection'
68
69
  require 'pg/result'
70
+ require 'pg/tuple'
69
71
 
70
72
  end # module PG
71
73
 
72
74
 
73
- autoload :PGError, 'pg/deprecated_constants'
74
- autoload :PGconn, 'pg/deprecated_constants'
75
- autoload :PGresult, 'pg/deprecated_constants'
76
-
@@ -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).
@@ -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,7 +181,7 @@ 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'
@@ -193,8 +220,8 @@ module PG::BasicTypeRegistry
193
220
  # register_type 'citext', OID::Text.new
194
221
  # register_type 'ltree', OID::Text.new
195
222
  #
196
- # register_type 'cidr', OID::Cidr.new
197
- # alias_type 'inet', 'cidr'
223
+ register_type 0, 'inet', PG::TextEncoder::Inet, PG::TextDecoder::Inet
224
+ alias_type 0, 'cidr', 'inet'
198
225
 
199
226
 
200
227
 
@@ -213,6 +240,8 @@ module PG::BasicTypeRegistry
213
240
  register_type 1, 'bool', PG::BinaryEncoder::Boolean, PG::BinaryDecoder::Boolean
214
241
  register_type 1, 'float4', nil, PG::BinaryDecoder::Float
215
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
216
245
  end
217
246
 
218
247
  # Simple set of rules for type casting common PostgreSQL types to Ruby.
@@ -258,7 +287,7 @@ end
258
287
  # This prints the rows with type casted columns:
259
288
  # ["a", 123, [5, 4, 3]]
260
289
  #
261
- # See also PG::BasicTypeMapBasedOnResult for the encoder direction.
290
+ # See also PG::BasicTypeMapBasedOnResult for the encoder direction and PG::BasicTypeRegistry for the definition of additional types.
262
291
  class PG::BasicTypeMapForResults < PG::TypeMapByOid
263
292
  include PG::BasicTypeRegistry
264
293
 
@@ -412,6 +441,10 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
412
441
  # to unnecessary type conversions on server side.
413
442
  Integer => [0, 'int8'],
414
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'],
415
448
  Array => :get_array_type,
416
449
  }
417
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
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  module PG
4
4
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'pg' unless defined?( PG )
4
4
  require 'uri'
@@ -47,7 +47,7 @@ class PG::Connection
47
47
 
48
48
  if args.length == 1
49
49
  case args.first
50
- when URI, URI.regexp
50
+ when URI, /\A#{URI.regexp}\z/
51
51
  uri = URI(args.first)
52
52
  options.merge!( Hash[URI.decode_www_form( uri.query )] ) if uri.query
53
53
  when /=/
@@ -85,7 +85,7 @@ class PG::Connection
85
85
 
86
86
 
87
87
  # call-seq:
88
- # conn.copy_data( sql ) {|sql_result| ... } -> PG::Result
88
+ # conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
89
89
  #
90
90
  # Execute a copy process for transfering data to or from the server.
91
91
  #
@@ -109,6 +109,11 @@ class PG::Connection
109
109
  # of blocking mode of operation, #copy_data is preferred to raw calls
110
110
  # of #put_copy_data, #get_copy_data and #put_copy_end.
111
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
+ #
112
117
  # Example with CSV input format:
113
118
  # conn.exec "create table my_table (a text,b text,c text,d text)"
114
119
  # conn.copy_data "COPY my_table FROM STDIN CSV" do
@@ -263,5 +268,24 @@ class PG::Connection
263
268
  end
264
269
  end
265
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
287
+
288
+ # pg-1.1.0+ defaults to libpq's async API for query related blocking methods
289
+ self.async_api = true
266
290
  end # class PG::Connection
267
291
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'pg' unless defined?( PG )
4
4
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'pg' unless defined?( PG )
4
4
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'pg' unless defined?( PG )
4
4
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'date'
4
4
  require 'json'
@@ -17,35 +17,31 @@ module PG
17
17
  end
18
18
  end
19
19
 
20
- class TimestampWithoutTimeZone < SimpleDecoder
21
- ISO_DATETIME_WITHOUT_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
22
-
20
+ class JSON < SimpleDecoder
23
21
  def decode(string, tuple=nil, field=nil)
24
- if string =~ ISO_DATETIME_WITHOUT_TIMEZONE
25
- Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r
26
- else
27
- string
28
- end
22
+ ::JSON.parse(string, quirks_mode: true)
29
23
  end
30
24
  end
31
25
 
32
- class TimestampWithTimeZone < SimpleDecoder
33
- ISO_DATETIME_WITH_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?([-\+]\d\d):?(\d\d)?:?(\d\d)?\z/
34
-
35
- def decode(string, tuple=nil, field=nil)
36
- if string =~ ISO_DATETIME_WITH_TIMEZONE
37
- Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r, "#{$8}:#{$9 || '00'}:#{$10 || '00'}"
38
- else
39
- string
40
- 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))
41
30
  end
42
31
  end
43
-
44
- class JSON < SimpleDecoder
45
- def decode(string, tuple=nil, field=nil)
46
- ::JSON.parse(string, quirks_mode: true)
32
+ class TimestampUtcToLocal < Timestamp
33
+ def initialize(params={})
34
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL))
47
35
  end
48
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
49
46
  end
50
47
  end # module PG
51
-
@@ -1,6 +1,7 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'json'
4
+ require 'ipaddr'
4
5
 
5
6
  module PG
6
7
  module TextEncoder
@@ -18,6 +19,13 @@ module PG
18
19
  end
19
20
  end
20
21
 
22
+ class TimestampUtc < SimpleEncoder
23
+ STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE_UTC = "%Y-%m-%d %H:%M:%S.%N".freeze
24
+ def encode(value)
25
+ value.respond_to?(:utc) ? value.utc.strftime(STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE_UTC) : value
26
+ end
27
+ end
28
+
21
29
  class TimestampWithTimeZone < SimpleEncoder
22
30
  STRFTIME_ISO_DATETIME_WITH_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N %:z".freeze
23
31
  def encode(value)
@@ -25,11 +33,37 @@ module PG
25
33
  end
26
34
  end
27
35
 
36
+ class Numeric < SimpleEncoder
37
+ def encode(value)
38
+ value.is_a?(BigDecimal) ? value.to_s('F') : value
39
+ end
40
+ end
41
+
28
42
  class JSON < SimpleEncoder
29
43
  def encode(value)
30
44
  ::JSON.generate(value, quirks_mode: true)
31
45
  end
32
46
  end
47
+
48
+ class Inet < SimpleEncoder
49
+ def encode(value)
50
+ case value
51
+ when IPAddr
52
+ default_prefix = (value.family == Socket::AF_INET ? 32 : 128)
53
+ s = value.to_s
54
+ if value.respond_to?(:prefix)
55
+ prefix = value.prefix
56
+ else
57
+ range = value.to_range
58
+ prefix = default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i
59
+ end
60
+ s << "/" << prefix.to_s if prefix != default_prefix
61
+ s
62
+ else
63
+ value
64
+ end
65
+ end
66
+ end
33
67
  end
34
68
  end # module PG
35
69
 
@@ -0,0 +1,30 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'pg' unless defined?( PG )
5
+
6
+
7
+ class PG::Tuple
8
+
9
+ ### Return a String representation of the object suitable for debugging.
10
+ def inspect
11
+ "#<#{self.class} #{self.map{|k,v| "#{k}: #{v.inspect}" }.join(", ") }>"
12
+ end
13
+
14
+ def has_key?(key)
15
+ field_map.has_key?(key)
16
+ end
17
+ alias key? has_key?
18
+
19
+ def keys
20
+ field_names || field_map.keys.freeze
21
+ end
22
+
23
+ def each_key(&block)
24
+ if fn=field_names
25
+ fn.each(&block)
26
+ else
27
+ field_map.each_key(&block)
28
+ end
29
+ end
30
+ end