pg 0.18.4 → 1.2.3

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 (85) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/BSDL +2 -2
  5. data/ChangeLog +0 -5911
  6. data/History.rdoc +240 -0
  7. data/Manifest.txt +8 -20
  8. data/README-Windows.rdoc +4 -4
  9. data/README.ja.rdoc +1 -2
  10. data/README.rdoc +64 -15
  11. data/Rakefile +20 -21
  12. data/Rakefile.cross +67 -69
  13. data/ext/errorcodes.def +101 -0
  14. data/ext/errorcodes.rb +1 -1
  15. data/ext/errorcodes.txt +33 -2
  16. data/ext/extconf.rb +26 -36
  17. data/ext/gvl_wrappers.c +4 -0
  18. data/ext/gvl_wrappers.h +27 -39
  19. data/ext/pg.c +156 -145
  20. data/ext/pg.h +74 -98
  21. data/ext/pg_binary_decoder.c +82 -15
  22. data/ext/pg_binary_encoder.c +20 -19
  23. data/ext/pg_coder.c +103 -21
  24. data/ext/pg_connection.c +917 -523
  25. data/ext/pg_copy_coder.c +50 -12
  26. data/ext/pg_record_coder.c +491 -0
  27. data/ext/pg_result.c +590 -208
  28. data/ext/pg_text_decoder.c +606 -40
  29. data/ext/pg_text_encoder.c +245 -94
  30. data/ext/pg_tuple.c +549 -0
  31. data/ext/pg_type_map.c +14 -7
  32. data/ext/pg_type_map_all_strings.c +4 -4
  33. data/ext/pg_type_map_by_class.c +9 -4
  34. data/ext/pg_type_map_by_column.c +7 -6
  35. data/ext/pg_type_map_by_mri_type.c +1 -1
  36. data/ext/pg_type_map_by_oid.c +3 -2
  37. data/ext/pg_type_map_in_ruby.c +1 -1
  38. data/ext/{util.c → pg_util.c} +10 -10
  39. data/ext/{util.h → pg_util.h} +2 -2
  40. data/lib/pg.rb +23 -13
  41. data/lib/pg/basic_type_mapping.rb +155 -32
  42. data/lib/pg/binary_decoder.rb +23 -0
  43. data/lib/pg/coder.rb +23 -2
  44. data/lib/pg/connection.rb +73 -13
  45. data/lib/pg/constants.rb +2 -1
  46. data/lib/pg/exceptions.rb +2 -1
  47. data/lib/pg/result.rb +24 -7
  48. data/lib/pg/text_decoder.rb +24 -22
  49. data/lib/pg/text_encoder.rb +40 -8
  50. data/lib/pg/tuple.rb +30 -0
  51. data/lib/pg/type_map_by_column.rb +3 -2
  52. data/spec/helpers.rb +61 -36
  53. data/spec/pg/basic_type_mapping_spec.rb +415 -36
  54. data/spec/pg/connection_spec.rb +732 -327
  55. data/spec/pg/connection_sync_spec.rb +41 -0
  56. data/spec/pg/result_spec.rb +253 -21
  57. data/spec/pg/tuple_spec.rb +333 -0
  58. data/spec/pg/type_map_by_class_spec.rb +4 -4
  59. data/spec/pg/type_map_by_column_spec.rb +6 -2
  60. data/spec/pg/type_map_by_mri_type_spec.rb +2 -2
  61. data/spec/pg/type_map_by_oid_spec.rb +3 -3
  62. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  63. data/spec/pg/type_map_spec.rb +1 -1
  64. data/spec/pg/type_spec.rb +446 -20
  65. data/spec/pg_spec.rb +2 -2
  66. metadata +63 -72
  67. metadata.gz.sig +0 -0
  68. data/sample/array_insert.rb +0 -20
  69. data/sample/async_api.rb +0 -106
  70. data/sample/async_copyto.rb +0 -39
  71. data/sample/async_mixed.rb +0 -56
  72. data/sample/check_conn.rb +0 -21
  73. data/sample/copyfrom.rb +0 -81
  74. data/sample/copyto.rb +0 -19
  75. data/sample/cursor.rb +0 -21
  76. data/sample/disk_usage_report.rb +0 -186
  77. data/sample/issue-119.rb +0 -94
  78. data/sample/losample.rb +0 -69
  79. data/sample/minimal-testcase.rb +0 -17
  80. data/sample/notify_wait.rb +0 -72
  81. data/sample/pg_statistics.rb +0 -294
  82. data/sample/replication_monitor.rb +0 -231
  83. data/sample/test_binary_values.rb +0 -33
  84. data/sample/wal_shipper.rb +0 -434
  85. data/sample/warehouse_partitions.rb +0 -320
@@ -1,11 +1,13 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
2
 
3
3
  require 'pathname'
4
4
  require 'rspec'
5
5
  require 'shellwords'
6
6
  require 'pg'
7
7
 
8
- TEST_DIRECTORY = Pathname.getwd + "tmp_test_specs"
8
+ DEFAULT_TEST_DIR_STR = File.join(Dir.pwd, "tmp_test_specs")
9
+ TEST_DIR_STR = ENV['RUBY_PG_TEST_DIR'] || DEFAULT_TEST_DIR_STR
10
+ TEST_DIRECTORY = Pathname.new(TEST_DIR_STR)
9
11
 
10
12
  module PG::TestingHelpers
11
13
 
@@ -20,12 +22,11 @@ module PG::TestingHelpers
20
22
 
21
23
  mod.around( :each ) do |example|
22
24
  begin
25
+ @conn.set_default_encoding
23
26
  @conn.exec( 'BEGIN' ) unless example.metadata[:without_transaction]
