pg 1.0.0 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +0 -6595
  5. data/History.rdoc +156 -0
  6. data/Manifest.txt +8 -2
  7. data/README-Windows.rdoc +4 -4
  8. data/README.ja.rdoc +1 -2
  9. data/README.rdoc +55 -9
  10. data/Rakefile +9 -7
  11. data/Rakefile.cross +58 -57
  12. data/ext/errorcodes.def +68 -0
  13. data/ext/errorcodes.rb +1 -1
  14. data/ext/errorcodes.txt +19 -2
  15. data/ext/extconf.rb +7 -5
  16. data/ext/pg.c +141 -98
  17. data/ext/pg.h +64 -21
  18. data/ext/pg_binary_decoder.c +82 -15
  19. data/ext/pg_binary_encoder.c +13 -12
  20. data/ext/pg_coder.c +73 -12
  21. data/ext/pg_connection.c +625 -346
  22. data/ext/pg_copy_coder.c +16 -8
  23. data/ext/pg_record_coder.c +491 -0
  24. data/ext/pg_result.c +571 -191
  25. data/ext/pg_text_decoder.c +606 -40
  26. data/ext/pg_text_encoder.c +185 -54
  27. data/ext/pg_tuple.c +549 -0
  28. data/ext/pg_type_map.c +1 -1
  29. data/ext/pg_type_map_all_strings.c +4 -4
  30. data/ext/pg_type_map_by_class.c +9 -4
  31. data/ext/pg_type_map_by_column.c +7 -6
  32. data/ext/pg_type_map_by_mri_type.c +1 -1
  33. data/ext/pg_type_map_by_oid.c +3 -2
  34. data/ext/pg_type_map_in_ruby.c +1 -1
  35. data/ext/{util.c → pg_util.c} +10 -10
  36. data/ext/{util.h → pg_util.h} +2 -2
  37. data/lib/pg.rb +8 -6
  38. data/lib/pg/basic_type_mapping.rb +121 -25
  39. data/lib/pg/binary_decoder.rb +23 -0
  40. data/lib/pg/coder.rb +23 -2
  41. data/lib/pg/connection.rb +22 -3
  42. data/lib/pg/constants.rb +2 -1
  43. data/lib/pg/exceptions.rb +2 -1
  44. data/lib/pg/result.rb +14 -2
  45. data/lib/pg/text_decoder.rb +21 -26
  46. data/lib/pg/text_encoder.rb +32 -8
  47. data/lib/pg/tuple.rb +30 -0
  48. data/lib/pg/type_map_by_column.rb +3 -2
  49. data/spec/helpers.rb +52 -20
  50. data/spec/pg/basic_type_mapping_spec.rb +362 -37
  51. data/spec/pg/connection_spec.rb +376 -146
  52. data/spec/pg/connection_sync_spec.rb +41 -0
  53. data/spec/pg/result_spec.rb +240 -15
  54. data/spec/pg/tuple_spec.rb +333 -0
  55. data/spec/pg/type_map_by_class_spec.rb +2 -2
  56. data/spec/pg/type_map_by_column_spec.rb +6 -2
  57. data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
  58. data/spec/pg/type_map_by_oid_spec.rb +3 -3
  59. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  60. data/spec/pg/type_map_spec.rb +1 -1
  61. data/spec/pg/type_spec.rb +363 -17
  62. data/spec/pg_spec.rb +1 -1
  63. metadata +47 -47
  64. metadata.gz.sig +0 -0
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module PG
5
+ module BinaryDecoder
6
+ # Convenience classes for timezone options
7
+ class TimestampUtc < Timestamp
8
+ def initialize(params={})
9
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC))
10
+ end
11
+ end
12
+ class TimestampUtcToLocal < Timestamp
13
+ def initialize(params={})
14
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL))
15
+ end
16
+ end
17
+ class TimestampLocal < Timestamp
18
+ def initialize(params={})
19
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL))
20
+ end
21
+ end
22
+ end
23
+ end # module PG
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  module PG
4
5
 
