pg 0.18.4 → 1.2.3

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