24
- if PG.respond_to?( :library_version )
25
- desc = example.source_location.join(':')
26
- @conn.exec_params %Q{SET application_name TO '%s'} %
27
- [@conn.escape_string(desc.slice(-60))]
28
- end
27
+ desc = example.source_location.join(':')
28
+ @conn.exec %Q{SET application_name TO '%s'} %
29
+ [@conn.escape_string(desc.slice(-60))]
29
30
  example.run
30
31
  ensure
31
32
  @conn.exec( 'ROLLBACK' ) unless example.metadata[:without_transaction]
@@ -171,18 +172,18 @@ module PG::TestingHelpers
171
172
  datadir = testdir + 'data'
172
173
  pidfile = datadir + 'postmaster.pid'
173
174
  if pidfile.exist? && pid = pidfile.read.chomp.to_i
174
- $stderr.puts "pidfile (%p) exists: %d" % [ pidfile, pid ]
175
+ trace "pidfile (%p) exists: %d" % [ pidfile, pid ]
175
176
  begin
176
177
  Process.kill( 0, pid )
177
178
  rescue Errno::ESRCH
178
- $stderr.puts "No postmaster running for %s" % [ datadir ]
179
+ trace "No postmaster running for %s" % [ datadir ]
179
180
  # Process isn't alive, so don't try to stop it
180
181
  else
181
- $stderr.puts "Stopping lingering database at PID %d" % [ pid ]
182
+ trace "Stopping lingering database at PID %d" % [ pid ]
182
183
  run 'pg_ctl', '-D', datadir.to_s, '-m', 'fast', 'stop'
183
184
  end
184
185
  else
185
- $stderr.puts "No pidfile (%p)" % [ pidfile ]
186
+ trace "No pidfile (%p)" % [ pidfile ]
186
187
  end
187
188
  end
188
189
  end
@@ -193,12 +194,12 @@ module PG::TestingHelpers
193
194
  require 'pg'
194
195
  stop_existing_postmasters()
195
196
 
196
- puts "Setting up test database for #{description}"
197
+ trace "Setting up test database for #{description}"
197
198
  @test_pgdata = TEST_DIRECTORY + 'data'
198
199
  @test_pgdata.mkpath
199
200
 
200
- @port = 54321
201
- ENV['PGPORT'] = @port.to_s
201
+ ENV['PGPORT'] ||= "54321"
202
+ @port = ENV['PGPORT'].to_i
202
203
  ENV['PGHOST'] = 'localhost'
203
204
  @conninfo = "host=localhost port=#{@port} dbname=test"
204
205
 
@@ -208,7 +209,7 @@ module PG::TestingHelpers
208
209
  begin
209
210
  unless (@test_pgdata+"postgresql.conf").exist?
210
211
  FileUtils.rm_rf( @test_pgdata, :verbose => $DEBUG )
211
- $stderr.puts "Running initdb"
212
+ trace "Running initdb"
212
213
  log_and_run @logfile, 'initdb', '-E', 'UTF8', '--no-locale', '-D', @test_pgdata.to_s
213
214
  end
214
215
 
@@ -217,14 +218,14 @@ module PG::TestingHelpers
217
218
  '-D', @test_pgdata.to_s, 'start'
218
219
  sleep 2
219
220
 
220
- $stderr.puts "Creating the test DB"
221
+ trace "Creating the test DB"
221
222
  log_and_run @logfile, 'psql', '-e', '-c', 'DROP DATABASE IF EXISTS test', 'postgres'
222
223
  log_and_run @logfile, 'createdb', '-e', 'test'
223
224
 
224
225
  rescue => err
225
226
  $stderr.puts "%p during test setup: %s" % [ err.class, err.message ]
226
227
  $stderr.puts "See #{@logfile} for details."
227
- $stderr.puts *err.backtrace if $DEBUG
228
+ $stderr.puts err.backtrace if $DEBUG
228
229
  fail
229
230
  end
230
231
 
@@ -238,7 +239,7 @@ module PG::TestingHelpers
238
239
 
239
240
 
240
241
  def teardown_testing_db( conn )
241
- puts "Tearing down test database"
242
+ trace "Tearing down test database"
242
243
 
243
244
  if conn
244
245
  check_for_lingering_connections( conn )
@@ -251,7 +252,7 @@ module PG::TestingHelpers
251
252
 
252
253
  def check_for_lingering_connections( conn )
253
254
  conn.exec( "SELECT * FROM pg_stat_activity" ) do |res|
254
- conns = res.find_all {|row| row['pid'].to_i != conn.backend_pid }
255
+ conns = res.find_all {|row| row['pid'].to_i != conn.backend_pid && ["client backend", nil].include?(row["backend_type"]) }
255
256
  unless conns.empty?
256
257
  puts "Lingering connections remain:"
257
258
  conns.each do |row|
@@ -264,7 +265,7 @@ module PG::TestingHelpers
264
265
 
265
266
  # Retrieve the names of the column types of a given result set.
266
267
  def result_typenames(res)
267
- @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(","),
268
269
  res.nfields.times.map{|i| [res.ftype(i), res.fmod(i)] }.flatten ).
269
270
  values[0]
270
271
  end
@@ -318,6 +319,39 @@ module PG::TestingHelpers
318
319
  return ConnStillUsableMatcher.new
319
320
  end
320
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
+
321
355
  end
322
356
 
323
357
 
@@ -336,20 +370,11 @@ RSpec.configure do |config|
336
370
  else
337
371
  config.filter_run_excluding :windows
338
372
  end
339
- config.filter_run_excluding :socket_io unless
340
- PG::Connection.instance_methods.map( &:to_sym ).include?( :socket_io )
341
-
342
- config.filter_run_excluding :postgresql_90 unless
343
- PG::Connection.instance_methods.map( &:to_sym ).include?( :escape_literal )
344
-
345
- if !PG.respond_to?( :library_version )
346
- config.filter_run_excluding( :postgresql_91, :postgresql_92, :postgresql_93, :postgresql_94 )
347
- elsif PG.library_version < 90200
348
- config.filter_run_excluding( :postgresql_92, :postgresql_93, :postgresql_94 )
349
- elsif PG.library_version < 90300
350
- config.filter_run_excluding( :postgresql_93, :postgresql_94 )
351
- elsif PG.library_version < 90400
352
- config.filter_run_excluding( :postgresql_94 )
353
- end
354
- end
355
373
 