@@ -28,6 +29,7 @@ module PG
28
29
  {
29
30
  oid: oid,
30
31
  format: format,
32
+ flags: flags,
31
33
  name: name,
32
34
  }
33
35
  end
@@ -52,6 +54,18 @@ module PG
52
54
  str[-1,0] = "#{name_str} #{oid_str}#{format_str}"
53
55
  str
54
56
  end
57
+
58
+ def inspect_short
59
+ str = case format
60
+ when 0 then "T"
61
+ when 1 then "B"
62
+ else format.to_s
63
+ end
64
+ str += "E" if respond_to?(:encode)
65
+ str += "D" if respond_to?(:decode)
66
+
67
+ "#{name || self.class.name}:#{str}"
68
+ end
55
69
  end
56
70
 
57
71
  class CompositeCoder < Coder
@@ -79,5 +93,12 @@ module PG
79
93
  })
80
94
  end
81
95
  end
82
- end # module PG
83
96
 
97
+ class RecordCoder < Coder
98
+ def to_h
99
+ super.merge!({
100
+ type_map: type_map,
101
+ })
102
+ end
103
+ end
104
+ end # module PG
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
  require 'uri'
@@ -47,7 +48,7 @@ class PG::Connection
47
48
 
48
49
  if args.length == 1
49
50
  case args.first
50
- when URI, /\A#{URI.regexp}\z/
51
+ when URI, /\A#{URI::ABS_URI_REF}\z/
51
52
  uri = URI(args.first)
52
53
  options.merge!( Hash[URI.decode_www_form( uri.query )] ) if uri.query
53
54
  when /=/
@@ -268,5 +269,23 @@ class PG::Connection
268
269
  end
269
270
  end
270
271
 
271
- end # class PG::Connection
272
+ REDIRECT_METHODS = {
273
+ :exec => [:async_exec, :sync_exec],
274
+ :query => [:async_exec, :sync_exec],
275
+ :exec_params => [:async_exec_params, :sync_exec_params],
276
+ :prepare => [:async_prepare, :sync_prepare],
277
+ :exec_prepared => [:async_exec_prepared, :sync_exec_prepared],
278
+ :describe_portal => [:async_describe_portal, :sync_describe_portal],
279
+ :describe_prepared => [:async_describe_prepared, :sync_describe_prepared],
280
+ }
272
281
 
282
+ def self.async_api=(enable)
283
+ REDIRECT_METHODS.each do |ali, (async, sync)|
284
+ remove_method(ali) if method_defined?(ali)
285
+ alias_method( ali, enable ? async : sync )
286
+ end
287
+ end
288
+
289
+ # pg-1.1.0+ defaults to libpq's async API for query related blocking methods
290
+ self.async_api = true
291
+ end # class PG::Connection
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
 
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
 
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
 
@@ -9,12 +10,23 @@ class PG::Result
9
10
  #
10
11
  # +type_map+: a PG::TypeMap instance.
11
12
  #
12
- # See PG::BasicTypeMapForResults
13
+ # This method is equal to #type_map= , but returns self, so that calls can be chained.
14
+ #
15
+ # See also PG::BasicTypeMapForResults
13
16
  def map_types!(type_map)
14
17
  self.type_map = type_map
15
18
  return self
16
19
  end
17
20
 
21
+ # Set the data type for all field name returning methods.
22
+ #
23
+ # +type+: a Symbol defining the field name type.
24
+ #
25
+ # This method is equal to #field_name_type= , but returns self, so that calls can be chained.
26
+ def field_names_as(type)
27
+ self.field_name_type = type
28
+ return self
29
+ end
18
30
 
19
31
  ### Return a String representation of the object suitable for debugging.
20
32
  def inspect
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'date'
4
5
  require 'json'
@@ -6,10 +7,8 @@ require 'json'
6
7
  module PG
7
8
  module TextDecoder
