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.
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