374
+ config.filter_run_excluding( :postgresql_93 ) if PG.library_version < 90300
375
+ config.filter_run_excluding( :postgresql_94 ) if PG.library_version < 90400
376
+ config.filter_run_excluding( :postgresql_95 ) if PG.library_version < 90500
377
+ config.filter_run_excluding( :postgresql_96 ) if PG.library_version < 90600
378
+ config.filter_run_excluding( :postgresql_10 ) if PG.library_version < 100000
379
+ config.filter_run_excluding( :postgresql_12 ) if PG.library_version < 120000
380
+ end
@@ -1,9 +1,25 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
5
5
 
6
6
  require 'pg'
7
+ require 'time'
8
+
9
+ def restore_type(types)
10
+ [0, 1].each do |format|
11
+ [types].flatten.each do |type|
12
+ PG::BasicTypeRegistry.alias_type(format, "restore_#{type}", type)
13
+ end
14
+ end
15
+ yield
16
+ ensure
17
+ [0, 1].each do |format|
18
+ [types].flatten.each do |type|
19
+ PG::BasicTypeRegistry.alias_type(format, type, "restore_#{type}")
20
+ end
21
+ end
22
+ end
7
23
 
8
24
  describe 'Basic type mapping' do
9
25
 
@@ -16,8 +32,8 @@ describe 'Basic type mapping' do
16
32
  # Encoding Examples
17
33
  #
18
34
 