8
9
  class Date < SimpleDecoder
9
- ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
10
-
11
10
  def decode(string, tuple=nil, field=nil)
12
- if string =~ ISO_DATE
11
+ if string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
13
12
  ::Date.new $1.to_i, $2.to_i, $3.to_i
14
13
  else
15
14
  string
@@ -17,35 +16,31 @@ module PG
17
16
  end
18
17
  end
19
18
 
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
-
19
+ class JSON < SimpleDecoder
23
20
  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
21
+ ::JSON.parse(string, quirks_mode: true)
29
22
  end
30
23
  end
31
24
 
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
25
+ # Convenience classes for timezone options
26
+ class TimestampUtc < Timestamp
27
+ def initialize(params={})
28
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC))
41
29
  end
42
30
  end
43
-
44
- class JSON < SimpleDecoder
45
- def decode(string, tuple=nil, field=nil)
46
- ::JSON.parse(string, quirks_mode: true)
31
+ class TimestampUtcToLocal < Timestamp
32
+ def initialize(params={})
33
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL))
34
+ end
35
+ end
36
+ class TimestampLocal < Timestamp
37
+ def initialize(params={})
38
+ super(params.merge(flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL))
47
39
  end
48
40
  end
41
+
42
+ # For backward compatibility:
43
+ TimestampWithoutTimeZone = TimestampLocal
44
+ TimestampWithTimeZone = Timestamp
49
45
  end
50
46
  end # module PG
51
-
@@ -1,27 +1,32 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'json'
5
+ require 'ipaddr'
4
6
 
5
7
  module PG
6
8
  module TextEncoder
7
9
  class Date < SimpleEncoder
8
- STRFTIME_ISO_DATE = "%Y-%m-%d".freeze
9
10
  def encode(value)
10
- value.respond_to?(:strftime) ? value.strftime(STRFTIME_ISO_DATE) : value
11
+ value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : value
11
12
  end
12
13
  end
13
14
 
14
15
  class TimestampWithoutTimeZone < SimpleEncoder
15
- STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N".freeze
16
16
  def encode(value)
17
- value.respond_to?(:strftime) ? value.strftime(STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE) : value
17
+ value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S.%N") : value
18
+ end
19
+ end
20
+
21
+ class TimestampUtc < SimpleEncoder
22
+ def encode(value)
23
+ value.respond_to?(:utc) ? value.utc.strftime("%Y-%m-%d %H:%M:%S.%N") : value
18
24
  end
19
25
  end
20
26
 
21
27
  class TimestampWithTimeZone < SimpleEncoder
22
- STRFTIME_ISO_DATETIME_WITH_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N %:z".freeze
23
28
  def encode(value)
24
- value.respond_to?(:strftime) ? value.strftime(STRFTIME_ISO_DATETIME_WITH_TIMEZONE) : value
29
+ value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S.%N %:z") : value
25
30
  end
26
31
  end
27
32
 
@@ -30,6 +35,25 @@ module PG
30
35
  ::JSON.generate(value, quirks_mode: true)
31
36
  end
32
37
  end
38
+
39
+ class Inet < SimpleEncoder
40
+ def encode(value)
41
+ case value
42
+ when IPAddr
43
+ default_prefix = (value.family == Socket::AF_INET ? 32 : 128)
44
+ s = value.to_s
45
+ if value.respond_to?(:prefix)
46
+ prefix = value.prefix
47
+ else
48
+ range = value.to_range
49
+ prefix = default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i
50
+ end
51
+ s << "/" << prefix.to_s if prefix != default_prefix
52
+ s
53
+ else
54
+ value
55
+ end
56
+ end
57
+ end
33
58
  end
34
59
  end # module PG
35
-
@@ -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
@@ -1,4 +1,5 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
5
 
@@ -9,7 +10,7 @@ class PG::TypeMapByColumn
9
10
  end
10
11
 
11
12
  def inspect
