pg 1.1.4 → 1.2.0
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +0 -6595
- data/History.rdoc +63 -0
- data/Manifest.txt +3 -2
- data/README-Windows.rdoc +4 -4
- data/README.ja.rdoc +1 -2
- data/README.rdoc +43 -8
- data/Rakefile +3 -3
- data/Rakefile.cross +6 -3
- data/ext/errorcodes.def +64 -0
- data/ext/errorcodes.txt +18 -2
- data/ext/extconf.rb +6 -6
- data/ext/pg.c +132 -95
- data/ext/pg.h +20 -18
- data/ext/pg_binary_decoder.c +9 -9
- data/ext/pg_binary_encoder.c +13 -12
- data/ext/pg_coder.c +5 -5
- data/ext/pg_connection.c +388 -298
- data/ext/pg_copy_coder.c +5 -3
- data/ext/pg_record_coder.c +490 -0
- data/ext/pg_result.c +269 -123
- data/ext/pg_text_decoder.c +14 -8
- data/ext/pg_text_encoder.c +180 -48
- data/ext/pg_tuple.c +14 -6
- data/ext/pg_type_map.c +1 -1
- data/ext/pg_type_map_all_strings.c +4 -4
- data/ext/pg_type_map_by_class.c +4 -3
- data/ext/pg_type_map_by_column.c +7 -6
- data/ext/pg_type_map_by_mri_type.c +1 -1
- data/ext/pg_type_map_by_oid.c +3 -2
- data/ext/pg_type_map_in_ruby.c +1 -1
- data/ext/{util.c → pg_util.c} +5 -5
- data/ext/{util.h → pg_util.h} +0 -0
- data/lib/pg.rb +2 -3
- data/lib/pg/basic_type_mapping.rb +79 -16
- data/lib/pg/binary_decoder.rb +1 -0
- data/lib/pg/coder.rb +22 -1
- data/lib/pg/connection.rb +2 -2
- data/lib/pg/constants.rb +1 -0
- data/lib/pg/exceptions.rb +1 -0
- data/lib/pg/result.rb +13 -1
- data/lib/pg/text_decoder.rb +2 -3
- data/lib/pg/text_encoder.rb +8 -18
- data/lib/pg/type_map_by_column.rb +2 -1
- data/spec/helpers.rb +10 -8
- data/spec/pg/basic_type_mapping_spec.rb +150 -13
- data/spec/pg/connection_spec.rb +89 -50
- data/spec/pg/result_spec.rb +193 -3
- data/spec/pg/tuple_spec.rb +55 -2
- data/spec/pg/type_map_by_column_spec.rb +5 -1
- data/spec/pg/type_spec.rb +180 -6
- metadata +17 -15
- metadata.gz.sig +0 -0
data/lib/pg/constants.rb
CHANGED
data/lib/pg/exceptions.rb
CHANGED
data/lib/pg/result.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
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
|
-
#
|
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
|
data/lib/pg/text_decoder.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
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 =~
|
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
|
data/lib/pg/text_encoder.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'json'
|
4
5
|
require 'ipaddr'
|
@@ -6,36 +7,26 @@ require 'ipaddr'
|
|
6
7
|
module PG
|
7
8
|
module TextEncoder
|
8
9
|
class Date < SimpleEncoder
|
9
|
-
STRFTIME_ISO_DATE = "%Y-%m-%d".freeze
|
10
10
|
def encode(value)
|
11
|
-
value.respond_to?(:strftime) ? value.strftime(
|
11
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : value
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
15
|
class TimestampWithoutTimeZone < SimpleEncoder
|
16
|
-
STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N".freeze
|
17
16
|
def encode(value)
|
18
|
-
value.respond_to?(:strftime) ? value.strftime(
|
17
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S.%N") : value
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
22
21
|
class TimestampUtc < SimpleEncoder
|
23
|
-
STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE_UTC = "%Y-%m-%d %H:%M:%S.%N".freeze
|
24
22
|
def encode(value)
|
25
|
-
value.respond_to?(:utc) ? value.utc.strftime(
|
23
|
+
value.respond_to?(:utc) ? value.utc.strftime("%Y-%m-%d %H:%M:%S.%N") : value
|
26
24
|
end
|
27
25
|
end
|
28
26
|
|
29
27
|
class TimestampWithTimeZone < SimpleEncoder
|
30
|
-
STRFTIME_ISO_DATETIME_WITH_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N %:z".freeze
|
31
28
|
def encode(value)
|
32
|
-
value.respond_to?(:strftime) ? value.strftime(
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
class Numeric < SimpleEncoder
|
37
|
-
def encode(value)
|
38
|
-
value.is_a?(BigDecimal) ? value.to_s('F') : value
|
29
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S.%N %:z") : value
|
39
30
|
end
|
40
31
|
end
|
41
32
|
|
@@ -51,12 +42,12 @@ module PG
|
|
51
42
|
when IPAddr
|
52
43
|
default_prefix = (value.family == Socket::AF_INET ? 32 : 128)
|
53
44
|
s = value.to_s
|
54
|
-
|
45
|
+
if value.respond_to?(:prefix)
|
55
46
|
prefix = value.prefix
|
56
|
-
|
47
|
+
else
|
57
48
|
range = value.to_range
|
58
49
|
prefix = default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i
|
59
|
-
|
50
|
+
end
|
60
51
|
s << "/" << prefix.to_s if prefix != default_prefix
|
61
52
|
s
|
62
53
|
else
|
@@ -66,4 +57,3 @@ module PG
|
|
66
57
|
end
|
67
58
|
end
|
68
59
|
end # module PG
|
69
|
-
|
@@ -1,4 +1,5 @@
|
|
1
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 ?
|
13
|
+
type_strings = coders.map{|c| c ? c.inspect_short : 'nil' }
|
13
14
|
"#<#{self.class} #{type_strings.join(' ')}>"
|
14
15
|
end
|
15
16
|
end
|
data/spec/helpers.rb
CHANGED
@@ -172,18 +172,18 @@ module PG::TestingHelpers
|
|
172
172
|
datadir = testdir + 'data'
|
173
173
|
pidfile = datadir + 'postmaster.pid'
|
174
174
|
if pidfile.exist? && pid = pidfile.read.chomp.to_i
|
175
|
-
|
175
|
+
trace "pidfile (%p) exists: %d" % [ pidfile, pid ]
|
176
176
|
begin
|
177
177
|
Process.kill( 0, pid )
|
178
178
|
rescue Errno::ESRCH
|
179
|
-
|
179
|
+
trace "No postmaster running for %s" % [ datadir ]
|
180
180
|
# Process isn't alive, so don't try to stop it
|
181
181
|
else
|
182
|
-
|
182
|
+
trace "Stopping lingering database at PID %d" % [ pid ]
|
183
183
|
run 'pg_ctl', '-D', datadir.to_s, '-m', 'fast', 'stop'
|
184
184
|
end
|
185
185
|
else
|
186
|
-
|
186
|
+
trace "No pidfile (%p)" % [ pidfile ]
|
187
187
|
end
|
188
188
|
end
|
189
189
|
end
|
@@ -194,7 +194,7 @@ module PG::TestingHelpers
|
|
194
194
|
require 'pg'
|
195
195
|
stop_existing_postmasters()
|
196
196
|
|
197
|
-
|
197
|
+
trace "Setting up test database for #{description}"
|
198
198
|
@test_pgdata = TEST_DIRECTORY + 'data'
|
199
199
|
@test_pgdata.mkpath
|
200
200
|
|
@@ -209,7 +209,7 @@ module PG::TestingHelpers
|
|
209
209
|
begin
|
210
210
|
unless (@test_pgdata+"postgresql.conf").exist?
|
211
211
|
FileUtils.rm_rf( @test_pgdata, :verbose => $DEBUG )
|
212
|
-
|
212
|
+
trace "Running initdb"
|
213
213
|
log_and_run @logfile, 'initdb', '-E', 'UTF8', '--no-locale', '-D', @test_pgdata.to_s
|
214
214
|
end
|
215
215
|
|
@@ -218,7 +218,7 @@ module PG::TestingHelpers
|
|
218
218
|
'-D', @test_pgdata.to_s, 'start'
|
219
219
|
sleep 2
|
220
220
|
|
221
|
-
|
221
|
+
trace "Creating the test DB"
|
222
222
|
log_and_run @logfile, 'psql', '-e', '-c', 'DROP DATABASE IF EXISTS test', 'postgres'
|
223
223
|
log_and_run @logfile, 'createdb', '-e', 'test'
|
224
224
|
|
@@ -239,7 +239,7 @@ module PG::TestingHelpers
|
|
239
239
|
|
240
240
|
|
241
241
|
def teardown_testing_db( conn )
|
242
|
-
|
242
|
+
trace "Tearing down test database"
|
243
243
|
|
244
244
|
if conn
|
245
245
|
check_for_lingering_connections( conn )
|
@@ -376,5 +376,7 @@ RSpec.configure do |config|
|
|
376
376
|
config.filter_run_excluding( :postgresql_93 ) if PG.library_version < 90300
|
377
377
|
config.filter_run_excluding( :postgresql_94 ) if PG.library_version < 90400
|
378
378
|
config.filter_run_excluding( :postgresql_95 ) if PG.library_version < 90500
|
379
|
+
config.filter_run_excluding( :postgresql_96 ) if PG.library_version < 90600
|
379
380
|
config.filter_run_excluding( :postgresql_10 ) if PG.library_version < 100000
|
381
|
+
config.filter_run_excluding( :postgresql_12 ) if PG.library_version < 120000
|
380
382
|
end
|
@@ -21,6 +21,14 @@ ensure
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
def expect_to_typecase_result_value_warning
|
25
|
+
warning = 'Warning: no type cast defined for type "name" with oid 19. '\
|
26
|
+
"Please cast this type explicitly to TEXT to be safe for future changes.\n"\
|
27
|
+
'Warning: no type cast defined for type "regproc" with oid 24. '\
|
28
|
+
"Please cast this type explicitly to TEXT to be safe for future changes.\n"
|
29
|
+
expect { yield }.to output(warning).to_stderr
|
30
|
+
end
|
31
|
+
|
24
32
|
describe 'Basic type mapping' do
|
25
33
|
|
26
34
|
describe PG::BasicTypeMapForQueries do
|
@@ -32,8 +40,8 @@ describe 'Basic type mapping' do
|
|
32
40
|
# Encoding Examples
|
33
41
|
#
|
34
42
|
|
35
|
-
it "should do basic param encoding"
|
36
|
-
res = @conn.exec_params( "SELECT $1::int8
|
43
|
+
it "should do basic param encoding" do
|
44
|
+
res = @conn.exec_params( "SELECT $1::int8, $2::float, $3, $4::TEXT",
|
37
45
|
[1, 2.1, true, "b"], nil, basic_type_mapping )
|
38
46
|
|
39
47
|
expect( res.values ).to eq( [
|
@@ -43,20 +51,126 @@ describe 'Basic type mapping' do
|
|
43
51
|
expect( result_typenames(res) ).to eq( ['bigint', 'double precision', 'boolean', 'text'] )
|
44
52
|
end
|
45
53
|
|
46
|
-
it "should do
|
47
|
-
res = @conn.exec_params( "SELECT $1
|
48
|
-
|
49
|
-
|
50
|
-
|
54
|
+
it "should do basic Time encoding" do
|
55
|
+
res = @conn.exec_params( "SELECT $1 AT TIME ZONE '-02'",
|
56
|
+
[Time.new(2019, 12, 8, 20, 38, 12.123, "-01:00")], nil, basic_type_mapping )
|
57
|
+
|
58
|
+
expect( res.values ).to eq( [[ "2019-12-08 23:38:12.123" ]] )
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should do basic param encoding of various float values" do
|
62
|
+
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",
|
63
|
+
[0, 7, 9, 0.1, 0.9, -0.11, 10.11,
|
64
|
+
9876543210987654321e-400,
|
65
|
+
9876543210987654321e400,
|
66
|
+
-1.234567890123456789e-280,
|
67
|
+
-1.234567890123456789e280,
|
68
|
+
9876543210987654321e280
|
69
|
+
], nil, basic_type_mapping )
|
70
|
+
|
71
|
+
expect( res.values[0][0, 9] ).to eq(
|
72
|
+
[ "0", "7", "9", "0.1", "0.9", "-0.11", "10.11", "0", "Infinity" ]
|
73
|
+
)
|
74
|
+
|
75
|
+
expect( res.values[0][9] ).to match( /^-1\.2345678901234\d*e\-280$/ )
|
76
|
+
expect( res.values[0][10] ).to match( /^-1\.2345678901234\d*e\+280$/ )
|
77
|
+
expect( res.values[0][11] ).to match( /^9\.8765432109876\d*e\+298$/ )
|
78
|
+
|
79
|
+
expect( result_typenames(res) ).to eq( ['double precision'] * 12 )
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should do default array-as-array param encoding" do
|
83
|
+
expect( basic_type_mapping.encode_array_as).to eq(:array)
|
84
|
+
res = @conn.exec_params( "SELECT $1,$2,$3,$4,$5,$6", [
|
85
|
+
[1, 2, 3], # Integer -> bigint[]
|
86
|
+
[[1, 2], [3, nil]], # Integer two dimensions -> bigint[]
|
87
|
+
[1.11, 2.21], # Float -> double precision[]
|
88
|
+
['/,"'.gsub("/", "\\"), nil, 'abcäöü'], # String -> text[]
|
89
|
+
[BigDecimal("123.45")], # BigDecimal -> numeric[]
|
90
|
+
[IPAddr.new('1234::5678')], # IPAddr -> inet[]
|
51
91
|
], nil, basic_type_mapping )
|
52
92
|
|
53
93
|
expect( res.values ).to eq( [[
|
54
|
-
'{1,2,3}',
|
94
|
+
'{1,2,3}',
|
95
|
+
'{{1,2},{3,NULL}}',
|
55
96
|
'{1.11,2.21}',
|
56
97
|
'{"//,/"",NULL,abcäöü}'.gsub("/", "\\"),
|
98
|
+
'{123.45}',
|
99
|
+
'{1234::5678}',
|
57
100
|
]] )
|
58
101
|
|
59
|
-
expect( result_typenames(res) ).to eq( ['bigint[]', 'bigint[]', 'double precision[]', 'text[]'] )
|
102
|
+
expect( result_typenames(res) ).to eq( ['bigint[]', 'bigint[]', 'double precision[]', 'text[]', 'numeric[]', 'inet[]'] )
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should do default array-as-array param encoding with Time objects" do
|
106
|
+
res = @conn.exec_params( "SELECT $1", [
|
107
|
+
[Time.new(2019, 12, 8, 20, 38, 12.123, "-01:00")], # Time -> timestamptz[]
|
108
|
+
], nil, basic_type_mapping )
|
109
|
+
|
110
|
+
expect( res.values[0][0] ).to match( /\{\"2019-12-08 \d\d:38:12.123[+-]\d\d\"\}/ )
|
111
|
+
expect( result_typenames(res) ).to eq( ['timestamp with time zone[]'] )
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should do array-as-json encoding" do
|
115
|
+
basic_type_mapping.encode_array_as = :json
|
116
|
+
expect( basic_type_mapping.encode_array_as).to eq(:json)
|
117
|
+
|
118
|
+
res = @conn.exec_params( "SELECT $1::JSON, $2::JSON", [
|
119
|
+
[1, {a: 5}, true, ["a", 2], [3.4, nil]],
|
120
|
+
['/,"'.gsub("/", "\\"), nil, 'abcäöü'],
|
121
|
+
], nil, basic_type_mapping )
|
122
|
+
|
123
|
+
expect( res.values ).to eq( [[
|
124
|
+
'[1,{"a":5},true,["a",2],[3.4,null]]',
|
125
|
+
'["//,/"",null,"abcäöü"]'.gsub("/", "\\"),
|
126
|
+
]] )
|
127
|
+
|
128
|
+
expect( result_typenames(res) ).to eq( ['json', 'json'] )
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should do hash-as-json encoding" do
|
132
|
+
res = @conn.exec_params( "SELECT $1::JSON, $2::JSON", [
|
133
|
+
{a: 5, b: ["a", 2], c: nil},
|
134
|
+
{qu: '/,"'.gsub("/", "\\"), ni: nil, uml: 'abcäöü'},
|
135
|
+
], nil, basic_type_mapping )
|
136
|
+
|
137
|
+
expect( res.values ).to eq( [[
|
138
|
+
'{"a":5,"b":["a",2],"c":null}',
|
139
|
+
'{"qu":"//,/"","ni":null,"uml":"abcäöü"}'.gsub("/", "\\"),
|
140
|
+
]] )
|
141
|
+
|
142
|
+
expect( result_typenames(res) ).to eq( ['json', 'json'] )
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "Record encoding" do
|
146
|
+
before :all do
|
147
|
+
@conn.exec("CREATE TYPE test_record1 AS (i int, d float, t text)")
|
148
|
+
@conn.exec("CREATE TYPE test_record2 AS (i int, r test_record1)")
|
149
|
+
end
|
150
|
+
|
151
|
+
after :all do
|
152
|
+
@conn.exec("DROP TYPE IF EXISTS test_record2 CASCADE")
|
153
|
+
@conn.exec("DROP TYPE IF EXISTS test_record1 CASCADE")
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should do array-as-record encoding" do
|
157
|
+
basic_type_mapping.encode_array_as = :record
|
158
|
+
expect( basic_type_mapping.encode_array_as).to eq(:record)
|
159
|
+
|
160
|
+
res = @conn.exec_params( "SELECT $1::test_record1, $2::test_record2, $3::text", [
|
161
|
+
[5, 3.4, "txt"],
|
162
|
+
[1, [2, 4.5, "bcd"]],
|
163
|
+
[4, 5, 6],
|
164
|
+
], nil, basic_type_mapping )
|
165
|
+
|
166
|
+
expect( res.values ).to eq( [[
|
167
|
+
'(5,3.4,txt)',
|
168
|
+
'(1,"(2,4.5,bcd)")',
|
169
|
+
'("4","5","6")',
|
170
|
+
]] )
|
171
|
+
|
172
|
+
expect( result_typenames(res) ).to eq( ['test_record1', 'test_record2', 'text'] )
|
173
|
+
end
|
60
174
|
end
|
61
175
|
|
62
176
|
it "should do bigdecimal param encoding" do
|
@@ -82,6 +196,23 @@ describe 'Basic type mapping' do
|
|
82
196
|
expect( result_typenames(res) ).to eq( ['inet', 'inet', 'cidr', 'cidr'] )
|
83
197
|
end
|
84
198
|
|
199
|
+
it "should do array of string encoding on unknown classes" do
|
200
|
+
iv = Class.new do
|
201
|
+
def to_s
|
202
|
+
"abc"
|
203
|
+
end
|
204
|
+
end.new
|
205
|
+
res = @conn.exec_params( "SELECT $1", [
|
206
|
+
[iv, iv], # Unknown -> text[]
|
207
|
+
], nil, basic_type_mapping )
|
208
|
+
|
209
|
+
expect( res.values ).to eq( [[
|
210
|
+
'{abc,abc}',
|
211
|
+
]] )
|
212
|
+
|
213
|
+
expect( result_typenames(res) ).to eq( ['text[]'] )
|
214
|
+
end
|
215
|
+
|
85
216
|
end
|
86
217
|
|
87
218
|
|
@@ -95,7 +226,7 @@ describe 'Basic type mapping' do
|
|
95
226
|
# Decoding Examples
|
96
227
|
#
|
97
228
|
|
98
|
-
it "should do OID based type conversions"
|
229
|
+
it "should do OID based type conversions" do
|
99
230
|
res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, TRUE, '2013-06-30'::DATE, generate_series(4,5)" )
|
100
231
|
expect( res.map_types!(basic_type_mapping).values ).to eq( [
|
101
232
|
[ 1, 'a', 2.0, true, Date.new(2013,6,30), 4 ],
|
@@ -187,7 +318,9 @@ describe 'Basic type mapping' do
|
|
187
318
|
it "should convert format #{format} timestamps per TimestampUtc" do
|
188
319
|
restore_type("timestamp") do
|
189
320
|
PG::BasicTypeRegistry.register_type 0, 'timestamp', nil, PG::TextDecoder::TimestampUtc
|
190
|
-
|
321
|
+
expect_to_typecase_result_value_warning do
|
322
|
+
@conn.type_map_for_results = PG::BasicTypeMapForResults.new(@conn)
|
323
|
+
end
|
191
324
|
res = @conn.exec_params( "SELECT CAST('2013-07-31 23:58:59+02' AS TIMESTAMP WITHOUT TIME ZONE),
|
192
325
|
CAST('1913-12-31 23:58:59.1231-03' AS TIMESTAMP WITHOUT TIME ZONE),
|
193
326
|
CAST('4714-11-24 23:58:59.1231-03 BC' AS TIMESTAMP WITHOUT TIME ZONE),
|
@@ -209,7 +342,9 @@ describe 'Basic type mapping' do
|
|
209
342
|
restore_type("timestamp") do
|
210
343
|
PG::BasicTypeRegistry.register_type 0, 'timestamp', nil, PG::TextDecoder::TimestampUtcToLocal
|
211
344
|
PG::BasicTypeRegistry.register_type 1, 'timestamp', nil, PG::BinaryDecoder::TimestampUtcToLocal
|
212
|
-
|
345
|
+
expect_to_typecase_result_value_warning do
|
346
|
+
@conn.type_map_for_results = PG::BasicTypeMapForResults.new(@conn)
|
347
|
+
end
|
213
348
|
res = @conn.exec_params( "SELECT CAST('2013-07-31 23:58:59+02' AS TIMESTAMP WITHOUT TIME ZONE),
|
214
349
|
CAST('1913-12-31 23:58:59.1231-03' AS TIMESTAMP WITHOUT TIME ZONE),
|
215
350
|
CAST('4714-11-24 23:58:59.1231-03 BC' AS TIMESTAMP WITHOUT TIME ZONE),
|
@@ -231,7 +366,9 @@ describe 'Basic type mapping' do
|
|
231
366
|
restore_type("timestamp") do
|
232
367
|
PG::BasicTypeRegistry.register_type 0, 'timestamp', nil, PG::TextDecoder::TimestampLocal
|
233
368
|
PG::BasicTypeRegistry.register_type 1, 'timestamp', nil, PG::BinaryDecoder::TimestampLocal
|
234
|
-
|
369
|
+
expect_to_typecase_result_value_warning do
|
370
|
+
@conn.type_map_for_results = PG::BasicTypeMapForResults.new(@conn)
|
371
|
+
end
|
235
372
|
res = @conn.exec_params( "SELECT CAST('2013-07-31 23:58:59' AS TIMESTAMP WITHOUT TIME ZONE),
|
236
373
|
CAST('1913-12-31 23:58:59.1231' AS TIMESTAMP WITHOUT TIME ZONE),
|
237
374
|
CAST('4714-11-24 23:58:59.1231-03 BC' AS TIMESTAMP WITHOUT TIME ZONE),
|
data/spec/pg/connection_spec.rb
CHANGED
@@ -288,7 +288,20 @@ describe PG::Connection do
|
|
288
288
|
expect( @conn.host ).to eq( "localhost" )
|
289
289
|
end
|
290
290
|
|
291
|
-
|
291
|
+
it "can set error verbosity" do
|
292
|
+
old = @conn.set_error_verbosity( PG::PQERRORS_TERSE )
|
293
|
+
new = @conn.set_error_verbosity( old )
|
294
|
+
expect( new ).to eq( PG::PQERRORS_TERSE )
|
295
|
+
end
|
296
|
+
|
297
|
+
it "can set error context visibility", :postgresql_96 do
|
298
|
+
old = @conn.set_error_context_visibility( PG::PQSHOW_CONTEXT_NEVER )
|
299
|
+
new = @conn.set_error_context_visibility( old )
|
300
|
+
expect( new ).to eq( PG::PQSHOW_CONTEXT_NEVER )
|
301
|
+
end
|
302
|
+
|
303
|
+
let(:expected_trace_output) do
|
304
|
+
%{
|
292
305
|
To backend> Msg Q
|
293
306
|
To backend> "SELECT 1 AS one"
|
294
307
|
To backend> Msg complete, length 21
|
@@ -316,6 +329,7 @@ describe PG::Connection do
|
|
316
329
|
From backend (#4)> 5
|
317
330
|
From backend> T
|
318
331
|
}.gsub( /^\t{2}/, '' ).lstrip
|
332
|
+
end
|
319
333
|
|
320
334
|
it "trace and untrace client-server communication", :unix do
|
321
335
|
# be careful to explicitly close files so that the
|
@@ -341,7 +355,7 @@ describe PG::Connection do
|
|
341
355
|
# From backend> T
|
342
356
|
trace_data.sub!( /(From backend> Z\nFrom backend \(#4\)> 5\n){3}/m, '\\1\\1' )
|
343
357
|
|
344
|
-
expect( trace_data ).to eq(
|
358
|
+
expect( trace_data ).to eq( expected_trace_output )
|
345
359
|
end
|
346
360
|
|
347
361
|
it "allows a query to be cancelled" do
|
@@ -356,8 +370,6 @@ describe PG::Connection do
|
|
356
370
|
end
|
357
371
|
|
358
372
|
it "can stop a thread that runs a blocking query with async_exec" do
|
359
|
-
pending "this does not work on Rubinius" if RUBY_ENGINE=='rbx'
|
360
|
-
|
361
373
|
start = Time.now
|
362
374
|
t = Thread.new do
|
363
375
|
@conn.async_exec( 'select pg_sleep(10)' )
|
@@ -371,24 +383,16 @@ describe PG::Connection do
|
|
371
383
|
|
372
384
|
it "should work together with signal handlers", :unix do
|
373
385
|
signal_received = false
|
374
|
-
trap '
|
386
|
+
trap 'USR2' do
|
375
387
|
signal_received = true
|
376
388
|
end
|
377
389
|
|
378
390
|
Thread.new do
|
379
391
|
sleep 0.1
|
380
|
-
Process.kill("
|
392
|
+
Process.kill("USR2", Process.pid)
|
381
393
|
end
|
382
394
|
@conn.exec("select pg_sleep(0.3)")
|
383
395
|
expect( signal_received ).to be_truthy
|
384
|
-
|
385
|
-
signal_received = false
|
386
|
-
Thread.new do
|
387
|
-
sleep 0.1
|
388
|
-
Process.kill("USR1", Process.pid)
|
389
|
-
end
|
390
|
-
@conn.async_exec("select pg_sleep(0.3)")
|
391
|
-
expect( signal_received ).to be_truthy
|
392
396
|
end
|
393
397
|
|
394
398
|
|
@@ -571,7 +575,7 @@ describe PG::Connection do
|
|
571
575
|
expect( @conn.wait_for_notify( 1 ) ).to be_nil
|
572
576
|
expect( notices.first ).to_not be_nil
|
573
577
|
et = Time.now
|
574
|
-
expect( (et - notices.first[1]) ).to be >= 0.
|
578
|
+
expect( (et - notices.first[1]) ).to be >= 0.3
|
575
579
|
expect( (et - st) ).to be >= 0.9
|
576
580
|
expect( (et - st) ).to be < 1.4
|
577
581
|
end
|
@@ -675,7 +679,7 @@ describe PG::Connection do
|
|
675
679
|
@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
676
680
|
@conn.put_copy_data "xyz\n"
|
677
681
|
end
|
678
|
-
}.to raise_error(PG::Error, /invalid input syntax for integer/)
|
682
|
+
}.to raise_error(PG::Error, /invalid input syntax for .*integer/)
|
679
683
|
end
|
680
684
|
expect( @conn ).to still_be_usable
|
681
685
|
end
|
@@ -1233,53 +1237,41 @@ describe PG::Connection do
|
|
1233
1237
|
|
1234
1238
|
end
|
1235
1239
|
|
1236
|
-
context "multinationalization support"
|
1240
|
+
context "multinationalization support" do
|
1237
1241
|
|
1238
1242
|
describe "rubyforge #22925: m17n support" do
|
1239
1243
|
it "should return results in the same encoding as the client (iso-8859-1)" do
|
1240
|
-
|
1241
|
-
@conn.
|
1242
|
-
|
1243
|
-
res = conn.exec_params("VALUES ('fantasia')", [], 0)
|
1244
|
-
out_string = res[0]['column1']
|
1245
|
-
end
|
1244
|
+
@conn.internal_encoding = 'iso8859-1'
|
1245
|
+
res = @conn.exec_params("VALUES ('fantasia')", [], 0)
|
1246
|
+
out_string = res[0]['column1']
|
1246
1247
|
expect( out_string ).to eq( 'fantasia' )
|
1247
1248
|
expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
|
1248
1249
|
end
|
1249
1250
|
|
1250
1251
|
it "should return results in the same encoding as the client (utf-8)" do
|
1251
|
-
|
1252
|
-
@conn.
|
1253
|
-
|
1254
|
-
res = conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
|
1255
|
-
out_string = res[0]['column1']
|
1256
|
-
end
|
1252
|
+
@conn.internal_encoding = 'utf-8'
|
1253
|
+
res = @conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
|
1254
|
+
out_string = res[0]['column1']
|
1257
1255
|
expect( out_string ).to eq( '世界線航跡蔵' )
|
1258
1256
|
expect( out_string.encoding ).to eq( Encoding::UTF_8 )
|
1259
1257
|
end
|
1260
1258
|
|
1261
1259
|
it "should return results in the same encoding as the client (EUC-JP)" do
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
res = conn.exec_params(stmt, [], 0)
|
1267
|
-
out_string = res[0]['column1']
|
1268
|
-
end
|
1260
|
+
@conn.internal_encoding = 'EUC-JP'
|
1261
|
+
stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
|
1262
|
+
res = @conn.exec_params(stmt, [], 0)
|
1263
|
+
out_string = res[0]['column1']
|
1269
1264
|
expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
|
1270
1265
|
expect( out_string.encoding ).to eq( Encoding::EUC_JP )
|
1271
1266
|
end
|
1272
1267
|
|
1273
1268
|
it "returns the results in the correct encoding even if the client_encoding has " +
|
1274
1269
|
"changed since the results were fetched" do
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
conn.internal_encoding = 'utf-8'
|
1281
|
-
out_string = res[0]['column1']
|
1282
|
-
end
|
1270
|
+
@conn.internal_encoding = 'EUC-JP'
|
1271
|
+
stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
|
1272
|
+
res = @conn.exec_params(stmt, [], 0)
|
1273
|
+
@conn.internal_encoding = 'utf-8'
|
1274
|
+
out_string = res[0]['column1']
|
1283
1275
|
expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
|
1284
1276
|
expect( out_string.encoding ).to eq( Encoding::EUC_JP )
|
1285
1277
|
end
|
@@ -1358,6 +1350,21 @@ describe PG::Connection do
|
|
1358
1350
|
expect { @conn.set_client_encoding( :invalid ) }.to raise_error(TypeError)
|
1359
1351
|
expect { @conn.set_client_encoding( nil ) }.to raise_error(TypeError)
|
1360
1352
|
end
|
1353
|
+
|
1354
|
+
it "can use an encoding with high index for client encoding" do
|
1355
|
+
# Allocate a lot of encoding indices, so that MRI's ENCODING_INLINE_MAX is exceeded
|
1356
|
+
unless Encoding.name_list.include?("pgtest-0")
|
1357
|
+
256.times do |eidx|
|
1358
|
+
Encoding::UTF_8.replicate("pgtest-#{eidx}")
|
1359
|
+
end
|
1360
|
+
end
|
1361
|
+
|
1362
|
+
# Now allocate the JOHAB encoding with an unusual high index
|
1363
|
+
@conn.set_client_encoding "JOHAB"
|
1364
|
+
val = @conn.exec("SELECT chr(x'3391'::int)").values[0][0]
|
1365
|
+
expect( val.encoding.name ).to eq( "JOHAB" )
|
1366
|
+
end
|
1367
|
+
|
1361
1368
|
end
|
1362
1369
|
|
1363
1370
|
describe "respect and convert character encoding of input strings" do
|
@@ -1501,7 +1508,7 @@ describe PG::Connection do
|
|
1501
1508
|
|
1502
1509
|
describe "Ruby 1.9.x default_internal encoding" do
|
1503
1510
|
|
1504
|
-
it "honors the Encoding.default_internal if it's set and the synchronous interface is used" do
|
1511
|
+
it "honors the Encoding.default_internal if it's set and the synchronous interface is used", :without_transaction do
|
1505
1512
|
@conn.transaction do |txn_conn|
|
1506
1513
|
txn_conn.internal_encoding = Encoding::ISO8859_1
|
1507
1514
|
txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
|
@@ -1603,9 +1610,8 @@ describe PG::Connection do
|
|
1603
1610
|
end
|
1604
1611
|
end
|
1605
1612
|
|
1606
|
-
it "receives properly encoded text from wait_for_notify" do
|
1613
|
+
it "receives properly encoded text from wait_for_notify", :without_transaction do
|
1607
1614
|
@conn.internal_encoding = 'utf-8'
|
1608
|
-
@conn.exec( 'ROLLBACK' )
|
1609
1615
|
@conn.exec( 'LISTEN "Möhre"' )
|
1610
1616
|
@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
|
1611
1617
|
event, pid, msg = nil
|
@@ -1620,9 +1626,8 @@ describe PG::Connection do
|
|
1620
1626
|
expect( msg.encoding ).to eq( Encoding::UTF_8 )
|
1621
1627
|
end
|
1622
1628
|
|
1623
|
-
it "returns properly encoded text from notifies" do
|
1629
|
+
it "returns properly encoded text from notifies", :without_transaction do
|
1624
1630
|
@conn.internal_encoding = 'utf-8'
|
1625
|
-
@conn.exec( 'ROLLBACK' )
|
1626
1631
|
@conn.exec( 'LISTEN "Möhre"' )
|
1627
1632
|
@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
|
1628
1633
|
@conn.exec( 'UNLISTEN "Möhre"' )
|
@@ -1636,7 +1641,7 @@ describe PG::Connection do
|
|
1636
1641
|
end
|
1637
1642
|
end
|
1638
1643
|
|
1639
|
-
context "OS thread support"
|
1644
|
+
context "OS thread support" do
|
1640
1645
|
it "Connection#exec shouldn't block a second thread" do
|
1641
1646
|
t = Thread.new do
|
1642
1647
|
@conn.exec( "select pg_sleep(1)" )
|
@@ -1831,6 +1836,40 @@ describe PG::Connection do
|
|
1831
1836
|
end
|
1832
1837
|
end
|
1833
1838
|
|
1839
|
+
describe :field_name_type do
|
1840
|
+
before :each do
|
1841
|
+
@conn2 = PG.connect(@conninfo)
|
1842
|
+
end
|
1843
|
+
after :each do
|
1844
|
+
@conn2.close
|
1845
|
+
end
|
1846
|
+
|
1847
|
+
it "uses string field names per default" do
|
1848
|
+
expect(@conn2.field_name_type).to eq(:string)
|
1849
|
+
end
|
1850
|
+
|
1851
|
+
it "can set string field names" do
|
1852
|
+
@conn2.field_name_type = :string
|
1853
|
+
expect(@conn2.field_name_type).to eq(:string)
|
1854
|
+
res = @conn2.exec("SELECT 1 as az")
|
1855
|
+
expect(res.field_name_type).to eq(:string)
|
1856
|
+
expect(res.fields).to eq(["az"])
|
1857
|
+
end
|
1858
|
+
|
1859
|
+
it "can set symbol field names" do
|
1860
|
+
@conn2.field_name_type = :symbol
|
1861
|
+
expect(@conn2.field_name_type).to eq(:symbol)
|
1862
|
+
res = @conn2.exec("SELECT 1 as az")
|
1863
|
+
expect(res.field_name_type).to eq(:symbol)
|
1864
|
+
expect(res.fields).to eq([:az])
|
1865
|
+
end
|
1866
|
+
|
1867
|
+
it "can't set invalid values" do
|
1868
|
+
expect{ @conn2.field_name_type = :sym }.to raise_error(ArgumentError, /invalid argument :sym/)
|
1869
|
+
expect{ @conn2.field_name_type = "symbol" }.to raise_error(ArgumentError, /invalid argument "symbol"/)
|
1870
|
+
end
|
1871
|
+
end
|
1872
|
+
|
1834
1873
|
describe "deprecated forms of methods" do
|
1835
1874
|
it "should forward exec to exec_params" do
|
1836
1875
|
res = @conn.exec("VALUES($1::INT)", [7]).values
|