19
- it "should do basic param encoding", :ruby_19 do
20
- res = @conn.exec_params( "SELECT $1::int8,$2::float,$3,$4::TEXT",
35
+ it "should do basic param encoding" do
36
+ res = @conn.exec_params( "SELECT $1::int8, $2::float, $3, $4::TEXT",
21
37
  [1, 2.1, true, "b"], nil, basic_type_mapping )
22
38
 
23
39
  expect( res.values ).to eq( [
@@ -27,21 +43,168 @@ describe 'Basic type mapping' do
27
43
  expect( result_typenames(res) ).to eq( ['bigint', 'double precision', 'boolean', 'text'] )
28
44
  end
29
45
 
30
- it "should do array param encoding" do
31
- res = @conn.exec_params( "SELECT $1,$2,$3,$4", [
32
- [1, 2, 3], [[1, 2], [3, nil]],
33
- [1.11, 2.21],
34
- ['/,"'.gsub("/", "\\"), nil, 'abcäöü'],
46
+ it "should do basic Time encoding" do
47
+ res = @conn.exec_params( "SELECT $1 AT TIME ZONE '-02'",
48
+ [Time.new(2019, 12, 8, 20, 38, 12.123, "-01:00")], nil, basic_type_mapping )
49
+
50
+ expect( res.values ).to eq( [[ "2019-12-08 23:38:12.123" ]] )
51
+ end
52
+
53
+ it "should do basic param encoding of various float values" do
54
+ res = @conn.exec_params( "SELECT $1::float, $2::float, $3::float, $4::float, $5::float, $6::float, $7::float, $8::float, $9::float, $10::float, $11::float, $12::float",
55
+ [0, 7, 9, 0.1, 0.9, -0.11, 10.11,
56
+ 9876543210987654321e-400,
57
+ 9876543210987654321e400,
58
+ -1.234567890123456789e-280,
59
+ -1.234567890123456789e280,
60
+ 9876543210987654321e280
61
+ ], nil, basic_type_mapping )
62
+
63
+ expect( res.values[0][0, 9] ).to eq(
64
+ [ "0", "7", "9", "0.1", "0.9", "-0.11", "10.11", "0", "Infinity" ]
65
+ )
66
+
67
+ expect( res.values[0][9] ).to match( /^-1\.2345678901234\d*e\-280$/ )
68
+ expect( res.values[0][10] ).to match( /^-1\.2345678901234\d*e\+280$/ )
69
+ expect( res.values[0][11] ).to match( /^9\.8765432109876\d*e\+298$/ )
70
+
71
+ expect( result_typenames(res) ).to eq( ['double precision'] * 12 )
72
+ end
73
+
74
+ it "should do default array-as-array param encoding" do
75
+ expect( basic_type_mapping.encode_array_as).to eq(:array)
76
+ res = @conn.exec_params( "SELECT $1,$2,$3,$4,$5,$6", [
77
+ [1, 2, 3], # Integer -> bigint[]
78
+ [[1, 2], [3, nil]], # Integer two dimensions -> bigint[]
79
+ [1.11, 2.21], # Float -> double precision[]
80
+ ['/,"'.gsub("/", "\\"), nil, 'abcäöü'], # String -> text[]
81
+ [BigDecimal("123.45")], # BigDecimal -> numeric[]
82
+ [IPAddr.new('1234::5678')], # IPAddr -> inet[]
35
83
  ], nil, basic_type_mapping )
36
84
 
37
85
  expect( res.values ).to eq( [[
38
- '{1,2,3}', '{{1,2},{3,NULL}}',
86
+ '{1,2,3}',
87
+ '{{1,2},{3,NULL}}',
39
88
  '{1.11,2.21}',
40
89
  '{"//,/"",NULL,abcäöü}'.gsub("/", "\\"),
90
+ '{123.45}',
91
+ '{1234::5678}',
92
+ ]] )
93
+
94
+ expect( result_typenames(res) ).to eq( ['bigint[]', 'bigint[]', 'double precision[]', 'text[]', 'numeric[]', 'inet[]'] )
95
+ end
96
+
97
+ it "should do default array-as-array param encoding with Time objects" do
98
+ res = @conn.exec_params( "SELECT $1", [
99
+ [Time.new(2019, 12, 8, 20, 38, 12.123, "-01:00")], # Time -> timestamptz[]
100
+ ], nil, basic_type_mapping )
101
+
102
+ expect( res.values[0][0] ).to match( /\{\"2019-12-08 \d\d:38:12.123[+-]\d\d\"\}/ )
103
+ expect( result_typenames(res) ).to eq( ['timestamp with time zone[]'] )
104
+ end
105
+
106
+ it "should do array-as-json encoding" do
107
+ basic_type_mapping.encode_array_as = :json
108
+ expect( basic_type_mapping.encode_array_as).to eq(:json)
109
+
110
+ res = @conn.exec_params( "SELECT $1::JSON, $2::JSON", [
111
+ [1, {a: 5}, true, ["a", 2], [3.4, nil]],
112
+ ['/,"'.gsub("/", "\\"), nil, 'abcäöü'],
113
+ ], nil, basic_type_mapping )
114
+
115
+ expect( res.values ).to eq( [[
116
+ '[1,{"a":5},true,["a",2],[3.4,null]]',
117
+ '["//,/"",null,"abcäöü"]'.gsub("/", "\\"),
118
+ ]] )
119
+
120
+ expect( result_typenames(res) ).to eq( ['json', 'json'] )
121
+ end
122
+
123
+ it "should do hash-as-json encoding" do
124
+ res = @conn.exec_params( "SELECT $1::JSON, $2::JSON", [
125
+ {a: 5, b: ["a", 2], c: nil},
126
+ {qu: '/,"'.gsub("/", "\\"), ni: nil, uml: 'abcäöü'},
127
+ ], nil, basic_type_mapping )
128
+
129
+ expect( res.values ).to eq( [[
130
+ '{"a":5,"b":["a",2],"c":null}',
131
+ '{"qu":"//,/"","ni":null,"uml":"abcäöü"}'.gsub("/", "\\"),
132
+ ]] )
133
+
134
+ expect( result_typenames(res) ).to eq( ['json', 'json'] )
135
+ end
136
+
137
+ describe "Record encoding" do
138
+ before :all do
139
+ @conn.exec("CREATE TYPE test_record1 AS (i int, d float, t text)")
140
+ @conn.exec("CREATE TYPE test_record2 AS (i int, r test_record1)")
141
+ end
142
+
143
+ after :all do
144
+ @conn.exec("DROP TYPE IF EXISTS test_record2 CASCADE")
145
+ @conn.exec("DROP TYPE IF EXISTS test_record1 CASCADE")
146
+ end
147
+
148
+ it "should do array-as-record encoding" do
149
+ basic_type_mapping.encode_array_as = :record
150
+ expect( basic_type_mapping.encode_array_as).to eq(:record)
151
+
152
+ res = @conn.exec_params( "SELECT $1::test_record1, $2::test_record2, $3::text", [
153
+ [5, 3.4, "txt"],
154
+ [1, [2, 4.5, "bcd"]],
155
+ [4, 5, 6],
156
+ ], nil, basic_type_mapping )
157
+
158
+ expect( res.values ).to eq( [[
159
+ '(5,3.4,txt)',
160
+ '(1,"(2,4.5,bcd)")',
161
+ '("4","5","6")',
162
+ ]] )
163
+
164
+ expect( result_typenames(res) ).to eq( ['test_record1', 'test_record2', 'text'] )
165
+ end
166
+ end
167
+
168
+ it "should do bigdecimal param encoding" do
169
+ large = ('123456790'*10) << '.' << ('012345679')
170
+ res = @conn.exec_params( "SELECT $1::numeric,$2::numeric",
171
+ [BigDecimal('1'), BigDecimal(large)], nil, basic_type_mapping )
172
+
173
+ expect( res.values ).to eq( [
174
+ [ "1.0", large ],
175
+ ] )
176
+
177
+ expect( result_typenames(res) ).to eq( ['numeric', 'numeric'] )
178
+ end
179
+
180
+ it "should do IPAddr param encoding" do
181
+ res = @conn.exec_params( "SELECT $1::inet,$2::inet,$3::cidr,$4::cidr",
182
+ ['1.2.3.4', IPAddr.new('1234::5678'), '1.2.3.4', IPAddr.new('1234:5678::/32')], nil, basic_type_mapping )
183
+
184
+ expect( res.values ).to eq( [
185
+ [ '1.2.3.4', '1234::5678', '1.2.3.4/32', '1234:5678::/32'],
186
+ ] )
187
+
188
+ expect( result_typenames(res) ).to eq( ['inet', 'inet', 'cidr', 'cidr'] )
189
+ end
190
+
191
+ it "should do array of string encoding on unknown classes" do
192
+ iv = Class.new do
193
+ def to_s
194
+ "abc"
195
+ end
196
+ end.new
197
+ res = @conn.exec_params( "SELECT $1", [
198
+ [iv, iv], # Unknown -> text[]
199
+ ], nil, basic_type_mapping )
200
+
201
+ expect( res.values ).to eq( [[
202
+ '{abc,abc}',
41
203
  ]] )
42
204
 
43
- expect( result_typenames(res) ).to eq( ['bigint[]', 'bigint[]', 'double precision[]', 'text[]'] )
205
+ expect( result_typenames(res) ).to eq( ['text[]'] )
44
206
  end
207
+
45
208
  end
46
209
 
47
210
 
@@ -55,7 +218,7 @@ describe 'Basic type mapping' do
55
218
  # Decoding Examples
56
219
  #
57
220
 
58
- it "should do OID based type conversions", :ruby_19 do
221
+ it "should do OID based type conversions" do
59
222
  res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, TRUE, '2013-06-30'::DATE, generate_series(4,5)" )
60
223
  expect( res.map_types!(basic_type_mapping).values ).to eq( [
61
224
  [ 1, 'a', 2.0, true, Date.new(2013,6,30), 4 ],
@@ -78,14 +241,14 @@ describe 'Basic type mapping' do
78
241
 
79
242
  it "should do boolean type conversions" do
80
243
  [1, 0].each do |format|
81
- res = @conn.exec( "SELECT true::BOOLEAN, false::BOOLEAN, NULL::BOOLEAN", [], format )
244
+ res = @conn.exec_params( "SELECT true::BOOLEAN, false::BOOLEAN, NULL::BOOLEAN", [], format )
82
245
  expect( res.values ).to eq( [[true, false, nil]] )
83
246
  end
84
247
  end
85
248
 
86
249
  it "should do binary type conversions" do
87
250
  [1, 0].each do |format|
88
- res = @conn.exec( "SELECT E'\\\\000\\\\377'::BYTEA", [], format )
251
+ res = @conn.exec_params( "SELECT E'\\\\000\\\\377'::BYTEA", [], format )
89
252
  expect( res.values ).to eq( [[["00ff"].pack("H*")]] )
90
253
  expect( res.values[0][0].encoding ).to eq( Encoding::ASCII_8BIT ) if Object.const_defined? :Encoding
91
254
  end
@@ -93,7 +256,7 @@ describe 'Basic type mapping' do
93
256
 
94
257
  it "should do integer type conversions" do
95
258
  [1, 0].each do |format|
96
- res = @conn.exec( "SELECT -8999::INT2, -899999999::INT4, -8999999999999999999::INT8", [], format )
259
+ res = @conn.exec_params( "SELECT -8999::INT2, -899999999::INT4, -8999999999999999999::INT8", [], format )
97
260
  expect( res.values ).to eq( [[-8999, -899999999, -8999999999999999999]] )
98
261
  end
99
262
  end
@@ -101,7 +264,7 @@ describe 'Basic type mapping' do
101
264
  it "should do string type conversions" do
102
265
  @conn.internal_encoding = 'utf-8' if Object.const_defined? :Encoding
103
266
  [1, 0].each do |format|
104
- res = @conn.exec( "SELECT 'abcäöü'::TEXT", [], format )
267
+ res = @conn.exec_params( "SELECT 'abcäöü'::TEXT", [], format )
105
268
  expect( res.values ).to eq( [['abcäöü']] )
106
269
  expect( res.values[0][0].encoding ).to eq( Encoding::UTF_8 ) if Object.const_defined? :Encoding
107
270
  end
@@ -109,7 +272,7 @@ describe 'Basic type mapping' do
109
272
 
110
273
  it "should do float type conversions" do
111
274
  [1, 0].each do |format|
112
- res = @conn.exec( "SELECT -8.999e3::FLOAT4,
275
+ res = @conn.exec_params( "SELECT -8.999e3::FLOAT4,
113
276
  8.999e10::FLOAT4,
114
277
  -8999999999e-99::FLOAT8,
115
278
  NULL::FLOAT4,
@@ -127,35 +290,107 @@ describe 'Basic type mapping' do
127
290
  end
128
291
  end
129
292
 
130
- it "should do datetime without time zone type conversions" do
131
- [0].each do |format|
132
- res = @conn.exec( "SELECT CAST('2013-12-31 23:58:59+02' AS TIMESTAMP WITHOUT TIME ZONE),
133
- CAST('1913-12-31 23:58:59.123-03' AS TIMESTAMP WITHOUT TIME ZONE),
134
- CAST('infinity' AS TIMESTAMP WITHOUT TIME ZONE),
135
- CAST('-infinity' AS TIMESTAMP WITHOUT TIME ZONE)", [], format )
136
- expect( res.getvalue(0,0) ).to eq( Time.new(2013, 12, 31, 23, 58, 59) )
137
- expect( res.getvalue(0,1) ).to be_within(1e-3).of(Time.new(1913, 12, 31, 23, 58, 59.123))
138
- expect( res.getvalue(0,2) ).to eq( 'infinity' )
139
- expect( res.getvalue(0,3) ).to eq( '-infinity' )
293
+ it "should do text datetime without time zone type conversions" do
294
+ # for backward compat text timestamps without time zone are treated as local times
295
+ res = @conn.exec_params( "SELECT CAST('2013-12-31 23:58:59+02' AS TIMESTAMP WITHOUT TIME ZONE),
296
+ CAST('1913-12-31 23:58:59.1231-03' AS TIMESTAMP WITHOUT TIME ZONE),
297
+ CAST('4714-11-24 23:58:59.1231-03 BC' AS TIMESTAMP WITHOUT TIME ZONE),
298
+ CAST('294276-12-31 23:58:59.1231-03' AS TIMESTAMP WITHOUT TIME ZONE),
299
+ CAST('infinity' AS TIMESTAMP WITHOUT TIME ZONE),
300
+ CAST('-infinity' AS TIMESTAMP WITHOUT TIME ZONE)", [], 0 )
301
+ expect( res.getvalue(0,0) ).to eq( Time.new(2013, 12, 31, 23, 58, 59) )
302
+ expect( res.getvalue(0,1).iso8601(3) ).to eq( Time.new(1913, 12, 31, 23, 58, 59.1231).iso8601(3) )
303
+ expect( res.getvalue(0,2).iso8601(3) ).to eq( Time.new(-4713, 11, 24, 23, 58, 59.1231).iso8601(3) )
304
+ expect( res.getvalue(0,3).iso8601(3) ).to eq( Time.new(294276, 12, 31, 23, 58, 59.1231).iso8601(3) )
305
+ expect( res.getvalue(0,4) ).to eq( 'infinity' )
306
+ expect( res.getvalue(0,5) ).to eq( '-infinity' )
307
+ end
308
+
309
+ [1, 0].each do |format|
310
+ it "should convert format #{format} timestamps per TimestampUtc" do
311
+ restore_type("timestamp") do
312
+ PG::BasicTypeRegistry.register_type 0, 'timestamp', nil, PG::TextDecoder::TimestampUtc
313
+ @conn.type_map_for_results = PG::BasicTypeMapForResults.new(@conn)
314
+ res = @conn.exec_params( "SELECT CAST('2013-07-31 23:58:59+02' AS TIMESTAMP WITHOUT TIME ZONE),
315
+ CAST('1913-12-31 23:58:59.1231-03' AS TIMESTAMP WITHOUT TIME ZONE),
316
+ CAST('4714-11-24 23:58:59.1231-03 BC' AS TIMESTAMP WITHOUT TIME ZONE),
317
+ CAST('294276-12-31 23:58:59.1231-03' AS TIMESTAMP WITHOUT TIME ZONE),
318
+ CAST('infinity' AS TIMESTAMP WITHOUT TIME ZONE),
319
+ CAST('-infinity' AS TIMESTAMP WITHOUT TIME ZONE)", [], format )
320
+ expect( res.getvalue(0,0).iso8601(3) ).to eq( Time.utc(2013, 7, 31, 23, 58, 59).iso8601(3) )
321
+ expect( res.getvalue(0,1).iso8601(3) ).to eq( Time.utc(1913, 12, 31, 23, 58, 59.1231).iso8601(3) )
322
+ expect( res.getvalue(0,2).iso8601(3) ).to eq( Time.utc(-4713, 11, 24, 23, 58, 59.1231).iso8601(3) )
323
+ expect( res.getvalue(0,3).iso8601(3) ).to eq( Time.utc(294276, 12, 31, 23, 58, 59.1231).iso8601(3) )
324
+ expect( res.getvalue(0,4) ).to eq( 'infinity' )
325
+ expect( res.getvalue(0,5) ).to eq( '-infinity' )
326
+ end
140
327
  end
141
328
  end
142
329
 
143
- it "should do datetime with time zone type conversions" do
144
- [0].each do |format|
145
- res = @conn.exec( "SELECT CAST('2013-12-31 23:58:59+02' AS TIMESTAMP WITH TIME ZONE),
146
- CAST('1913-12-31 23:58:59.123-03' AS TIMESTAMP WITH TIME ZONE),
330
+ [1, 0].each do |format|
331
+ it "should convert format #{format} timestamps per TimestampUtcToLocal" do
332
+ restore_type("timestamp") do
333
+ PG::BasicTypeRegistry.register_type 0, 'timestamp', nil, PG::TextDecoder::TimestampUtcToLocal
334
+ PG::BasicTypeRegistry.register_type 1, 'timestamp', nil, PG::BinaryDecoder::TimestampUtcToLocal
335
+ @conn.type_map_for_results = PG::BasicTypeMapForResults.new(@conn)
336
+ res = @conn.exec_params( "SELECT CAST('2013-07-31 23:58:59+02' AS TIMESTAMP WITHOUT TIME ZONE),
337
+ CAST('1913-12-31 23:58:59.1231-03' AS TIMESTAMP WITHOUT TIME ZONE),
338
+ CAST('4714-11-24 23:58:59.1231-03 BC' AS TIMESTAMP WITHOUT TIME ZONE),
339
+ CAST('294276-12-31 23:58:59.1231-03' AS TIMESTAMP WITHOUT TIME ZONE),
340
+ CAST('infinity' AS TIMESTAMP WITHOUT TIME ZONE),
341
+ CAST('-infinity' AS TIMESTAMP WITHOUT TIME ZONE)", [], format )
342
+ expect( res.getvalue(0,0).iso8601(3) ).to eq( Time.utc(2013, 7, 31, 23, 58, 59).getlocal.iso8601(3) )
343
+ expect( res.getvalue(0,1).iso8601(3) ).to eq( Time.utc(1913, 12, 31, 23, 58, 59.1231).getlocal.iso8601(3) )
344
+ expect( res.getvalue(0,2).iso8601(3) ).to eq( Time.utc(-4713, 11, 24, 23, 58, 59.1231).getlocal.iso8601(3) )
345
+ expect( res.getvalue(0,3).iso8601(3) ).to eq( Time.utc(294276, 12, 31, 23, 58, 59.1231).getlocal.iso8601(3) )
346
+ expect( res.getvalue(0,4) ).to eq( 'infinity' )
347
+ expect( res.getvalue(0,5) ).to eq( '-infinity' )
348
+ end
349
+ end
350
+ end
351
+
352
+ [1, 0].each do |format|
353
+ it "should convert format #{format} timestamps per TimestampLocal" do
354
+ restore_type("timestamp") do
355
+ PG::BasicTypeRegistry.register_type 0, 'timestamp', nil, PG::TextDecoder::TimestampLocal
356
+ PG::BasicTypeRegistry.register_type 1, 'timestamp', nil, PG::BinaryDecoder::TimestampLocal
357
+ @conn.type_map_for_results = PG::BasicTypeMapForResults.new(@conn)
358
+ res = @conn.exec_params( "SELECT CAST('2013-07-31 23:58:59' AS TIMESTAMP WITHOUT TIME ZONE),
359
+ CAST('1913-12-31 23:58:59.1231' AS TIMESTAMP WITHOUT TIME ZONE),
360
+ CAST('4714-11-24 23:58:59.1231-03 BC' AS TIMESTAMP WITHOUT TIME ZONE),
361
+ CAST('294276-12-31 23:58:59.1231+03' AS TIMESTAMP WITHOUT TIME ZONE),
362
+ CAST('infinity' AS TIMESTAMP WITHOUT TIME ZONE),
363
+ CAST('-infinity' AS TIMESTAMP WITHOUT TIME ZONE)", [], format )
364
+ expect( res.getvalue(0,0).iso8601(3) ).to eq( Time.new(2013, 7, 31, 23, 58, 59).iso8601(3) )
365
+ expect( res.getvalue(0,1).iso8601(3) ).to eq( Time.new(1913, 12, 31, 23, 58, 59.1231).iso8601(3) )
366
+ expect( res.getvalue(0,2).iso8601(3) ).to eq( Time.new(-4713, 11, 24, 23, 58, 59.1231).iso8601(3) )
367
+ expect( res.getvalue(0,3).iso8601(3) ).to eq( Time.new(294276, 12, 31, 23, 58, 59.1231).iso8601(3) )
368
+ expect( res.getvalue(0,4) ).to eq( 'infinity' )
369
+ expect( res.getvalue(0,5) ).to eq( '-infinity' )
370
+ end
371
+ end
372
+ end
373
+
374
+ [0, 1].each do |format|
375
+ it "should convert format #{format} timestamps with time zone" do
376
+ res = @conn.exec_params( "SELECT CAST('2013-12-31 23:58:59+02' AS TIMESTAMP WITH TIME ZONE),
377
+ CAST('1913-12-31 23:58:59.1231-03' AS TIMESTAMP WITH TIME ZONE),
378
+ CAST('4714-11-24 23:58:59.1231-03 BC' AS TIMESTAMP WITH TIME ZONE),
379
+ CAST('294276-12-31 23:58:59.1231+03' AS TIMESTAMP WITH TIME ZONE),
147
380
  CAST('infinity' AS TIMESTAMP WITH TIME ZONE),
148
381
  CAST('-infinity' AS TIMESTAMP WITH TIME ZONE)", [], format )
149
- expect( res.getvalue(0,0) ).to eq( Time.new(2013, 12, 31, 23, 58, 59, "+02:00") )
150
- expect( res.getvalue(0,1) ).to be_within(1e-3).of(Time.new(1913, 12, 31, 23, 58, 59.123, "-03:00"))
151
- expect( res.getvalue(0,2) ).to eq( 'infinity' )
152
- expect( res.getvalue(0,3) ).to eq( '-infinity' )
382
+ expect( res.getvalue(0,0) ).to be_within(1e-3).of( Time.new(2013, 12, 31, 23, 58, 59, "+02:00").getlocal )
383
+ expect( res.getvalue(0,1) ).to be_within(1e-3).of( Time.new(1913, 12, 31, 23, 58, 59.1231, "-03:00").getlocal )
384
+ expect( res.getvalue(0,2) ).to be_within(1e-3).of( Time.new(-4713, 11, 24, 23, 58, 59.1231, "-03:00").getlocal )
385
+ expect( res.getvalue(0,3) ).to be_within(1e-3).of( Time.new(294276, 12, 31, 23, 58, 59.1231, "+03:00").getlocal )
386
+ expect( res.getvalue(0,4) ).to eq( 'infinity' )
387
+ expect( res.getvalue(0,5) ).to eq( '-infinity' )
153
388
  end
154
389
  end
155
390
 
156
391
  it "should do date type conversions" do
157
392
  [0].each do |format|
158
- res = @conn.exec( "SELECT CAST('2113-12-31' AS DATE),
393
+ res = @conn.exec_params( "SELECT CAST('2113-12-31' AS DATE),
159
394
  CAST('1913-12-31' AS DATE),
160
395
  CAST('infinity' AS DATE),
161
396
  CAST('-infinity' AS DATE)", [], format )
@@ -166,9 +401,51 @@ describe 'Basic type mapping' do
166
401
  end
167
402
  end
168
403
 
404
+ it "should do numeric type conversions" do
405
+ [0].each do |format|
406
+ small = '123456790123.12'
407
+ large = ('123456790'*10) << '.' << ('012345679')
408
+ numerics = [
409
+ '1',
410
+ '1.0',
411
+ '1.2',
412
+ small,
413
+ large,
414
+ ]
415
+ sql_numerics = numerics.map { |v| "CAST(#{v} AS numeric)" }
416
+ res = @conn.exec_params( "SELECT #{sql_numerics.join(',')}", [], format )
417
+ expect( res.getvalue(0,0) ).to eq( BigDecimal('1') )
418
+ expect( res.getvalue(0,1) ).to eq( BigDecimal('1') )
419
+ expect( res.getvalue(0,2) ).to eq( BigDecimal('1.2') )
420
+ expect( res.getvalue(0,3) ).to eq( BigDecimal(small) )
421
+ expect( res.getvalue(0,4) ).to eq( BigDecimal(large) )
422
+ end
423
+ end
424
+
425
+ it "should do JSON conversions", :postgresql_94 do
426
+ [0].each do |format|
427
+ ['JSON', 'JSONB'].each do |type|
428
+ res = @conn.exec_params( "SELECT CAST('123' AS #{type}),
429
+ CAST('12.3' AS #{type}),
430
+ CAST('true' AS #{type}),
431
+ CAST('false' AS #{type}),
432
+ CAST('null' AS #{type}),
433
+ CAST('[1, \"a\", null]' AS #{type}),
434
+ CAST('{\"b\" : [2,3]}' AS #{type})", [], format )
435
+ expect( res.getvalue(0,0) ).to eq( 123 )
436
+ expect( res.getvalue(0,1) ).to be_within(0.1).of( 12.3 )
437
+ expect( res.getvalue(0,2) ).to eq( true )
438
+ expect( res.getvalue(0,3) ).to eq( false )
439
+ expect( res.getvalue(0,4) ).to eq( nil )
440
+ expect( res.getvalue(0,5) ).to eq( [1, "a", nil] )
441
+ expect( res.getvalue(0,6) ).to eq( {"b" => [2, 3]} )
442
+ end
443
+ end
444
+ end
445
+
169
446
  it "should do array type conversions" do
170
447
  [0].each do |format|
171
- res = @conn.exec( "SELECT CAST('{1,2,3}' AS INT2[]), CAST('{{1,2},{3,4}}' AS INT2[][]),
448
+ res = @conn.exec_params( "SELECT CAST('{1,2,3}' AS INT2[]), CAST('{{1,2},{3,4}}' AS INT2[][]),
172
449
  CAST('{1,2,3}' AS INT4[]),
173
450
  CAST('{1,2,3}' AS INT8[]),
174
451
  CAST('{1,2,3}' AS TEXT[]),
@@ -186,6 +463,75 @@ describe 'Basic type mapping' do
186
463
  expect( res.getvalue(0,7) ).to eq( [1.0,2.0,3.0] )
187
464
  end
188
465
  end
466
+
467
+ it "should do inet type conversions" do
468
+ [0].each do |format|
469
+ vals = [
470
+ '1.2.3.4',
471
+ '0.0.0.0/0',
472
+ '1.0.0.0/8',
473
+ '1.2.0.0/16',
474
+ '1.2.3.0/24',
475
+ '1.2.3.4/24',
476
+ '1.2.3.4/32',
477
+ '1.2.3.128/25',
478
+ '1234:3456:5678:789a:9abc:bced:edf0:f012',
479
+ '::/0',
480
+ '1234:3456::/32',
481
+ '1234:3456:5678:789a::/64',
482
+ '1234:3456:5678:789a:9abc:bced::/96',
483
+ '1234:3456:5678:789a:9abc:bced:edf0:f012/128',
484
+ '1234:3456:5678:789a:9abc:bced:edf0:f012/0',
485
+ '1234:3456:5678:789a:9abc:bced:edf0:f012/32',
486
+ '1234:3456:5678:789a:9abc:bced:edf0:f012/64',
487
+ '1234:3456:5678:789a:9abc:bced:edf0:f012/96',
488
+ ]
489
+ sql_vals = vals.map{|v| "CAST('#{v}' AS inet)"}
490
+ res = @conn.exec_params(("SELECT " + sql_vals.join(', ')), [], format )
491
+ vals.each_with_index do |v, i|
492
+ expect( res.getvalue(0,i) ).to eq( IPAddr.new(v) )
493
+ end
494
+ end
495
+ end
496
+
497
+ it "should do cidr type conversions" do
498
+ [0].each do |format|
499
+ vals = [
500
+ '0.0.0.0/0',
501
+ '1.0.0.0/8',
502
+ '1.2.0.0/16',
503
+ '1.2.3.0/24',
504
+ '1.2.3.4/32',
505
+ '1.2.3.128/25',
506
+ '::/0',
507
+ '1234:3456::/32',
508
+ '1234:3456:5678:789a::/64',
509
+ '1234:3456:5678:789a:9abc:bced::/96',
510
+ '1234:3456:5678:789a:9abc:bced:edf0:f012/128',
511
+ ]
512
+ sql_vals = vals.map { |v| "CAST('#{v}' AS cidr)" }
513
+ res = @conn.exec_params(("SELECT " + sql_vals.join(', ')), [], format )
514
+ vals.each_with_index do |v, i|
515
+ val = res.getvalue(0,i)
516
+ ip, prefix = v.split('/', 2)
517
+ expect( val.to_s ).to eq( ip )
518
+ if val.respond_to?(:prefix)
519
+ val_prefix = val.prefix
520
+ else
521
+ default_prefix = (val.family == Socket::AF_INET ? 32 : 128)
522
+ range = val.to_range
523
+ val_prefix = default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i
524
+ end
525
+ if v.include?('/')
526
+ expect( val_prefix ).to eq( prefix.to_i )
527
+ elsif v.include?('.')
528
+ expect( val_prefix ).to eq( 32 )
529
+ else
530
+ expect( val_prefix ).to eq( 128 )
531
+ end
532
+ end
533
+ end
534
+ end
189
535
  end
190
536
 
191
537
  context "with usage of result oids for copy decoder selection" do
@@ -228,6 +574,39 @@ describe 'Basic type mapping' do
228
574
  res = @conn.exec( "SELECT * FROM copytable" )
229
575
  expect( res.values ).to eq( [['a', '123', '{5,4,3}'], ['b', '234', '{2,3}']] )
230
576
  end
577
+
578
+ it "can do JSON conversions", :postgresql_94 do
579
+ ['JSON', 'JSONB'].each do |type|
580
+ sql = "SELECT CAST('123' AS #{type}),
581
+ CAST('12.3' AS #{type}),
582
+ CAST('true' AS #{type}),
583
+ CAST('false' AS #{type}),
584
+ CAST('null' AS #{type}),
585
+ CAST('[1, \"a\", null]' AS #{type}),
586
+ CAST('{\"b\" : [2,3]}' AS #{type})"
587
+
588
+ tm = basic_type_mapping.build_column_map( @conn.exec( sql ) )
589
+ expect( tm.coders.map(&:name) ).to eq( [type.downcase] * 7 )
590
+
591
+ res = @conn.exec_params( "SELECT $1, $2, $3, $4, $5, $6, $7",
592
+ [ 123,
593
+ 12.3,
594
+ true,
595
+ false,
596
+ nil,
597
+ [1, "a", nil],
598
+ {"b" => [2, 3]},
599
+ ], 0, tm )
600
+
601
+ expect( res.getvalue(0,0) ).to eq( "123" )
602
+ expect( res.getvalue(0,1) ).to eq( "12.3" )
603
+ expect( res.getvalue(0,2) ).to eq( "true" )
604
+ expect( res.getvalue(0,3) ).to eq( "false" )
605
+ expect( res.getvalue(0,4) ).to eq( nil )
606
+ expect( res.getvalue(0,5).gsub(" ","") ).to eq( "[1,\"a\",null]" )
607
+ expect( res.getvalue(0,6).gsub(" ","") ).to eq( "{\"b\":[2,3]}" )
608
+ end
609
+ end
231
610
  end
232
611
 
233
612
  context "with usage of result oids for copy encoder selection" do