12
- type_strings = coders.map{|c| c ? "#{c.name}:#{c.format}" : 'nil' }
13
+ type_strings = coders.map{|c| c ? c.inspect_short : 'nil' }
13
14
  "#<#{self.class} #{type_strings.join(' ')}>"
14
15
  end
15
16
  end
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'pathname'
4
4
  require 'rspec'
@@ -22,12 +22,11 @@ module PG::TestingHelpers
22
22
 
23
23
  mod.around( :each ) do |example|
24
24
  begin
25
+ @conn.set_default_encoding
25
26
  @conn.exec( 'BEGIN' ) unless example.metadata[:without_transaction]
26
- if PG.respond_to?( :library_version )
27
- desc = example.source_location.join(':')
28
- @conn.exec_params %Q{SET application_name TO '%s'} %
29
- [@conn.escape_string(desc.slice(-60))]
30
- end
27
+ desc = example.source_location.join(':')
28
+ @conn.exec %Q{SET application_name TO '%s'} %
29
+ [@conn.escape_string(desc.slice(-60))]
31
30
  example.run
32
31
  ensure
33
32
  @conn.exec( 'ROLLBACK' ) unless example.metadata[:without_transaction]
@@ -173,18 +172,18 @@ module PG::TestingHelpers
173
172
  datadir = testdir + 'data'
174
173
  pidfile = datadir + 'postmaster.pid'
175
174
  if pidfile.exist? && pid = pidfile.read.chomp.to_i
176
- $stderr.puts "pidfile (%p) exists: %d" % [ pidfile, pid ]
175
+ trace "pidfile (%p) exists: %d" % [ pidfile, pid ]
177
176
  begin
178
177
  Process.kill( 0, pid )
179
178
  rescue Errno::ESRCH
180
- $stderr.puts "No postmaster running for %s" % [ datadir ]
179
+ trace "No postmaster running for %s" % [ datadir ]
181
180
  # Process isn't alive, so don't try to stop it
182
181
  else
183
- $stderr.puts "Stopping lingering database at PID %d" % [ pid ]
182
+ trace "Stopping lingering database at PID %d" % [ pid ]
184
183
  run 'pg_ctl', '-D', datadir.to_s, '-m', 'fast', 'stop'
185
184
  end
186
185
  else
187
- $stderr.puts "No pidfile (%p)" % [ pidfile ]
186
+ trace "No pidfile (%p)" % [ pidfile ]
188
187
  end
189
188
  end
190
189
  end
@@ -195,12 +194,12 @@ module PG::TestingHelpers
195
194
  require 'pg'
196
195
  stop_existing_postmasters()
197
196
 
198
- puts "Setting up test database for #{description}"
197
+ trace "Setting up test database for #{description}"
199
198
  @test_pgdata = TEST_DIRECTORY + 'data'
200
199
  @test_pgdata.mkpath
201
200
 
202
- @port = 54321
203
- ENV['PGPORT'] = @port.to_s
201
+ ENV['PGPORT'] ||= "54321"
202
+ @port = ENV['PGPORT'].to_i
204
203
  ENV['PGHOST'] = 'localhost'
205
204
  @conninfo = "host=localhost port=#{@port} dbname=test"
206
205
 
@@ -210,7 +209,7 @@ module PG::TestingHelpers
210
209
  begin
211
210
  unless (@test_pgdata+"postgresql.conf").exist?
212
211
  FileUtils.rm_rf( @test_pgdata, :verbose => $DEBUG )
213
- $stderr.puts "Running initdb"
212
+ trace "Running initdb"
214
213
  log_and_run @logfile, 'initdb', '-E', 'UTF8', '--no-locale', '-D', @test_pgdata.to_s
215
214
  end
216
215
 
@@ -219,14 +218,14 @@ module PG::TestingHelpers
219
218
  '-D', @test_pgdata.to_s, 'start'
220
219
  sleep 2
221
220
 
