pg 0.18.1 → 0.19.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 +3 -3
- data.tar.gz.sig +0 -0
- data/BSDL +2 -2
- data/ChangeLog +1004 -4
- data/History.rdoc +66 -0
- data/README-Windows.rdoc +15 -26
- data/README.rdoc +16 -9
- data/Rakefile +26 -16
- data/Rakefile.cross +42 -21
- data/ext/errorcodes.def +16 -0
- data/ext/errorcodes.txt +5 -1
- data/ext/extconf.rb +14 -2
- data/ext/gvl_wrappers.h +4 -0
- data/ext/pg.c +5 -4
- data/ext/pg.h +15 -2
- data/ext/pg_binary_decoder.c +3 -1
- data/ext/pg_binary_encoder.c +14 -12
- data/ext/pg_coder.c +30 -9
- data/ext/pg_connection.c +241 -115
- data/ext/pg_copy_coder.c +34 -4
- data/ext/pg_result.c +5 -5
- data/ext/pg_text_decoder.c +9 -10
- data/ext/pg_text_encoder.c +93 -73
- data/ext/pg_type_map.c +7 -7
- data/ext/pg_type_map_by_column.c +7 -7
- data/ext/pg_type_map_by_mri_type.c +2 -2
- data/ext/pg_type_map_in_ruby.c +4 -7
- data/ext/util.c +3 -3
- data/ext/util.h +1 -1
- data/lib/pg.rb +3 -3
- data/lib/pg/basic_type_mapping.rb +69 -42
- data/lib/pg/connection.rb +84 -34
- data/lib/pg/result.rb +6 -2
- data/lib/pg/text_decoder.rb +12 -3
- data/lib/pg/text_encoder.rb +8 -0
- data/spec/helpers.rb +7 -10
- data/spec/pg/basic_type_mapping_spec.rb +58 -4
- data/spec/pg/connection_spec.rb +251 -34
- data/spec/pg/type_map_by_class_spec.rb +1 -1
- data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
- data/spec/pg/type_spec.rb +144 -32
- metadata +65 -52
- metadata.gz.sig +0 -0
data/lib/pg/result.rb
CHANGED
@@ -12,15 +12,19 @@ class PG::Result
|
|
12
12
|
# See PG::BasicTypeMapForResults
|
13
13
|
def map_types!(type_map)
|
14
14
|
self.type_map = type_map
|
15
|
-
self
|
15
|
+
return self
|
16
16
|
end
|
17
17
|
|
18
|
+
|
19
|
+
### Return a String representation of the object suitable for debugging.
|
18
20
|
def inspect
|
19
21
|
str = self.to_s
|
20
22
|
str[-1,0] = " status=#{res_status(result_status)} ntuples=#{ntuples} nfields=#{nfields} cmd_tuples=#{cmd_tuples}"
|
21
|
-
str
|
23
|
+
return str
|
22
24
|
end
|
25
|
+
|
23
26
|
end # class PG::Result
|
24
27
|
|
28
|
+
# :stopdoc:
|
25
29
|
# Backward-compatible alias
|
26
30
|
PGresult = PG::Result
|
data/lib/pg/text_decoder.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'date'
|
4
|
+
require 'json'
|
5
|
+
|
3
6
|
module PG
|
4
7
|
module TextDecoder
|
5
8
|
class Date < SimpleDecoder
|
@@ -7,7 +10,7 @@ module PG
|
|
7
10
|
|
8
11
|
def decode(string, tuple=nil, field=nil)
|
9
12
|
if string =~ ISO_DATE
|
10
|
-
|
13
|
+
::Date.new $1.to_i, $2.to_i, $3.to_i
|
11
14
|
else
|
12
15
|
string
|
13
16
|
end
|
@@ -27,16 +30,22 @@ module PG
|
|
27
30
|
end
|
28
31
|
|
29
32
|
class TimestampWithTimeZone < SimpleDecoder
|
30
|
-
ISO_DATETIME_WITH_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?([-\+]\d\d)\z/
|
33
|
+
ISO_DATETIME_WITH_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?([-\+]\d\d):?(\d\d)?:?(\d\d)?\z/
|
31
34
|
|
32
35
|
def decode(string, tuple=nil, field=nil)
|
33
36
|
if string =~ ISO_DATETIME_WITH_TIMEZONE
|
34
|
-
Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r, "#{$8}
|
37
|
+
Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r, "#{$8}:#{$9 || '00'}:#{$10 || '00'}"
|
35
38
|
else
|
36
39
|
string
|
37
40
|
end
|
38
41
|
end
|
39
42
|
end
|
43
|
+
|
44
|
+
class JSON < SimpleDecoder
|
45
|
+
def decode(string, tuple=nil, field=nil)
|
46
|
+
::JSON.load(string)
|
47
|
+
end
|
48
|
+
end
|
40
49
|
end
|
41
50
|
end # module PG
|
42
51
|
|
data/lib/pg/text_encoder.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'json'
|
4
|
+
|
3
5
|
module PG
|
4
6
|
module TextEncoder
|
5
7
|
class Date < SimpleEncoder
|
@@ -22,6 +24,12 @@ module PG
|
|
22
24
|
value.respond_to?(:strftime) ? value.strftime(STRFTIME_ISO_DATETIME_WITH_TIMEZONE) : value
|
23
25
|
end
|
24
26
|
end
|
27
|
+
|
28
|
+
class JSON < SimpleEncoder
|
29
|
+
def encode(value)
|
30
|
+
::JSON.dump(value)
|
31
|
+
end
|
32
|
+
end
|
25
33
|
end
|
26
34
|
end # module PG
|
27
35
|
|
data/spec/helpers.rb
CHANGED
@@ -255,7 +255,7 @@ module PG::TestingHelpers
|
|
255
255
|
unless conns.empty?
|
256
256
|
puts "Lingering connections remain:"
|
257
257
|
conns.each do |row|
|
258
|
-
puts " [%
|
258
|
+
puts " [%s] {%s} %s -- %s" % row.values_at( 'pid', 'state', 'application_name', 'query' )
|
259
259
|
end
|
260
260
|
end
|
261
261
|
end
|
@@ -339,17 +339,14 @@ RSpec.configure do |config|
|
|
339
339
|
config.filter_run_excluding :socket_io unless
|
340
340
|
PG::Connection.instance_methods.map( &:to_sym ).include?( :socket_io )
|
341
341
|
|
342
|
-
|
343
|
-
|
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 )
|
342
|
+
if PG.library_version < 90200
|
343
|
+
config.filter_run_excluding( :postgresql_92, :postgresql_93, :postgresql_94, :postgresql_95 )
|
349
344
|
elsif PG.library_version < 90300
|
350
|
-
config.filter_run_excluding( :postgresql_93, :postgresql_94 )
|
345
|
+
config.filter_run_excluding( :postgresql_93, :postgresql_94, :postgresql_95 )
|
351
346
|
elsif PG.library_version < 90400
|
352
|
-
config.filter_run_excluding( :postgresql_94 )
|
347
|
+
config.filter_run_excluding( :postgresql_94, :postgresql_95 )
|
348
|
+
elsif PG.library_version < 90500
|
349
|
+
config.filter_run_excluding( :postgresql_95 )
|
353
350
|
end
|
354
351
|
end
|
355
352
|
|
@@ -58,8 +58,8 @@ describe 'Basic type mapping' do
|
|
58
58
|
it "should do OID based type conversions", :ruby_19 do
|
59
59
|
res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, TRUE, '2013-06-30'::DATE, generate_series(4,5)" )
|
60
60
|
expect( res.map_types!(basic_type_mapping).values ).to eq( [
|
61
|
-
[ 1, 'a', 2.0, true,
|
62
|
-
[ 1, 'a', 2.0, true,
|
61
|
+
[ 1, 'a', 2.0, true, Date.new(2013,6,30), 4 ],
|
62
|
+
[ 1, 'a', 2.0, true, Date.new(2013,6,30), 5 ],
|
63
63
|
] )
|
64
64
|
end
|
65
65
|
|
@@ -159,13 +159,34 @@ describe 'Basic type mapping' do
|
|
159
159
|
CAST('1913-12-31' AS DATE),
|
160
160
|
CAST('infinity' AS DATE),
|
161
161
|
CAST('-infinity' AS DATE)", [], format )
|
162
|
-
expect( res.getvalue(0,0) ).to eq(
|
163
|
-
expect( res.getvalue(0,1) ).to eq(
|
162
|
+
expect( res.getvalue(0,0) ).to eq( Date.new(2113, 12, 31) )
|
163
|
+
expect( res.getvalue(0,1) ).to eq( Date.new(1913, 12, 31) )
|
164
164
|
expect( res.getvalue(0,2) ).to eq( 'infinity' )
|
165
165
|
expect( res.getvalue(0,3) ).to eq( '-infinity' )
|
166
166
|
end
|
167
167
|
end
|
168
168
|
|
169
|
+
it "should do JSON conversions", :postgresql_94 do
|
170
|
+
[0].each do |format|
|
171
|
+
['JSON', 'JSONB'].each do |type|
|
172
|
+
res = @conn.exec( "SELECT CAST('123' AS #{type}),
|
173
|
+
CAST('12.3' AS #{type}),
|
174
|
+
CAST('true' AS #{type}),
|
175
|
+
CAST('false' AS #{type}),
|
176
|
+
CAST('null' AS #{type}),
|
177
|
+
CAST('[1, \"a\", null]' AS #{type}),
|
178
|
+
CAST('{\"b\" : [2,3]}' AS #{type})", [], format )
|
179
|
+
expect( res.getvalue(0,0) ).to eq( 123 )
|
180
|
+
expect( res.getvalue(0,1) ).to be_within(0.1).of( 12.3 )
|
181
|
+
expect( res.getvalue(0,2) ).to eq( true )
|
182
|
+
expect( res.getvalue(0,3) ).to eq( false )
|
183
|
+
expect( res.getvalue(0,4) ).to eq( nil )
|
184
|
+
expect( res.getvalue(0,5) ).to eq( [1, "a", nil] )
|
185
|
+
expect( res.getvalue(0,6) ).to eq( {"b" => [2, 3]} )
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
169
190
|
it "should do array type conversions" do
|
170
191
|
[0].each do |format|
|
171
192
|
res = @conn.exec( "SELECT CAST('{1,2,3}' AS INT2[]), CAST('{{1,2},{3,4}}' AS INT2[][]),
|
@@ -228,6 +249,39 @@ describe 'Basic type mapping' do
|
|
228
249
|
res = @conn.exec( "SELECT * FROM copytable" )
|
229
250
|
expect( res.values ).to eq( [['a', '123', '{5,4,3}'], ['b', '234', '{2,3}']] )
|
230
251
|
end
|
252
|
+
|
253
|
+
it "can do JSON conversions", :postgresql_94 do
|
254
|
+
['JSON', 'JSONB'].each do |type|
|
255
|
+
sql = "SELECT CAST('123' AS #{type}),
|
256
|
+
CAST('12.3' AS #{type}),
|
257
|
+
CAST('true' AS #{type}),
|
258
|
+
CAST('false' AS #{type}),
|
259
|
+
CAST('null' AS #{type}),
|
260
|
+
CAST('[1, \"a\", null]' AS #{type}),
|
261
|
+
CAST('{\"b\" : [2,3]}' AS #{type})"
|
262
|
+
|
263
|
+
tm = basic_type_mapping.build_column_map( @conn.exec( sql ) )
|
264
|
+
expect( tm.coders.map(&:name) ).to eq( [type.downcase] * 7 )
|
265
|
+
|
266
|
+
res = @conn.exec_params( "SELECT $1, $2, $3, $4, $5, $6, $7",
|
267
|
+
[ 123,
|
268
|
+
12.3,
|
269
|
+
true,
|
270
|
+
false,
|
271
|
+
nil,
|
272
|
+
[1, "a", nil],
|
273
|
+
{"b" => [2, 3]},
|
274
|
+
], 0, tm )
|
275
|
+
|
276
|
+
expect( res.getvalue(0,0) ).to eq( "123" )
|
277
|
+
expect( res.getvalue(0,1) ).to eq( "12.3" )
|
278
|
+
expect( res.getvalue(0,2) ).to eq( "true" )
|
279
|
+
expect( res.getvalue(0,3) ).to eq( "false" )
|
280
|
+
expect( res.getvalue(0,4) ).to eq( nil )
|
281
|
+
expect( res.getvalue(0,5).gsub(" ","") ).to eq( "[1,\"a\",null]" )
|
282
|
+
expect( res.getvalue(0,6).gsub(" ","") ).to eq( "{\"b\":[2,3]}" )
|
283
|
+
end
|
284
|
+
end
|
231
285
|
end
|
232
286
|
|
233
287
|
context "with usage of result oids for copy encoder selection" do
|
data/spec/pg/connection_spec.rb
CHANGED
@@ -45,6 +45,14 @@ describe PG::Connection do
|
|
45
45
|
expect( optstring ).to match( /(^|\s)user='jrandom'/ )
|
46
46
|
end
|
47
47
|
|
48
|
+
it "can create a connection option string from an option string and a hash" do
|
49
|
+
optstring = described_class.parse_connect_args( 'dbname=original', :user => 'jrandom' )
|
50
|
+
|
51
|
+
expect( optstring ).to be_a( String )
|
52
|
+
expect( optstring ).to match( /(^|\s)dbname=original/ )
|
53
|
+
expect( optstring ).to match( /(^|\s)user='jrandom'/ )
|
54
|
+
end
|
55
|
+
|
48
56
|
it "escapes single quotes and backslashes in connection parameters" do
|
49
57
|
expect(
|
50
58
|
described_class.parse_connect_args( "DB 'browser' \\" )
|
@@ -52,18 +60,72 @@ describe PG::Connection do
|
|
52
60
|
|
53
61
|
end
|
54
62
|
|
63
|
+
let(:uri) { 'postgresql://user:pass@pgsql.example.com:222/db01?sslmode=require' }
|
64
|
+
|
65
|
+
it "can connect using a URI" do
|
66
|
+
string = described_class.parse_connect_args( uri )
|
67
|
+
|
68
|
+
expect( string ).to be_a( String )
|
69
|
+
expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
|
70
|
+
expect( string ).to match( %r{\?.*sslmode=require} )
|
71
|
+
|
72
|
+
string = described_class.parse_connect_args( URI.parse(uri) )
|
73
|
+
|
74
|
+
expect( string ).to be_a( String )
|
75
|
+
expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
|
76
|
+
expect( string ).to match( %r{\?.*sslmode=require} )
|
77
|
+
end
|
78
|
+
|
79
|
+
it "can create a connection URI from a URI and a hash" do
|
80
|
+
string = described_class.parse_connect_args( uri, :connect_timeout => 2 )
|
81
|
+
|
82
|
+
expect( string ).to be_a( String )
|
83
|
+
expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
|
84
|
+
expect( string ).to match( %r{\?.*sslmode=require} )
|
85
|
+
expect( string ).to match( %r{\?.*connect_timeout=2} )
|
86
|
+
|
87
|
+
string = described_class.parse_connect_args( uri,
|
88
|
+
:user => 'a',
|
89
|
+
:password => 'b',
|
90
|
+
:host => 'localhost',
|
91
|
+
:port => 555,
|
92
|
+
:dbname => 'x' )
|
93
|
+
|
94
|
+
expect( string ).to be_a( String )
|
95
|
+
expect( string ).to match( %r{^postgresql://\?} )
|
96
|
+
expect( string ).to match( %r{\?.*user=a} )
|
97
|
+
expect( string ).to match( %r{\?.*password=b} )
|
98
|
+
expect( string ).to match( %r{\?.*host=localhost} )
|
99
|
+
expect( string ).to match( %r{\?.*port=555} )
|
100
|
+
expect( string ).to match( %r{\?.*dbname=x} )
|
101
|
+
end
|
102
|
+
|
103
|
+
it "can create a connection URI with a non-standard domain socket directory" do
|
104
|
+
string = described_class.parse_connect_args( 'postgresql://%2Fvar%2Flib%2Fpostgresql/dbname' )
|
105
|
+
|
106
|
+
expect( string ).to be_a( String )
|
107
|
+
expect( string ).to match( %r{^postgresql://%2Fvar%2Flib%2Fpostgresql/dbname} )
|
108
|
+
|
109
|
+
string = described_class.
|
110
|
+
parse_connect_args( 'postgresql:///dbname', :host => '/var/lib/postgresql' )
|
111
|
+
|
112
|
+
expect( string ).to be_a( String )
|
113
|
+
expect( string ).to match( %r{^postgresql:///dbname\?} )
|
114
|
+
expect( string ).to match( %r{\?.*host=%2Fvar%2Flib%2Fpostgresql} )
|
115
|
+
end
|
116
|
+
|
55
117
|
it "connects with defaults if no connection parameters are given" do
|
56
118
|
expect( described_class.parse_connect_args ).to eq( '' )
|
57
119
|
end
|
58
120
|
|
59
121
|
it "connects successfully with connection string" do
|
60
|
-
tmpconn = described_class.connect(@conninfo)
|
122
|
+
tmpconn = described_class.connect( @conninfo )
|
61
123
|
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
62
124
|
tmpconn.finish
|
63
125
|
end
|
64
126
|
|
65
127
|
it "connects using 7 arguments converted to strings" do
|
66
|
-
tmpconn = described_class.connect('localhost', @port, nil, nil, :test, nil, nil)
|
128
|
+
tmpconn = described_class.connect( 'localhost', @port, nil, nil, :test, nil, nil )
|
67
129
|
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
68
130
|
tmpconn.finish
|
69
131
|
end
|
@@ -89,8 +151,13 @@ describe PG::Connection do
|
|
89
151
|
|
90
152
|
it "raises an exception when connecting with an invalid number of arguments" do
|
91
153
|
expect {
|
92
|
-
described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'extra' )
|
93
|
-
}.to raise_error
|
154
|
+
described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'the-extra-arg' )
|
155
|
+
}.to raise_error do |error|
|
156
|
+
expect( error ).to be_an( ArgumentError )
|
157
|
+
expect( error.message ).to match( /extra positional parameter/i )
|
158
|
+
expect( error.message ).to match( /8/ )
|
159
|
+
expect( error.message ).to match( /the-extra-arg/ )
|
160
|
+
end
|
94
161
|
end
|
95
162
|
|
96
163
|
it "can connect asynchronously", :socket_io do
|
@@ -161,13 +228,14 @@ describe PG::Connection do
|
|
161
228
|
expect( @conn.db ).to eq( "test" )
|
162
229
|
expect( @conn.user ).to be_a_kind_of( String )
|
163
230
|
expect( @conn.pass ).to eq( "" )
|
164
|
-
expect( @conn.host ).to eq( "localhost" )
|
165
|
-
# TODO: Not sure why libpq returns a NULL ptr instead of "127.0.0.1"
|
166
|
-
expect( @conn.hostaddr ).to eq( nil ) if @conn.server_version >= 9_04_00
|
167
231
|
expect( @conn.port ).to eq( 54321 )
|
168
232
|
expect( @conn.tty ).to eq( "" )
|
169
233
|
expect( @conn.options ).to eq( "" )
|
170
234
|
end
|
235
|
+
it "can retrieve it's connection parameters for the established connection",
|
236
|
+
skip: RUBY_PLATFORM=~/x64-mingw/ ? "host segfaults on Windows-x64" : false do
|
237
|
+
expect( @conn.host ).to eq( "localhost" )
|
238
|
+
end
|
171
239
|
|
172
240
|
EXPECTED_TRACE_OUTPUT = %{
|
173
241
|
To backend> Msg Q
|
@@ -575,6 +643,31 @@ describe PG::Connection do
|
|
575
643
|
expect( @conn ).to still_be_usable
|
576
644
|
end
|
577
645
|
|
646
|
+
it "gracefully handle SQL statements while in #copy_data for input" do
|
647
|
+
@conn.exec "ROLLBACK"
|
648
|
+
@conn.transaction do
|
649
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
|
650
|
+
expect {
|
651
|
+
@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
652
|
+
@conn.exec "SELECT 1"
|
653
|
+
end
|
654
|
+
}.to raise_error(PG::Error, /no COPY in progress/)
|
655
|
+
end
|
656
|
+
expect( @conn ).to still_be_usable
|
657
|
+
end
|
658
|
+
|
659
|
+
it "gracefully handle SQL statements while in #copy_data for output" do
|
660
|
+
@conn.exec "ROLLBACK"
|
661
|
+
@conn.transaction do
|
662
|
+
expect {
|
663
|
+
@conn.copy_data( "COPY (VALUES(1), (2)) TO STDOUT" ) do |res|
|
664
|
+
@conn.exec "SELECT 3"
|
665
|
+
end
|
666
|
+
}.to raise_error(PG::Error, /no COPY in progress/)
|
667
|
+
end
|
668
|
+
expect( @conn ).to still_be_usable
|
669
|
+
end
|
670
|
+
|
578
671
|
it "should raise an error for non copy statements in #copy_data" do
|
579
672
|
expect {
|
580
673
|
@conn.copy_data( "SELECT 1" ){}
|
@@ -616,13 +709,13 @@ describe PG::Connection do
|
|
616
709
|
end
|
617
710
|
|
618
711
|
it "described_class#block should allow a timeout" do
|
619
|
-
@conn.send_query( "select pg_sleep(
|
712
|
+
@conn.send_query( "select pg_sleep(1)" )
|
620
713
|
|
621
714
|
start = Time.now
|
622
|
-
@conn.block( 0.
|
715
|
+
@conn.block( 0.3 )
|
623
716
|
finish = Time.now
|
624
717
|
|
625
|
-
expect( (finish - start) ).to be_within( 0.
|
718
|
+
expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
|
626
719
|
end
|
627
720
|
|
628
721
|
|
@@ -640,7 +733,7 @@ describe PG::Connection do
|
|
640
733
|
it "can return the default connection options as a Hash" do
|
641
734
|
expect( described_class.conndefaults_hash ).to be_a( Hash )
|
642
735
|
expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
|
643
|
-
expect(
|
736
|
+
expect( ['5432', '54321'] ).to include( described_class.conndefaults_hash[:port] )
|
644
737
|
expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
|
645
738
|
end
|
646
739
|
|
@@ -657,6 +750,25 @@ describe PG::Connection do
|
|
657
750
|
expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
|
658
751
|
end
|
659
752
|
|
753
|
+
describe "connection information related to SSL" do
|
754
|
+
|
755
|
+
it "can retrieve connection's ssl state", :postgresql_95 do
|
756
|
+
expect( @conn.ssl_in_use? ).to be false
|
757
|
+
end
|
758
|
+
|
759
|
+
it "can retrieve connection's ssl attribute_names", :postgresql_95 do
|
760
|
+
expect( @conn.ssl_attribute_names ).to be_a(Array)
|
761
|
+
end
|
762
|
+
|
763
|
+
it "can retrieve a single ssl connection attribute", :postgresql_95 do
|
764
|
+
expect( @conn.ssl_attribute('dbname') ).to eq( nil )
|
765
|
+
end
|
766
|
+
|
767
|
+
it "can retrieve all connection's ssl attributes", :postgresql_95 do
|
768
|
+
expect( @conn.ssl_attributes ).to be_a_kind_of( Hash )
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
660
772
|
|
661
773
|
it "honors the connect_timeout connection parameter", :postgresql_93 do
|
662
774
|
conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
|
@@ -1002,8 +1114,8 @@ describe PG::Connection do
|
|
1002
1114
|
res.check
|
1003
1115
|
first_row_time = Time.now unless first_row_time
|
1004
1116
|
end
|
1005
|
-
expect( (Time.now - start_time) ).to be >=
|
1006
|
-
expect( (first_row_time - start_time) ).to be <
|
1117
|
+
expect( (Time.now - start_time) ).to be >= 0.9
|
1118
|
+
expect( (first_row_time - start_time) ).to be < 0.9
|
1007
1119
|
end
|
1008
1120
|
|
1009
1121
|
it "should receive rows before entire query fails" do
|
@@ -1082,55 +1194,158 @@ describe PG::Connection do
|
|
1082
1194
|
end
|
1083
1195
|
|
1084
1196
|
it "uses the client encoding for escaped string" do
|
1085
|
-
original = "
|
1197
|
+
original = "Möhre to\0 escape".encode( "utf-16be" )
|
1086
1198
|
@conn.set_client_encoding( "euc_jp" )
|
1087
1199
|
escaped = @conn.escape( original )
|
1088
1200
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1089
|
-
expect( escaped ).to eq( "
|
1201
|
+
expect( escaped ).to eq( "Möhre to".encode(Encoding::EUC_JP) )
|
1090
1202
|
end
|
1091
1203
|
|
1092
1204
|
it "uses the client encoding for escaped literal", :postgresql_90 do
|
1093
|
-
original = "
|
1205
|
+
original = "Möhre to\0 escape".encode( "utf-16be" )
|
1094
1206
|
@conn.set_client_encoding( "euc_jp" )
|
1095
1207
|
escaped = @conn.escape_literal( original )
|
1096
1208
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1097
|
-
expect( escaped ).to eq( "'
|
1209
|
+
expect( escaped ).to eq( "'Möhre to'".encode(Encoding::EUC_JP) )
|
1098
1210
|
end
|
1099
1211
|
|
1100
1212
|
it "uses the client encoding for escaped identifier", :postgresql_90 do
|
1101
|
-
original = "
|
1213
|
+
original = "Möhre to\0 escape".encode( "utf-16le" )
|
1102
1214
|
@conn.set_client_encoding( "euc_jp" )
|
1103
1215
|
escaped = @conn.escape_identifier( original )
|
1104
1216
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1105
|
-
expect( escaped ).to eq( "\"
|
1217
|
+
expect( escaped ).to eq( "\"Möhre to\"".encode(Encoding::EUC_JP) )
|
1106
1218
|
end
|
1107
1219
|
|
1108
1220
|
it "uses the client encoding for quote_ident" do
|
1109
|
-
original = "
|
1221
|
+
original = "Möhre to\0 escape".encode( "utf-16le" )
|
1110
1222
|
@conn.set_client_encoding( "euc_jp" )
|
1111
1223
|
escaped = @conn.quote_ident( original )
|
1112
1224
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1113
|
-
expect( escaped ).to eq( "\"
|
1225
|
+
expect( escaped ).to eq( "\"Möhre to\"".encode(Encoding::EUC_JP) )
|
1114
1226
|
end
|
1115
1227
|
|
1116
1228
|
it "uses the previous string encoding for escaped string" do
|
1117
|
-
original = "
|
1229
|
+
original = "Möhre to\0 escape".encode( "iso-8859-1" )
|
1118
1230
|
@conn.set_client_encoding( "euc_jp" )
|
1119
1231
|
escaped = described_class.escape( original )
|
1120
1232
|
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
1121
|
-
expect( escaped ).to eq( "
|
1233
|
+
expect( escaped ).to eq( "Möhre to".encode(Encoding::ISO8859_1) )
|
1122
1234
|
end
|
1123
1235
|
|
1124
1236
|
it "uses the previous string encoding for quote_ident" do
|
1125
|
-
original = "
|
1237
|
+
original = "Möhre to\0 escape".encode( "iso-8859-1" )
|
1126
1238
|
@conn.set_client_encoding( "euc_jp" )
|
1127
1239
|
escaped = described_class.quote_ident( original )
|
1128
1240
|
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
1129
|
-
expect( escaped ).to eq( "\"
|
1241
|
+
expect( escaped.encode ).to eq( "\"Möhre to\"".encode(Encoding::ISO8859_1) )
|
1242
|
+
end
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
describe "respect and convert character encoding of input strings" do
|
1246
|
+
before :each do
|
1247
|
+
@conn.internal_encoding = __ENCODING__
|
1248
|
+
end
|
1249
|
+
|
1250
|
+
it "should convert query string and parameters to #exec_params" do
|
1251
|
+
r = @conn.exec_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
|
1252
|
+
['grün'.encode('utf-16be'), 'grün'.encode('iso-8859-1')])
|
1253
|
+
expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1254
|
+
end
|
1255
|
+
|
1256
|
+
it "should convert query string and parameters to #async_exec" do
|
1257
|
+
r = @conn.async_exec("VALUES( $1, $2, $1=$2, 'grün')".encode("cp936"),
|
1258
|
+
['grün'.encode('cp850'), 'grün'.encode('utf-16le')])
|
1259
|
+
expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
it "should convert query string to #exec" do
|
1263
|
+
r = @conn.exec("SELECT 'grün'".encode("utf-16be"))
|
1264
|
+
expect( r.values ).to eq( [['grün']] )
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
it "should convert query string to #async_exec" do
|
1268
|
+
r = @conn.async_exec("SELECT 'grün'".encode("utf-16le"))
|
1269
|
+
expect( r.values ).to eq( [['grün']] )
|
1130
1270
|
end
|
1131
1271
|
|
1272
|
+
it "should convert strings and parameters to #prepare and #exec_prepared" do
|
1273
|
+
@conn.prepare("weiß1".encode("utf-16be"), "VALUES( $1, $2, $1=$2, 'grün')".encode("cp850"))
|
1274
|
+
r = @conn.exec_prepared("weiß1".encode("utf-32le"),
|
1275
|
+
['grün'.encode('cp936'), 'grün'.encode('utf-16le')])
|
1276
|
+
expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
it "should convert strings to #describe_prepared" do
|
1280
|
+
@conn.prepare("weiß2", "VALUES(123)")
|
1281
|
+
r = @conn.describe_prepared("weiß2".encode("utf-16be"))
|
1282
|
+
expect( r.nfields ).to eq( 1 )
|
1283
|
+
end
|
1284
|
+
|
1285
|
+
it "should convert strings to #describe_portal" do
|
1286
|
+
@conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
|
1287
|
+
r = @conn.describe_portal("cörsör".encode("utf-16le"))
|
1288
|
+
expect( r.nfields ).to eq( 3 )
|
1289
|
+
end
|
1290
|
+
|
1291
|
+
it "should convert query string to #send_query" do
|
1292
|
+
@conn.send_query("VALUES('grün')".encode("utf-16be"))
|
1293
|
+
expect( @conn.get_last_result.values ).to eq( [['grün']] )
|
1294
|
+
end
|
1295
|
+
|
1296
|
+
it "should convert query string and parameters to #send_query" do
|
1297
|
+
@conn.send_query("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
|
1298
|
+
['grün'.encode('utf-32be'), 'grün'.encode('iso-8859-1')])
|
1299
|
+
expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1300
|
+
end
|
1301
|
+
|
1302
|
+
it "should convert strings and parameters to #send_prepare and #send_query_prepared" do
|
1303
|
+
@conn.send_prepare("weiß3".encode("iso-8859-1"), "VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16be"))
|
1304
|
+
@conn.get_last_result
|
1305
|
+
@conn.send_query_prepared("weiß3".encode("utf-32le"),
|
1306
|
+
['grün'.encode('utf-16le'), 'grün'.encode('iso-8859-1')])
|
1307
|
+
expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1308
|
+
end
|
1309
|
+
|
1310
|
+
it "should convert strings to #send_describe_prepared" do
|
1311
|
+
@conn.prepare("weiß4", "VALUES(123)")
|
1312
|
+
@conn.send_describe_prepared("weiß4".encode("utf-16be"))
|
1313
|
+
expect( @conn.get_last_result.nfields ).to eq( 1 )
|
1314
|
+
end
|
1315
|
+
|
1316
|
+
it "should convert strings to #send_describe_portal" do
|
1317
|
+
@conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
|
1318
|
+
@conn.send_describe_portal("cörsör".encode("utf-16le"))
|
1319
|
+
expect( @conn.get_last_result.nfields ).to eq( 3 )
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
it "should convert error string to #put_copy_end" do
|
1323
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1324
|
+
@conn.exec( "COPY copytable FROM STDIN" )
|
1325
|
+
@conn.put_copy_end("grün".encode("utf-16be"))
|
1326
|
+
expect( @conn.get_result.error_message ).to match(/grün/)
|
1327
|
+
@conn.get_result
|
1328
|
+
end
|
1132
1329
|
end
|
1133
1330
|
|
1331
|
+
it "can quote bigger strings with quote_ident" do
|
1332
|
+
original = "'01234567\"" * 100
|
1333
|
+
escaped = described_class.quote_ident( original + "\0afterzero" )
|
1334
|
+
expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
|
1335
|
+
end
|
1336
|
+
|
1337
|
+
it "can quote Arrays with quote_ident" do
|
1338
|
+
original = "'01234567\""
|
1339
|
+
escaped = described_class.quote_ident( [original]*3 )
|
1340
|
+
expected = ["\"" + original.gsub("\"", "\"\"") + "\""] * 3
|
1341
|
+
expect( escaped ).to eq( expected.join(".") )
|
1342
|
+
end
|
1343
|
+
|
1344
|
+
it "will raise a TypeError for invalid arguments to quote_ident" do
|
1345
|
+
expect{ described_class.quote_ident( nil ) }.to raise_error(TypeError)
|
1346
|
+
expect{ described_class.quote_ident( [nil] ) }.to raise_error(TypeError)
|
1347
|
+
expect{ described_class.quote_ident( [['a']] ) }.to raise_error(TypeError)
|
1348
|
+
end
|
1134
1349
|
|
1135
1350
|
describe "Ruby 1.9.x default_internal encoding" do
|
1136
1351
|
|
@@ -1143,12 +1358,12 @@ describe PG::Connection do
|
|
1143
1358
|
|
1144
1359
|
begin
|
1145
1360
|
prev_encoding = Encoding.default_internal
|
1146
|
-
Encoding.default_internal = Encoding::
|
1361
|
+
Encoding.default_internal = Encoding::ISO8859_2
|
1147
1362
|
|
1148
1363
|
conn = PG.connect( @conninfo )
|
1149
|
-
expect( conn.internal_encoding ).to eq( Encoding::
|
1364
|
+
expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
|
1150
1365
|
res = conn.exec( "SELECT foo FROM defaultinternaltest" )
|
1151
|
-
expect( res[0]['foo'].encoding ).to eq( Encoding::
|
1366
|
+
expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
|
1152
1367
|
ensure
|
1153
1368
|
conn.exec( "DROP TABLE defaultinternaltest" )
|
1154
1369
|
conn.finish if conn
|
@@ -1378,15 +1593,15 @@ describe PG::Connection do
|
|
1378
1593
|
end
|
1379
1594
|
end
|
1380
1595
|
|
1381
|
-
it "can process #copy_data input queries with row encoder" do
|
1596
|
+
it "can process #copy_data input queries with row encoder and respects character encoding" do
|
1382
1597
|
@conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1383
1598
|
res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
1384
1599
|
@conn2.put_copy_data [1]
|
1385
|
-
@conn2.put_copy_data ["
|
1600
|
+
@conn2.put_copy_data ["Möhre".encode("utf-16le")]
|
1386
1601
|
end
|
1387
1602
|
|
1388
1603
|
res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
|
1389
|
-
expect( res.values ).to eq( [["1"], ["
|
1604
|
+
expect( res.values ).to eq( [["1"], ["Möhre"]] )
|
1390
1605
|
end
|
1391
1606
|
end
|
1392
1607
|
|
@@ -1428,14 +1643,16 @@ describe PG::Connection do
|
|
1428
1643
|
end
|
1429
1644
|
end
|
1430
1645
|
|
1431
|
-
it "can process #copy_data output with row decoder" do
|
1646
|
+
it "can process #copy_data output with row decoder and respects character encoding" do
|
1647
|
+
@conn2.internal_encoding = Encoding::ISO8859_1
|
1432
1648
|
rows = []
|
1433
|
-
res2 = @conn2.copy_data( "COPY (
|
1649
|
+
res2 = @conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
|
1434
1650
|
while row=@conn2.get_copy_data
|
1435
1651
|
rows << row
|
1436
1652
|
end
|
1437
1653
|
end
|
1438
|
-
expect( rows ).to eq(
|
1654
|
+
expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
|
1655
|
+
expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
|
1439
1656
|
end
|
1440
1657
|
|
1441
1658
|
it "can type cast #copy_data output with explicit decoder" do
|