pg 0.21.0 → 1.1.4

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