222
- $stderr.puts "Creating the test DB"
221
+ trace "Creating the test DB"
223
222
  log_and_run @logfile, 'psql', '-e', '-c', 'DROP DATABASE IF EXISTS test', 'postgres'
224
223
  log_and_run @logfile, 'createdb', '-e', 'test'
225
224
 
226
225
  rescue => err
227
226
  $stderr.puts "%p during test setup: %s" % [ err.class, err.message ]
228
227
  $stderr.puts "See #{@logfile} for details."
229
- $stderr.puts *err.backtrace if $DEBUG
228
+ $stderr.puts err.backtrace if $DEBUG
230
229
  fail
231
230
  end
232
231
 
@@ -240,7 +239,7 @@ module PG::TestingHelpers
240
239
 
241
240
 
242
241
  def teardown_testing_db( conn )
243
- puts "Tearing down test database"
242
+ trace "Tearing down test database"
244
243
 
245
244
  if conn
246
245
  check_for_lingering_connections( conn )
@@ -266,7 +265,7 @@ module PG::TestingHelpers
266
265
 
267
266
  # Retrieve the names of the column types of a given result set.
268
267
  def result_typenames(res)
269
- @conn.exec( "SELECT " + res.nfields.times.map{|i| "format_type($#{i*2+1},$#{i*2+2})"}.join(","),
268
+ @conn.exec_params( "SELECT " + res.nfields.times.map{|i| "format_type($#{i*2+1},$#{i*2+2})"}.join(","),
270
269
  res.nfields.times.map{|i| [res.ftype(i), res.fmod(i)] }.flatten ).
271
270
  values[0]
272
271
  end
@@ -320,6 +319,39 @@ module PG::TestingHelpers
320
319
  return ConnStillUsableMatcher.new
321
320
  end
322
321
 
322
+ def wait_for_polling_ok(conn, meth = :connect_poll)
323
+ status = conn.send(meth)
324
+
325
+ while status != PG::PGRES_POLLING_OK
326
+ if status == PG::PGRES_POLLING_READING
327
+ select( [conn.socket_io], [], [], 5.0 ) or
328
+ raise "Asynchronous connection timed out!"
329
+
330
+ elsif status == PG::PGRES_POLLING_WRITING
331
+ select( [], [conn.socket_io], [], 5.0 ) or
332
+ raise "Asynchronous connection timed out!"
333
+ end
334
+ status = conn.send(meth)
335
+ end
336
+ end
337
+
338
+ def wait_for_query_result(conn)
339
+ result = nil
340
+ loop do
341
+ # Buffer any incoming data on the socket until a full result is ready.
342
+ conn.consume_input
343
+ while conn.is_busy
344
+ select( [conn.socket_io], nil, nil, 5.0 ) or
345
+ raise "Timeout waiting for query response."
346
+ conn.consume_input
347
+ end
348
+
349
+ # Fetch the next result. If there isn't one, the query is finished
350
+ result = conn.get_result || break
351
+ end
352
+ result
353
+ end
354
+
323
355
  end
324
356
 
325
357
 
@@ -338,11 +370,11 @@ RSpec.configure do |config|
338
370
  else
339
371
  config.filter_run_excluding :windows
340
372
  end
341
- config.filter_run_excluding :socket_io unless
342
- PG::Connection.instance_methods.map( &:to_sym ).include?( :socket_io )
343
373
 
344
374
  config.filter_run_excluding( :postgresql_93 ) if PG.library_version < 90300
345
375
  config.filter_run_excluding( :postgresql_94 ) if PG.library_version < 90400
346
376
  config.filter_run_excluding( :postgresql_95 ) if PG.library_version < 90500
377
+ config.filter_run_excluding( :postgresql_96 ) if PG.library_version < 90600
347
378
  config.filter_run_excluding( :postgresql_10 ) if PG.library_version < 100000
379
+ config.filter_run_excluding( :postgresql_12 ) if PG.library_version < 120000
348
380
  end