pg 0.18.1 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
- Time.new $1.to_i, $2.to_i, $3.to_i
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}:00"
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
 
@@ -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 " [%d] {%s} %s -- %s" % row.values_at( 'pid', 'state', 'application_name', 'query' )
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
- 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 )
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, Time.new(2013,6,30), 4 ],
62
- [ 1, 'a', 2.0, true, Time.new(2013,6,30), 5 ],
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( Time.new(2113, 12, 31) )
163
- expect( res.getvalue(0,1) ).to eq( Time.new(1913, 12, 31) )
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
@@ -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( ArgumentError, /extra positional parameter/i )
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(3)" )
712
+ @conn.send_query( "select pg_sleep(1)" )
620
713
 
621
714
  start = Time.now
622
- @conn.block( 0.1 )
715
+ @conn.block( 0.3 )
623
716
  finish = Time.now
624
717
 
625
- expect( (finish - start) ).to be_within( 0.05 ).of( 0.1 )
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( described_class.conndefaults_hash[:port] ).to eq( '54321' )
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 >= 1.0
1006
- expect( (first_row_time - start_time) ).to be < 1.0
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 = "string to\0 escape".force_encoding( "iso8859-1" )
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( "string to" )
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 = "string to\0 escape".force_encoding( "iso8859-1" )
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( "'string to'" )
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 = "string to\0 escape".force_encoding( "iso8859-1" )
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( "\"string to\"" )
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 = "string to\0 escape".force_encoding( "iso8859-1" )
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( "\"string to\"" )
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 = "string to\0 escape".force_encoding( "iso8859-1" )
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( "string to" )
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 = "string to\0 escape".force_encoding( "iso8859-1" )
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( "\"string to\"" )
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::UTF_8
1361
+ Encoding.default_internal = Encoding::ISO8859_2
1147
1362
 
1148
1363
  conn = PG.connect( @conninfo )
1149
- expect( conn.internal_encoding ).to eq( Encoding::UTF_8 )
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::UTF_8 )
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 ["2"]
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"], ["2"]] )
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 (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
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( [["1"], ["2"]] )
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