autocompl 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/lib/autocompl/repository.rb +11 -1
  3. data/lib/autocompl/version.rb +1 -1
  4. data/test/dummy/log/development.log +467 -0
  5. data/test/dummy/log/test.log +3 -0
  6. data/test/dummy/vendor/bundle/ruby/2.3.0/bin/console +23 -0
  7. data/test/dummy/vendor/bundle/ruby/2.3.0/cache/pg-0.19.0.gem +0 -0
  8. data/test/dummy/vendor/bundle/ruby/2.3.0/cache/pq-0.0.1.gem +0 -0
  9. data/test/dummy/vendor/bundle/ruby/2.3.0/extensions/x86_64-darwin-15/2.3.0-static/pg-0.19.0/gem.build_complete +0 -0
  10. data/test/dummy/vendor/bundle/ruby/2.3.0/extensions/x86_64-darwin-15/2.3.0-static/pg-0.19.0/gem_make.out +78 -0
  11. data/test/dummy/vendor/bundle/ruby/2.3.0/extensions/x86_64-darwin-15/2.3.0-static/pg-0.19.0/mkmf.log +1346 -0
  12. data/test/dummy/vendor/bundle/ruby/2.3.0/extensions/x86_64-darwin-15/2.3.0-static/pg-0.19.0/pg_ext.bundle +0 -0
  13. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/BSDL +22 -0
  14. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ChangeLog +6378 -0
  15. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/Contributors.rdoc +46 -0
  16. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/History.rdoc +363 -0
  17. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/LICENSE +56 -0
  18. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/Manifest.txt +85 -0
  19. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/POSTGRES +23 -0
  20. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/README-OS_X.rdoc +68 -0
  21. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/README-Windows.rdoc +56 -0
  22. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/README.ja.rdoc +14 -0
  23. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/README.rdoc +168 -0
  24. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/Rakefile +216 -0
  25. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/Rakefile.cross +301 -0
  26. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/Makefile +261 -0
  27. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/errorcodes.def +947 -0
  28. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/errorcodes.rb +45 -0
  29. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/errorcodes.txt +467 -0
  30. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/extconf.h +38 -0
  31. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/extconf.rb +112 -0
  32. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/gvl_wrappers.c +13 -0
  33. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/gvl_wrappers.h +257 -0
  34. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/gvl_wrappers.o +0 -0
  35. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg.c +667 -0
  36. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg.h +395 -0
  37. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg.o +0 -0
  38. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_binary_decoder.c +162 -0
  39. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_binary_decoder.o +0 -0
  40. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_binary_encoder.c +162 -0
  41. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_binary_encoder.o +0 -0
  42. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_coder.c +500 -0
  43. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_coder.o +0 -0
  44. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_connection.c +4102 -0
  45. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_connection.o +0 -0
  46. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_copy_coder.c +591 -0
  47. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_copy_coder.o +0 -0
  48. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_errors.c +95 -0
  49. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_errors.o +0 -0
  50. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_ext.bundle +0 -0
  51. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_result.c +1271 -0
  52. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_result.o +0 -0
  53. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_text_decoder.c +421 -0
  54. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_text_decoder.o +0 -0
  55. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_text_encoder.c +683 -0
  56. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_text_encoder.o +0 -0
  57. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map.c +159 -0
  58. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map.o +0 -0
  59. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map_all_strings.c +116 -0
  60. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map_all_strings.o +0 -0
  61. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map_by_class.c +239 -0
  62. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map_by_class.o +0 -0
  63. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map_by_column.c +312 -0
  64. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map_by_column.o +0 -0
  65. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map_by_mri_type.c +284 -0
  66. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map_by_mri_type.o +0 -0
  67. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map_by_oid.c +355 -0
  68. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map_by_oid.o +0 -0
  69. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map_in_ruby.c +299 -0
  70. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/pg_type_map_in_ruby.o +0 -0
  71. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/util.c +149 -0
  72. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/util.h +65 -0
  73. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/util.o +0 -0
  74. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/vc/pg.sln +26 -0
  75. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/vc/pg_18/pg.vcproj +216 -0
  76. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/ext/vc/pg_19/pg_19.vcproj +209 -0
  77. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/lib/pg.rb +64 -0
  78. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/lib/pg/basic_type_mapping.rb +426 -0
  79. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/lib/pg/coder.rb +83 -0
  80. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/lib/pg/connection.rb +271 -0
  81. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/lib/pg/constants.rb +11 -0
  82. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/lib/pg/exceptions.rb +11 -0
  83. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/lib/pg/result.rb +30 -0
  84. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/lib/pg/text_decoder.rb +51 -0
  85. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/lib/pg/text_encoder.rb +35 -0
  86. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/lib/pg/type_map_by_column.rb +15 -0
  87. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/lib/pg_ext.bundle +0 -0
  88. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/array_insert.rb +20 -0
  89. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/async_api.rb +106 -0
  90. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/async_copyto.rb +39 -0
  91. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/async_mixed.rb +56 -0
  92. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/check_conn.rb +21 -0
  93. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/copyfrom.rb +81 -0
  94. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/copyto.rb +19 -0
  95. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/cursor.rb +21 -0
  96. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/disk_usage_report.rb +186 -0
  97. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/issue-119.rb +94 -0
  98. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/losample.rb +69 -0
  99. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/minimal-testcase.rb +17 -0
  100. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/notify_wait.rb +72 -0
  101. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/pg_statistics.rb +294 -0
  102. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/replication_monitor.rb +231 -0
  103. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/test_binary_values.rb +33 -0
  104. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/wal_shipper.rb +434 -0
  105. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/sample/warehouse_partitions.rb +320 -0
  106. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/data/expected_trace.out +26 -0
  107. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/data/random_binary_data +0 -0
  108. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/helpers.rb +352 -0
  109. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/pg/basic_type_mapping_spec.rb +305 -0
  110. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/pg/connection_spec.rb +1676 -0
  111. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/pg/result_spec.rb +449 -0
  112. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/pg/type_map_by_class_spec.rb +138 -0
  113. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/pg/type_map_by_column_spec.rb +222 -0
  114. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/pg/type_map_by_mri_type_spec.rb +136 -0
  115. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/pg/type_map_by_oid_spec.rb +149 -0
  116. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/pg/type_map_in_ruby_spec.rb +164 -0
  117. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/pg/type_map_spec.rb +22 -0
  118. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/pg/type_spec.rb +777 -0
  119. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pg-0.19.0/spec/pg_spec.rb +50 -0
  120. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pq-0.0.1/Gemfile +4 -0
  121. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pq-0.0.1/LICENSE.txt +22 -0
  122. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pq-0.0.1/README.md +76 -0
  123. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pq-0.0.1/Rakefile +1 -0
  124. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pq-0.0.1/bin/console +7 -0
  125. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pq-0.0.1/lib/pq.rb +99 -0
  126. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pq-0.0.1/pq.gemspec +29 -0
  127. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pq-0.0.1/spec/helpers.rb +10 -0
  128. data/test/dummy/vendor/bundle/ruby/2.3.0/gems/pq-0.0.1/spec/queue_spec.rb +84 -0
  129. data/test/dummy/vendor/bundle/ruby/2.3.0/specifications/pg-0.19.0.gemspec +63 -0
  130. data/test/dummy/vendor/bundle/ruby/2.3.0/specifications/pq-0.0.1.gemspec +49 -0
  131. metadata +253 -1
@@ -0,0 +1,305 @@
1
+ #!/usr/bin/env rspec
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'pg'
7
+
8
+ describe 'Basic type mapping' do
9
+
10
+ describe PG::BasicTypeMapForQueries do
11
+ let!(:basic_type_mapping) do
12
+ PG::BasicTypeMapForQueries.new @conn
13
+ end
14
+
15
+ #
16
+ # Encoding Examples
17
+ #
18
+
19
+ it "should do basic param encoding", :ruby_19 do
20
+ res = @conn.exec_params( "SELECT $1::int8,$2::float,$3,$4::TEXT",
21
+ [1, 2.1, true, "b"], nil, basic_type_mapping )
22
+
23
+ expect( res.values ).to eq( [
24
+ [ "1", "2.1", "t", "b" ],
25
+ ] )
26
+
27
+ expect( result_typenames(res) ).to eq( ['bigint', 'double precision', 'boolean', 'text'] )
28
+ end
29
+
30
+ it "should do array param encoding" do
31
+ res = @conn.exec_params( "SELECT $1,$2,$3,$4", [
32
+ [1, 2, 3], [[1, 2], [3, nil]],
33
+ [1.11, 2.21],
34
+ ['/,"'.gsub("/", "\\"), nil, 'abcäöü'],
35
+ ], nil, basic_type_mapping )
36
+
37
+ expect( res.values ).to eq( [[
38
+ '{1,2,3}', '{{1,2},{3,NULL}}',
39
+ '{1.11,2.21}',
40
+ '{"//,/"",NULL,abcäöü}'.gsub("/", "\\"),
41
+ ]] )
42
+
43
+ expect( result_typenames(res) ).to eq( ['bigint[]', 'bigint[]', 'double precision[]', 'text[]'] )
44
+ end
45
+ end
46
+
47
+
48
+
49
+ describe PG::BasicTypeMapForResults do
50
+ let!(:basic_type_mapping) do
51
+ PG::BasicTypeMapForResults.new @conn
52
+ end
53
+
54
+ #
55
+ # Decoding Examples
56
+ #
57
+
58
+ it "should do OID based type conversions", :ruby_19 do
59
+ res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, TRUE, '2013-06-30'::DATE, generate_series(4,5)" )
60
+ expect( res.map_types!(basic_type_mapping).values ).to eq( [
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
+ ] )
64
+ end
65
+
66
+ #
67
+ # Decoding Examples text+binary format converters
68
+ #
69
+
70
+ describe "connection wide type mapping" do
71
+ before :each do
72
+ @conn.type_map_for_results = basic_type_mapping
73
+ end
74
+
75
+ after :each do
76
+ @conn.type_map_for_results = PG::TypeMapAllStrings.new
77
+ end
78
+
79
+ it "should do boolean type conversions" do
80
+ [1, 0].each do |format|
81
+ res = @conn.exec( "SELECT true::BOOLEAN, false::BOOLEAN, NULL::BOOLEAN", [], format )
82
+ expect( res.values ).to eq( [[true, false, nil]] )
83
+ end
84
+ end
85
+
86
+ it "should do binary type conversions" do
87
+ [1, 0].each do |format|
88
+ res = @conn.exec( "SELECT E'\\\\000\\\\377'::BYTEA", [], format )
89
+ expect( res.values ).to eq( [[["00ff"].pack("H*")]] )
90
+ expect( res.values[0][0].encoding ).to eq( Encoding::ASCII_8BIT ) if Object.const_defined? :Encoding
91
+ end
92
+ end
93
+
94
+ it "should do integer type conversions" do
95
+ [1, 0].each do |format|
96
+ res = @conn.exec( "SELECT -8999::INT2, -899999999::INT4, -8999999999999999999::INT8", [], format )
97
+ expect( res.values ).to eq( [[-8999, -899999999, -8999999999999999999]] )
98
+ end
99
+ end
100
+
101
+ it "should do string type conversions" do
102
+ @conn.internal_encoding = 'utf-8' if Object.const_defined? :Encoding
103
+ [1, 0].each do |format|
104
+ res = @conn.exec( "SELECT 'abcäöü'::TEXT", [], format )
105
+ expect( res.values ).to eq( [['abcäöü']] )
106
+ expect( res.values[0][0].encoding ).to eq( Encoding::UTF_8 ) if Object.const_defined? :Encoding
107
+ end
108
+ end
109
+
110
+ it "should do float type conversions" do
111
+ [1, 0].each do |format|
112
+ res = @conn.exec( "SELECT -8.999e3::FLOAT4,
113
+ 8.999e10::FLOAT4,
114
+ -8999999999e-99::FLOAT8,
115
+ NULL::FLOAT4,
116
+ 'NaN'::FLOAT4,
117
+ 'Infinity'::FLOAT4,
118
+ '-Infinity'::FLOAT4
119
+ ", [], format )
120
+ expect( res.getvalue(0,0) ).to be_within(1e-2).of(-8.999e3)
121
+ expect( res.getvalue(0,1) ).to be_within(1e5).of(8.999e10)
122
+ expect( res.getvalue(0,2) ).to be_within(1e-109).of(-8999999999e-99)
123
+ expect( res.getvalue(0,3) ).to be_nil
124
+ expect( res.getvalue(0,4) ).to be_nan
125
+ expect( res.getvalue(0,5) ).to eq( Float::INFINITY )
126
+ expect( res.getvalue(0,6) ).to eq( -Float::INFINITY )
127
+ end
128
+ end
129
+
130
+ it "should do datetime without time zone type conversions" do
131
+ [0].each do |format|
132
+ res = @conn.exec( "SELECT CAST('2013-12-31 23:58:59+02' AS TIMESTAMP WITHOUT TIME ZONE),
133
+ CAST('1913-12-31 23:58:59.123-03' AS TIMESTAMP WITHOUT TIME ZONE),
134
+ CAST('infinity' AS TIMESTAMP WITHOUT TIME ZONE),
135
+ CAST('-infinity' AS TIMESTAMP WITHOUT TIME ZONE)", [], format )
136
+ expect( res.getvalue(0,0) ).to eq( Time.new(2013, 12, 31, 23, 58, 59) )
137
+ expect( res.getvalue(0,1) ).to be_within(1e-3).of(Time.new(1913, 12, 31, 23, 58, 59.123))
138
+ expect( res.getvalue(0,2) ).to eq( 'infinity' )
139
+ expect( res.getvalue(0,3) ).to eq( '-infinity' )
140
+ end
141
+ end
142
+
143
+ it "should do datetime with time zone type conversions" do
144
+ [0].each do |format|
145
+ res = @conn.exec( "SELECT CAST('2013-12-31 23:58:59+02' AS TIMESTAMP WITH TIME ZONE),
146
+ CAST('1913-12-31 23:58:59.123-03' AS TIMESTAMP WITH TIME ZONE),
147
+ CAST('infinity' AS TIMESTAMP WITH TIME ZONE),
148
+ CAST('-infinity' AS TIMESTAMP WITH TIME ZONE)", [], format )
149
+ expect( res.getvalue(0,0) ).to eq( Time.new(2013, 12, 31, 23, 58, 59, "+02:00") )
150
+ expect( res.getvalue(0,1) ).to be_within(1e-3).of(Time.new(1913, 12, 31, 23, 58, 59.123, "-03:00"))
151
+ expect( res.getvalue(0,2) ).to eq( 'infinity' )
152
+ expect( res.getvalue(0,3) ).to eq( '-infinity' )
153
+ end
154
+ end
155
+
156
+ it "should do date type conversions" do
157
+ [0].each do |format|
158
+ res = @conn.exec( "SELECT CAST('2113-12-31' AS DATE),
159
+ CAST('1913-12-31' AS DATE),
160
+ CAST('infinity' AS DATE),
161
+ CAST('-infinity' AS DATE)", [], format )
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
+ expect( res.getvalue(0,2) ).to eq( 'infinity' )
165
+ expect( res.getvalue(0,3) ).to eq( '-infinity' )
166
+ end
167
+ end
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
+
190
+ it "should do array type conversions" do
191
+ [0].each do |format|
192
+ res = @conn.exec( "SELECT CAST('{1,2,3}' AS INT2[]), CAST('{{1,2},{3,4}}' AS INT2[][]),
193
+ CAST('{1,2,3}' AS INT4[]),
194
+ CAST('{1,2,3}' AS INT8[]),
195
+ CAST('{1,2,3}' AS TEXT[]),
196
+ CAST('{1,2,3}' AS VARCHAR[]),
197
+ CAST('{1,2,3}' AS FLOAT4[]),
198
+ CAST('{1,2,3}' AS FLOAT8[])
199
+ ", [], format )
200
+ expect( res.getvalue(0,0) ).to eq( [1,2,3] )
201
+ expect( res.getvalue(0,1) ).to eq( [[1,2],[3,4]] )
202
+ expect( res.getvalue(0,2) ).to eq( [1,2,3] )
203
+ expect( res.getvalue(0,3) ).to eq( [1,2,3] )
204
+ expect( res.getvalue(0,4) ).to eq( ['1','2','3'] )
205
+ expect( res.getvalue(0,5) ).to eq( ['1','2','3'] )
206
+ expect( res.getvalue(0,6) ).to eq( [1.0,2.0,3.0] )
207
+ expect( res.getvalue(0,7) ).to eq( [1.0,2.0,3.0] )
208
+ end
209
+ end
210
+ end
211
+
212
+ context "with usage of result oids for copy decoder selection" do
213
+ it "can type cast #copy_data output with explicit decoder" do
214
+ @conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
215
+ @conn.exec( "INSERT INTO copytable VALUES ('a', 123, '{5,4,3}'), ('b', 234, '{2,3}')" )
216
+
217
+ # Retrieve table OIDs per empty result.
218
+ res = @conn.exec( "SELECT * FROM copytable LIMIT 0" )
219
+ tm = basic_type_mapping.build_column_map( res )
220
+ row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
221
+
222
+ rows = []
223
+ @conn.copy_data( "COPY copytable TO STDOUT", row_decoder ) do |res|
224
+ while row=@conn.get_copy_data
225
+ rows << row
226
+ end
227
+ end
228
+ expect( rows ).to eq( [['a', 123, [5,4,3]], ['b', 234, [2,3]]] )
229
+ end
230
+ end
231
+ end
232
+
233
+
234
+ describe PG::BasicTypeMapBasedOnResult do
235
+ let!(:basic_type_mapping) do
236
+ PG::BasicTypeMapBasedOnResult.new @conn
237
+ end
238
+
239
+ context "with usage of result oids for bind params encoder selection" do
240
+ it "can type cast query params" do
241
+ @conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
242
+
243
+ # Retrieve table OIDs per empty result.
244
+ res = @conn.exec( "SELECT * FROM copytable LIMIT 0" )
245
+ tm = basic_type_mapping.build_column_map( res )
246
+
247
+ @conn.exec_params( "INSERT INTO copytable VALUES ($1, $2, $3)", ['a', 123, [5,4,3]], 0, tm )
248
+ @conn.exec_params( "INSERT INTO copytable VALUES ($1, $2, $3)", ['b', 234, [2,3]], 0, tm )
249
+ res = @conn.exec( "SELECT * FROM copytable" )
250
+ expect( res.values ).to eq( [['a', '123', '{5,4,3}'], ['b', '234', '{2,3}']] )
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
285
+ end
286
+
287
+ context "with usage of result oids for copy encoder selection" do
288
+ it "can type cast #copy_data input with explicit encoder" do
289
+ @conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
290
+
291
+ # Retrieve table OIDs per empty result set.
292
+ res = @conn.exec( "SELECT * FROM copytable LIMIT 0" )
293
+ tm = basic_type_mapping.build_column_map( res )
294
+ row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
295
+
296
+ @conn.copy_data( "COPY copytable FROM STDIN", row_encoder ) do |res|
297
+ @conn.put_copy_data ['a', 123, [5,4,3]]
298
+ @conn.put_copy_data ['b', 234, [2,3]]
299
+ end
300
+ res = @conn.exec( "SELECT * FROM copytable" )
301
+ expect( res.values ).to eq( [['a', '123', '{5,4,3}'], ['b', '234', '{2,3}']] )
302
+ end
303
+ end
304
+ end
305
+ end
@@ -0,0 +1,1676 @@
1
+ #!/usr/bin/env rspec
2
+ #encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'timeout'
7
+ require 'socket'
8
+ require 'pg'
9
+
10
+ describe PG::Connection do
11
+
12
+ it "can create a connection option string from a Hash of options" do
13
+ optstring = described_class.parse_connect_args(
14
+ :host => 'pgsql.example.com',
15
+ :dbname => 'db01',
16
+ 'sslmode' => 'require'
17
+ )
18
+
19
+ expect( optstring ).to be_a( String )
20
+ expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
21
+ expect( optstring ).to match( /(^|\s)dbname='db01'/ )
22
+ expect( optstring ).to match( /(^|\s)sslmode='require'/ )
23
+ end
24
+
25
+ it "can create a connection option string from positional parameters" do
26
+ optstring = described_class.parse_connect_args( 'pgsql.example.com', nil, '-c geqo=off', nil,
27
+ 'sales' )
28
+
29
+ expect( optstring ).to be_a( String )
30
+ expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
31
+ expect( optstring ).to match( /(^|\s)dbname='sales'/ )
32
+ expect( optstring ).to match( /(^|\s)options='-c geqo=off'/ )
33
+
34
+ expect( optstring ).to_not match( /port=/ )
35
+ expect( optstring ).to_not match( /tty=/ )
36
+ end
37
+
38
+ it "can create a connection option string from a mix of positional and hash parameters" do
39
+ optstring = described_class.parse_connect_args( 'pgsql.example.com',
40
+ :dbname => 'licensing', :user => 'jrandom' )
41
+
42
+ expect( optstring ).to be_a( String )
43
+ expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
44
+ expect( optstring ).to match( /(^|\s)dbname='licensing'/ )
45
+ expect( optstring ).to match( /(^|\s)user='jrandom'/ )
46
+ end
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
+
56
+ it "escapes single quotes and backslashes in connection parameters" do
57
+ expect(
58
+ described_class.parse_connect_args( "DB 'browser' \\" )
59
+ ).to match( /host='DB \\'browser\\' \\\\'/ )
60
+
61
+ end
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
+
117
+ it "connects with defaults if no connection parameters are given" do
118
+ expect( described_class.parse_connect_args ).to eq( '' )
119
+ end
120
+
121
+ it "connects successfully with connection string" do
122
+ tmpconn = described_class.connect( @conninfo )
123
+ expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
124
+ tmpconn.finish
125
+ end
126
+
127
+ it "connects using 7 arguments converted to strings" do
128
+ tmpconn = described_class.connect( 'localhost', @port, nil, nil, :test, nil, nil )
129
+ expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
130
+ tmpconn.finish
131
+ end
132
+
133
+ it "connects using a hash of connection parameters" do
134
+ tmpconn = described_class.connect(
135
+ :host => 'localhost',
136
+ :port => @port,
137
+ :dbname => :test)
138
+ expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
139
+ tmpconn.finish
140
+ end
141
+
142
+ it "connects using a hash of optional connection parameters", :postgresql_90 do
143
+ tmpconn = described_class.connect(
144
+ :host => 'localhost',
145
+ :port => @port,
146
+ :dbname => :test,
147
+ :keepalives => 1)
148
+ expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
149
+ tmpconn.finish
150
+ end
151
+
152
+ it "raises an exception when connecting with an invalid number of arguments" do
153
+ expect {
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
161
+ end
162
+
163
+ it "can connect asynchronously", :socket_io do
164
+ tmpconn = described_class.connect_start( @conninfo )
165
+ expect( tmpconn ).to be_a( described_class )
166
+ socket = tmpconn.socket_io
167
+ status = tmpconn.connect_poll
168
+
169
+ while status != PG::PGRES_POLLING_OK
170
+ if status == PG::PGRES_POLLING_READING
171
+ select( [socket], [], [], 5.0 ) or
172
+ raise "Asynchronous connection timed out!"
173
+
174
+ elsif status == PG::PGRES_POLLING_WRITING
175
+ select( [], [socket], [], 5.0 ) or
176
+ raise "Asynchronous connection timed out!"
177
+ end
178
+ status = tmpconn.connect_poll
179
+ end
180
+
181
+ expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
182
+ tmpconn.finish
183
+ end
184
+
185
+ it "can connect asynchronously for the duration of a block", :socket_io do
186
+ conn = nil
187
+
188
+ described_class.connect_start(@conninfo) do |tmpconn|
189
+ expect( tmpconn ).to be_a( described_class )
190
+ conn = tmpconn
191
+ socket = tmpconn.socket_io
192
+ status = tmpconn.connect_poll
193
+
194
+ while status != PG::PGRES_POLLING_OK
195
+ if status == PG::PGRES_POLLING_READING
196
+ if(not select([socket],[],[],5.0))
197
+ raise "Asynchronous connection timed out!"
198
+ end
199
+ elsif(status == PG::PGRES_POLLING_WRITING)
200
+ if(not select([],[socket],[],5.0))
201
+ raise "Asynchronous connection timed out!"
202
+ end
203
+ end
204
+ status = tmpconn.connect_poll
205
+ end
206
+
207
+ expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
208
+ end
209
+
210
+ expect( conn ).to be_finished()
211
+ end
212
+
213
+ it "raises proper error when sending fails" do
214
+ conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
215
+ expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
216
+ end
217
+
218
+ it "doesn't leave stale server connections after finish" do
219
+ described_class.connect(@conninfo).finish
220
+ sleep 0.5
221
+ res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
222
+ WHERE usename IS NOT NULL])
223
+ # there's still the global @conn, but should be no more
224
+ expect( res[0]['n'] ).to eq( '1' )
225
+ end
226
+
227
+ it "can retrieve it's connection parameters for the established connection" do
228
+ expect( @conn.db ).to eq( "test" )
229
+ expect( @conn.user ).to be_a_kind_of( String )
230
+ expect( @conn.pass ).to eq( "" )
231
+ expect( @conn.port ).to eq( 54321 )
232
+ expect( @conn.tty ).to eq( "" )
233
+ expect( @conn.options ).to eq( "" )
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
239
+
240
+ EXPECTED_TRACE_OUTPUT = %{
241
+ To backend> Msg Q
242
+ To backend> "SELECT 1 AS one"
243
+ To backend> Msg complete, length 21
244
+ From backend> T
245
+ From backend (#4)> 28
246
+ From backend (#2)> 1
247
+ From backend> "one"
248
+ From backend (#4)> 0
249
+ From backend (#2)> 0
250
+ From backend (#4)> 23
251
+ From backend (#2)> 4
252
+ From backend (#4)> -1
253
+ From backend (#2)> 0
254
+ From backend> D
255
+ From backend (#4)> 11
256
+ From backend (#2)> 1
257
+ From backend (#4)> 1
258
+ From backend (1)> 1
259
+ From backend> C
260
+ From backend (#4)> 13
261
+ From backend> "SELECT 1"
262
+ From backend> Z
263
+ From backend (#4)> 5
264
+ From backend> Z
265
+ From backend (#4)> 5
266
+ From backend> T
267
+ }.gsub( /^\t{2}/, '' ).lstrip
268
+
269
+ it "trace and untrace client-server communication", :unix do
270
+ # be careful to explicitly close files so that the
271
+ # directory can be removed and we don't have to wait for
272
+ # the GC to run.
273
+ trace_file = TEST_DIRECTORY + "test_trace.out"
274
+ trace_io = trace_file.open( 'w', 0600 )
275
+ @conn.trace( trace_io )
276
+ trace_io.close
277
+
278
+ res = @conn.exec("SELECT 1 AS one")
279
+ @conn.untrace
280
+
281
+ res = @conn.exec("SELECT 2 AS two")
282
+
283
+ trace_data = trace_file.read
284
+
285
+ expected_trace_output = EXPECTED_TRACE_OUTPUT.dup
286
+ # For PostgreSQL < 9.0, the output will be different:
287
+ # -From backend (#4)> 13
288
+ # -From backend> "SELECT 1"
289
+ # +From backend (#4)> 11
290
+ # +From backend> "SELECT"
291
+ if @conn.server_version < 90000
292
+ expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
293
+ expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
294
+ end
295
+
296
+ expect( trace_data ).to eq( expected_trace_output )
297
+ end
298
+
299
+ it "allows a query to be cancelled" do
300
+ error = false
301
+ @conn.send_query("SELECT pg_sleep(1000)")
302
+ @conn.cancel
303
+ tmpres = @conn.get_result
304
+ if(tmpres.result_status != PG::PGRES_TUPLES_OK)
305
+ error = true
306
+ end
307
+ expect( error ).to eq( true )
308
+ end
309
+
310
+ it "can stop a thread that runs a blocking query with async_exec" do
311
+ pending "this does not work on Rubinius" if RUBY_ENGINE=='rbx'
312
+
313
+ start = Time.now
314
+ t = Thread.new do
315
+ @conn.async_exec( 'select pg_sleep(10)' )
316
+ end
317
+ sleep 0.1
318
+
319
+ t.kill
320
+ t.join
321
+ expect( (Time.now - start) ).to be < 10
322
+ end
323
+
324
+ it "should work together with signal handlers", :unix do
325
+ signal_received = false
326
+ trap 'USR1' do
327
+ signal_received = true
328
+ end
329
+
330
+ Thread.new do
331
+ sleep 0.1
332
+ Process.kill("USR1", Process.pid)
333
+ end
334
+ @conn.exec("select pg_sleep(0.3)")
335
+ expect( signal_received ).to be_truthy
336
+
337
+ signal_received = false
338
+ Thread.new do
339
+ sleep 0.1
340
+ Process.kill("USR1", Process.pid)
341
+ end
342
+ @conn.async_exec("select pg_sleep(0.3)")
343
+ expect( signal_received ).to be_truthy
344
+ end
345
+
346
+
347
+ it "automatically rolls back a transaction started with Connection#transaction if an exception " +
348
+ "is raised" do
349
+ # abort the per-example transaction so we can test our own
350
+ @conn.exec( 'ROLLBACK' )
351
+
352
+ res = nil
353
+ @conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
354
+
355
+ begin
356
+ expect {
357
+ res = @conn.transaction do
358
+ @conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
359
+ raise "Oh noes! All pie is gone!"
360
+ end
361
+ }.to raise_exception( RuntimeError, /all pie is gone/i )
362
+
363
+ res = @conn.exec( "SELECT * FROM pie" )
364
+ expect( res.ntuples ).to eq( 0 )
365
+ ensure
366
+ @conn.exec( "DROP TABLE pie" )
367
+ end
368
+ end
369
+
370
+ it "returns the block result from Connection#transaction" do
371
+ # abort the per-example transaction so we can test our own
372
+ @conn.exec( 'ROLLBACK' )
373
+
374
+ res = @conn.transaction do
375
+ "transaction result"
376
+ end
377
+ expect( res ).to eq( "transaction result" )
378
+ end
379
+
380
+ it "not read past the end of a large object" do
381
+ @conn.transaction do
382
+ oid = @conn.lo_create( 0 )
383
+ fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
384
+ @conn.lo_write( fd, "foobar" )
385
+ expect( @conn.lo_read( fd, 10 ) ).to be_nil()
386
+ @conn.lo_lseek( fd, 0, PG::SEEK_SET )
387
+ expect( @conn.lo_read( fd, 10 ) ).to eq( 'foobar' )
388
+ end
389
+ end
390
+
391
+
392
+ it "supports parameters passed to #exec (backward compatibility)" do
393
+ @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
394
+ @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
395
+ @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
396
+ @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
397
+
398
+ res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
399
+ expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
400
+ end
401
+
402
+ it "supports explicitly calling #exec_params" do
403
+ @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
404
+ @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
405
+ @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
406
+ @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
407
+
408
+ res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
409
+ expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
410
+ end
411
+
412
+ it "supports hash form parameters for #exec_params" do
413
+ hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
414
+ hash_param_nil = { value: nil, type: 17, format: 1 }
415
+ res = @conn.exec_params( "SELECT $1, $2",
416
+ [ hash_param_bin, hash_param_nil ] )
417
+ expect( res.values ).to eq( [["\\x00ff", nil]] )
418
+ expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
419
+ end
420
+
421
+ it "should work with arbitrary number of params" do
422
+ begin
423
+ 3.step( 12, 0.2 ) do |exp|
424
+ num_params = (2 ** exp).to_i
425
+ sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
426
+ params = num_params.times.to_a
427
+ res = @conn.exec_params( "SELECT #{sql}", params )
428
+ expect( res.nfields ).to eq( num_params )
429
+ expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
430
+ end
431
+ rescue PG::ProgramLimitExceeded
432
+ # Stop silently if the server complains about too many params
433
+ end
434
+ end
435
+
436
+ it "can wait for NOTIFY events" do
437
+ @conn.exec( 'ROLLBACK' )
438
+ @conn.exec( 'LISTEN woo' )
439
+
440
+ t = Thread.new do
441
+ begin
442
+ conn = described_class.connect( @conninfo )
443
+ sleep 1
444
+ conn.async_exec( 'NOTIFY woo' )
445
+ ensure
446
+ conn.finish
447
+ end
448
+ end
449
+
450
+ expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
451
+ @conn.exec( 'UNLISTEN woo' )
452
+
453
+ t.join
454
+ end
455
+
456
+ it "calls a block for NOTIFY events if one is given" do
457
+ @conn.exec( 'ROLLBACK' )
458
+ @conn.exec( 'LISTEN woo' )
459
+
460
+ t = Thread.new do
461
+ begin
462
+ conn = described_class.connect( @conninfo )
463
+ sleep 1
464
+ conn.async_exec( 'NOTIFY woo' )
465
+ ensure
466
+ conn.finish
467
+ end
468
+ end
469
+
470
+ eventpid = event = nil
471
+ @conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
472
+ expect( event ).to eq( 'woo' )
473
+ expect( eventpid ).to be_an( Integer )
474
+
475
+ @conn.exec( 'UNLISTEN woo' )
476
+
477
+ t.join
478
+ end
479
+
480
+ it "doesn't collapse sequential notifications" do
481
+ @conn.exec( 'ROLLBACK' )
482
+ @conn.exec( 'LISTEN woo' )
483
+ @conn.exec( 'LISTEN war' )
484
+ @conn.exec( 'LISTEN woz' )
485
+
486
+ begin
487
+ conn = described_class.connect( @conninfo )
488
+ conn.exec( 'NOTIFY woo' )
489
+ conn.exec( 'NOTIFY war' )
490
+ conn.exec( 'NOTIFY woz' )
491
+ ensure
492
+ conn.finish
493
+ end
494
+
495
+ channels = []
496
+ 3.times do
497
+ channels << @conn.wait_for_notify( 2 )
498
+ end
499
+
500
+ expect( channels.size ).to eq( 3 )
501
+ expect( channels ).to include( 'woo', 'war', 'woz' )
502
+
503
+ @conn.exec( 'UNLISTEN woz' )
504
+ @conn.exec( 'UNLISTEN war' )
505
+ @conn.exec( 'UNLISTEN woo' )
506
+ end
507
+
508
+ it "returns notifications which are already in the queue before wait_for_notify is called " +
509
+ "without waiting for the socket to become readable" do
510
+ @conn.exec( 'ROLLBACK' )
511
+ @conn.exec( 'LISTEN woo' )
512
+
513
+ begin
514
+ conn = described_class.connect( @conninfo )
515
+ conn.exec( 'NOTIFY woo' )
516
+ ensure
517
+ conn.finish
518
+ end
519
+
520
+ # Cause the notification to buffer, but not be read yet
521
+ @conn.exec( 'SELECT 1' )
522
+
523
+ expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
524
+ @conn.exec( 'UNLISTEN woo' )
525
+ end
526
+
527
+ it "can receive notices while waiting for NOTIFY without exceeding the timeout", :postgresql_90 do
528
+ notices = []
529
+ @conn.set_notice_processor do |msg|
530
+ notices << [msg, Time.now]
531
+ end
532
+ st = Time.now
533
+ @conn.send_query "SELECT pg_sleep(0.5); do $$ BEGIN RAISE NOTICE 'woohoo'; END; $$ LANGUAGE plpgsql;"
534
+ expect( @conn.wait_for_notify( 1 ) ).to be_nil
535
+ expect( notices.first ).to_not be_nil
536
+ et = Time.now
537
+ expect( (et - notices.first[1]) ).to be >= 0.4
538
+ expect( (et - st) ).to be >= 0.9
539
+ expect( (et - st) ).to be < 1.4
540
+ end
541
+
542
+ it "yields the result if block is given to exec" do
543
+ rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
544
+ values = []
545
+ expect( result ).to be_kind_of( PG::Result )
546
+ expect( result.ntuples ).to eq( 2 )
547
+ result.each do |tuple|
548
+ values << tuple['a']
549
+ end
550
+ values
551
+ end
552
+
553
+ expect( rval.size ).to eq( 2 )
554
+ expect( rval ).to include( '5678', '1234' )
555
+ end
556
+
557
+ it "can process #copy_data output queries" do
558
+ rows = []
559
+ res2 = @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
560
+ expect( res.result_status ).to eq( PG::PGRES_COPY_OUT )
561
+ expect( res.nfields ).to eq( 1 )
562
+ while row=@conn.get_copy_data
563
+ rows << row
564
+ end
565
+ end
566
+ expect( rows ).to eq( ["1\n", "2\n"] )
567
+ expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
568
+ expect( @conn ).to still_be_usable
569
+ end
570
+
571
+ it "can handle incomplete #copy_data output queries" do
572
+ expect {
573
+ @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
574
+ @conn.get_copy_data
575
+ end
576
+ }.to raise_error(PG::NotAllCopyDataRetrieved, /Not all/)
577
+ expect( @conn ).to still_be_usable
578
+ end
579
+
580
+ it "can handle client errors in #copy_data for output" do
581
+ expect {
582
+ @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do
583
+ raise "boom"
584
+ end
585
+ }.to raise_error(RuntimeError, "boom")
586
+ expect( @conn ).to still_be_usable
587
+ end
588
+
589
+ it "can handle server errors in #copy_data for output", :postgresql_90 do
590
+ @conn.exec "ROLLBACK"
591
+ @conn.transaction do
592
+ @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
593
+ expect {
594
+ @conn.copy_data( "COPY (SELECT errfunc()) TO STDOUT" ) do |res|
595
+ while @conn.get_copy_data
596
+ end
597
+ end
598
+ }.to raise_error(PG::Error, /test-error/)
599
+ end
600
+ expect( @conn ).to still_be_usable
601
+ end
602
+
603
+ it "can process #copy_data input queries" do
604
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
605
+ res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
606
+ expect( res.result_status ).to eq( PG::PGRES_COPY_IN )
607
+ expect( res.nfields ).to eq( 1 )
608
+ @conn.put_copy_data "1\n"
609
+ @conn.put_copy_data "2\n"
610
+ end
611
+ expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
612
+
613
+ expect( @conn ).to still_be_usable
614
+
615
+ res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
616
+ expect( res.values ).to eq( [["1"], ["2"]] )
617
+ end
618
+
619
+ it "can handle client errors in #copy_data for input" do
620
+ @conn.exec "ROLLBACK"
621
+ @conn.transaction do
622
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
623
+ expect {
624
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
625
+ raise "boom"
626
+ end
627
+ }.to raise_error(RuntimeError, "boom")
628
+ end
629
+
630
+ expect( @conn ).to still_be_usable
631
+ end
632
+
633
+ it "can handle server errors in #copy_data for input" do
634
+ @conn.exec "ROLLBACK"
635
+ @conn.transaction do
636
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
637
+ expect {
638
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
639
+ @conn.put_copy_data "xyz\n"
640
+ end
641
+ }.to raise_error(PG::Error, /invalid input syntax for integer/)
642
+ end
643
+ expect( @conn ).to still_be_usable
644
+ end
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
+
671
+ it "should raise an error for non copy statements in #copy_data" do
672
+ expect {
673
+ @conn.copy_data( "SELECT 1" ){}
674
+ }.to raise_error(ArgumentError, /no COPY/)
675
+
676
+ expect( @conn ).to still_be_usable
677
+ end
678
+
679
+ it "correctly finishes COPY queries passed to #async_exec" do
680
+ @conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
681
+
682
+ results = []
683
+ begin
684
+ data = @conn.get_copy_data( true )
685
+ if false == data
686
+ @conn.block( 2.0 )
687
+ data = @conn.get_copy_data( true )
688
+ end
689
+ results << data if data
690
+ end until data.nil?
691
+
692
+ expect( results.size ).to eq( 2 )
693
+ expect( results ).to include( "1\n", "2\n" )
694
+ end
695
+
696
+
697
+ it "described_class#block shouldn't block a second thread" do
698
+ start = Time.now
699
+ t = Thread.new do
700
+ @conn.send_query( "select pg_sleep(3)" )
701
+ @conn.block
702
+ end
703
+
704
+ sleep 0.5
705
+ expect( t ).to be_alive()
706
+ @conn.cancel
707
+ t.join
708
+ expect( (Time.now - start) ).to be < 3
709
+ end
710
+
711
+ it "described_class#block should allow a timeout" do
712
+ @conn.send_query( "select pg_sleep(1)" )
713
+
714
+ start = Time.now
715
+ @conn.block( 0.3 )
716
+ finish = Time.now
717
+
718
+ expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
719
+ end
720
+
721
+
722
+ it "can encrypt a string given a password and username" do
723
+ expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
724
+ end
725
+
726
+ it "can return the default connection options" do
727
+ expect( described_class.conndefaults ).to be_a( Array )
728
+ expect( described_class.conndefaults ).to all( be_a(Hash) )
729
+ expect( described_class.conndefaults[0] ).to include( :keyword, :label, :dispchar, :dispsize )
730
+ expect( @conn.conndefaults ).to eq( described_class.conndefaults )
731
+ end
732
+
733
+ it "can return the default connection options as a Hash" do
734
+ expect( described_class.conndefaults_hash ).to be_a( Hash )
735
+ expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
736
+ expect( ['5432', '54321'] ).to include( described_class.conndefaults_hash[:port] )
737
+ expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
738
+ end
739
+
740
+ it "can return the connection's connection options", :postgresql_93 do
741
+ expect( @conn.conninfo ).to be_a( Array )
742
+ expect( @conn.conninfo ).to all( be_a(Hash) )
743
+ expect( @conn.conninfo[0] ).to include( :keyword, :label, :dispchar, :dispsize )
744
+ end
745
+
746
+
747
+ it "can return the connection's connection options as a Hash", :postgresql_93 do
748
+ expect( @conn.conninfo_hash ).to be_a( Hash )
749
+ expect( @conn.conninfo_hash ).to include( :user, :password, :connect_timeout, :dbname, :host )
750
+ expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
751
+ end
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
+
772
+
773
+ it "honors the connect_timeout connection parameter", :postgresql_93 do
774
+ conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
775
+ begin
776
+ expect( conn.conninfo_hash[:connect_timeout] ).to eq( "11" )
777
+ ensure
778
+ conn.finish
779
+ end
780
+ end
781
+
782
+
783
+ it "raises an appropriate error if either of the required arguments for encrypt_password " +
784
+ "is not valid" do
785
+ expect {
786
+ described_class.encrypt_password( nil, nil )
787
+ }.to raise_error( TypeError )
788
+ expect {
789
+ described_class.encrypt_password( "postgres", nil )
790
+ }.to raise_error( TypeError )
791
+ expect {
792
+ described_class.encrypt_password( nil, "postgres" )
793
+ }.to raise_error( TypeError )
794
+ end
795
+
796
+
797
+ it "allows fetching a column of values from a result by column number" do
798
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
799
+ expect( res.column_values( 0 ) ).to eq( %w[1 2 3] )
800
+ expect( res.column_values( 1 ) ).to eq( %w[2 3 4] )
801
+ end
802
+
803
+
804
+ it "allows fetching a column of values from a result by field name" do
805
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
806
+ expect( res.field_values( 'column1' ) ).to eq( %w[1 2 3] )
807
+ expect( res.field_values( 'column2' ) ).to eq( %w[2 3 4] )
808
+ end
809
+
810
+
811
+ it "raises an error if selecting an invalid column index" do
812
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
813
+ expect {
814
+ res.column_values( 20 )
815
+ }.to raise_error( IndexError )
816
+ end
817
+
818
+
819
+ it "raises an error if selecting an invalid field name" do
820
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
821
+ expect {
822
+ res.field_values( 'hUUuurrg' )
823
+ }.to raise_error( IndexError )
824
+ end
825
+
826
+
827
+ it "raises an error if column index is not a number" do
828
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
829
+ expect {
830
+ res.column_values( 'hUUuurrg' )
831
+ }.to raise_error( TypeError )
832
+ end
833
+
834
+
835
+ it "can connect asynchronously", :socket_io do
836
+ serv = TCPServer.new( '127.0.0.1', 54320 )
837
+ conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
838
+ expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
839
+ select( nil, [conn.socket_io], nil, 0.2 )
840
+ serv.close
841
+ if conn.connect_poll == PG::PGRES_POLLING_READING
842
+ select( [conn.socket_io], nil, nil, 0.2 )
843
+ end
844
+ expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
845
+ end
846
+
847
+ it "discards previous results (if any) before waiting on an #async_exec"
848
+
849
+ it "calls the block if one is provided to #async_exec" do
850
+ result = nil
851
+ @conn.async_exec( "select 47 as one" ) do |pg_res|
852
+ result = pg_res[0]
853
+ end
854
+ expect( result ).to eq( { 'one' => '47' } )
855
+ end
856
+
857
+ it "raises a rescue-able error if #finish is called twice", :without_transaction do
858
+ conn = PG.connect( @conninfo )
859
+
860
+ conn.finish
861
+ expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
862
+ end
863
+
864
+ it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
865
+ conn = PG.connect( @conninfo )
866
+ io = conn.socket_io
867
+ conn.finish
868
+ expect( io ).to be_closed()
869
+ expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
870
+ end
871
+
872
+ it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction, :socket_io do
873
+ conn = PG.connect( @conninfo )
874
+ io = conn.socket_io
875
+ conn.reset
876
+ expect( io ).to be_closed()
877
+ expect( conn.socket_io ).to_not equal( io )
878
+ conn.finish
879
+ end
880
+
881
+ it "block should raise ConnectionBad for a closed connection" do
882
+ serv = TCPServer.new( '127.0.0.1', 54320 )
883
+ conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
884
+ while [PG::CONNECTION_STARTED, PG::CONNECTION_MADE].include?(conn.connect_poll)
885
+ sleep 0.1
886
+ end
887
+ serv.close
888
+ expect{ conn.block }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
889
+ expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
890
+ end
891
+
892
+ context "under PostgreSQL 9", :postgresql_90 do
893
+
894
+ before( :each ) do
895
+ pending "only works with a PostgreSQL >= 9.0 server" if @conn.server_version < 9_00_00
896
+ end
897
+
898
+ it "sets the fallback_application_name on new connections" do
899
+ conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
900
+
901
+ conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
902
+ expect( conn_name ).to include( $0[0..10] )
903
+ expect( conn_name ).to include( $0[-10..-1] )
904
+ expect( conn_name.length ).to be <= 64
905
+ end
906
+
907
+ it "sets a shortened fallback_application_name on new connections" do
908
+ old_0 = $0
909
+ begin
910
+ $0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
911
+ conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
912
+ conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
913
+ expect( conn_name ).to include( $0[0..10] )
914
+ expect( conn_name ).to include( $0[-10..-1] )
915
+ expect( conn_name.length ).to be <= 64
916
+ ensure
917
+ $0 = old_0
918
+ end
919
+ end
920
+
921
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
922
+ "any number of arguments" do
923
+
924
+ @conn.exec( 'ROLLBACK' )
925
+ @conn.exec( 'LISTEN knees' )
926
+
927
+ conn = described_class.connect( @conninfo )
928
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
929
+ conn.finish
930
+
931
+ event, pid, msg = nil
932
+ @conn.wait_for_notify( 10 ) do |*args|
933
+ event, pid, msg = *args
934
+ end
935
+ @conn.exec( 'UNLISTEN knees' )
936
+
937
+ expect( event ).to eq( 'knees' )
938
+ expect( pid ).to be_a_kind_of( Integer )
939
+ expect( msg ).to eq( 'skirt and boots' )
940
+ end
941
+
942
+ it "accepts nil as the timeout in #wait_for_notify " do
943
+ @conn.exec( 'ROLLBACK' )
944
+ @conn.exec( 'LISTEN knees' )
945
+
946
+ conn = described_class.connect( @conninfo )
947
+ conn.exec( %Q{NOTIFY knees} )
948
+ conn.finish
949
+
950
+ event, pid = nil
951
+ @conn.wait_for_notify( nil ) do |*args|
952
+ event, pid = *args
953
+ end
954
+ @conn.exec( 'UNLISTEN knees' )
955
+
956
+ expect( event ).to eq( 'knees' )
957
+ expect( pid ).to be_a_kind_of( Integer )
958
+ end
959
+
960
+ it "sends nil as the payload if the notification wasn't given one" do
961
+ @conn.exec( 'ROLLBACK' )
962
+ @conn.exec( 'LISTEN knees' )
963
+
964
+ conn = described_class.connect( @conninfo )
965
+ conn.exec( %Q{NOTIFY knees} )
966
+ conn.finish
967
+
968
+ payload = :notnil
969
+ @conn.wait_for_notify( nil ) do |*args|
970
+ payload = args[ 2 ]
971
+ end
972
+ @conn.exec( 'UNLISTEN knees' )
973
+
974
+ expect( payload ).to be_nil()
975
+ end
976
+
977
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
978
+ "two arguments" do
979
+
980
+ @conn.exec( 'ROLLBACK' )
981
+ @conn.exec( 'LISTEN knees' )
982
+
983
+ conn = described_class.connect( @conninfo )
984
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
985
+ conn.finish
986
+
987
+ event, pid, msg = nil
988
+ @conn.wait_for_notify( 10 ) do |arg1, arg2|
989
+ event, pid, msg = arg1, arg2
990
+ end
991
+ @conn.exec( 'UNLISTEN knees' )
992
+
993
+ expect( event ).to eq( 'knees' )
994
+ expect( pid ).to be_a_kind_of( Integer )
995
+ expect( msg ).to be_nil()
996
+ end
997
+
998
+ it "calls the block supplied to wait_for_notify with the notify payload if it " +
999
+ "doesn't accept arguments" do
1000
+
1001
+ @conn.exec( 'ROLLBACK' )
1002
+ @conn.exec( 'LISTEN knees' )
1003
+
1004
+ conn = described_class.connect( @conninfo )
1005
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1006
+ conn.finish
1007
+
1008
+ notification_received = false
1009
+ @conn.wait_for_notify( 10 ) do
1010
+ notification_received = true
1011
+ end
1012
+ @conn.exec( 'UNLISTEN knees' )
1013
+
1014
+ expect( notification_received ).to be_truthy()
1015
+ end
1016
+
1017
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1018
+ "three arguments" do
1019
+
1020
+ @conn.exec( 'ROLLBACK' )
1021
+ @conn.exec( 'LISTEN knees' )
1022
+
1023
+ conn = described_class.connect( @conninfo )
1024
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1025
+ conn.finish
1026
+
1027
+ event, pid, msg = nil
1028
+ @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
1029
+ event, pid, msg = arg1, arg2, arg3
1030
+ end
1031
+ @conn.exec( 'UNLISTEN knees' )
1032
+
1033
+ expect( event ).to eq( 'knees' )
1034
+ expect( pid ).to be_a_kind_of( Integer )
1035
+ expect( msg ).to eq( 'skirt and boots' )
1036
+ end
1037
+
1038
+ end
1039
+
1040
+ context "under PostgreSQL 9.1 client library", :postgresql_91, :without_transaction do
1041
+
1042
+ it "pings successfully with connection string" do
1043
+ ping = described_class.ping(@conninfo)
1044
+ expect( ping ).to eq( PG::PQPING_OK )
1045
+ end
1046
+
1047
+ it "pings using 7 arguments converted to strings" do
1048
+ ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
1049
+ expect( ping ).to eq( PG::PQPING_OK )
1050
+ end
1051
+
1052
+ it "pings using a hash of connection parameters" do
1053
+ ping = described_class.ping(
1054
+ :host => 'localhost',
1055
+ :port => @port,
1056
+ :dbname => :test)
1057
+ expect( ping ).to eq( PG::PQPING_OK )
1058
+ end
1059
+
1060
+ it "returns correct response when ping connection cannot be established" do
1061
+ ping = described_class.ping(
1062
+ :host => 'localhost',
1063
+ :port => 9999,
1064
+ :dbname => :test)
1065
+ expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
1066
+ end
1067
+
1068
+ it "returns correct response when ping connection arguments are wrong" do
1069
+ ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
1070
+ expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
1071
+ end
1072
+
1073
+
1074
+ end
1075
+
1076
+ context "under PostgreSQL 9.2 client library", :postgresql_92 do
1077
+ describe "set_single_row_mode" do
1078
+
1079
+ it "raises an error when called at the wrong time" do
1080
+ expect {
1081
+ @conn.set_single_row_mode
1082
+ }.to raise_error(PG::Error)
1083
+ end
1084
+
1085
+ it "should work in single row mode" do
1086
+ @conn.send_query( "SELECT generate_series(1,10)" )
1087
+ @conn.set_single_row_mode
1088
+
1089
+ results = []
1090
+ loop do
1091
+ @conn.block
1092
+ res = @conn.get_result or break
1093
+ results << res
1094
+ end
1095
+ expect( results.length ).to eq( 11 )
1096
+ results[0..-2].each do |res|
1097
+ expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1098
+ values = res.field_values('generate_series')
1099
+ expect( values.length ).to eq( 1 )
1100
+ expect( values.first.to_i ).to be > 0
1101
+ end
1102
+ expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
1103
+ expect( results.last.ntuples ).to eq( 0 )
1104
+ end
1105
+
1106
+ it "should receive rows before entire query is finished" do
1107
+ @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
1108
+ @conn.set_single_row_mode
1109
+
1110
+ start_time = Time.now
1111
+ first_row_time = nil
1112
+ loop do
1113
+ res = @conn.get_result or break
1114
+ res.check
1115
+ first_row_time = Time.now unless first_row_time
1116
+ end
1117
+ expect( (Time.now - start_time) ).to be >= 0.9
1118
+ expect( (first_row_time - start_time) ).to be < 0.9
1119
+ end
1120
+
1121
+ it "should receive rows before entire query fails" do
1122
+ @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
1123
+ @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
1124
+ @conn.set_single_row_mode
1125
+
1126
+ first_result = nil
1127
+ expect do
1128
+ loop do
1129
+ res = @conn.get_result or break
1130
+ res.check
1131
+ first_result ||= res
1132
+ end
1133
+ end.to raise_error(PG::Error)
1134
+ expect( first_result.kind_of?(PG::Result) ).to be_truthy
1135
+ expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1136
+ end
1137
+ end
1138
+ end
1139
+
1140
+ context "multinationalization support", :ruby_19 do
1141
+
1142
+ describe "rubyforge #22925: m17n support" do
1143
+ it "should return results in the same encoding as the client (iso-8859-1)" do
1144
+ out_string = nil
1145
+ @conn.transaction do |conn|
1146
+ conn.internal_encoding = 'iso8859-1'
1147
+ res = conn.exec("VALUES ('fantasia')", [], 0)
1148
+ out_string = res[0]['column1']
1149
+ end
1150
+ expect( out_string ).to eq( 'fantasia' )
1151
+ expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
1152
+ end
1153
+
1154
+ it "should return results in the same encoding as the client (utf-8)" do
1155
+ out_string = nil
1156
+ @conn.transaction do |conn|
1157
+ conn.internal_encoding = 'utf-8'
1158
+ res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
1159
+ out_string = res[0]['column1']
1160
+ end
1161
+ expect( out_string ).to eq( '世界線航跡蔵' )
1162
+ expect( out_string.encoding ).to eq( Encoding::UTF_8 )
1163
+ end
1164
+
1165
+ it "should return results in the same encoding as the client (EUC-JP)" do
1166
+ out_string = nil
1167
+ @conn.transaction do |conn|
1168
+ conn.internal_encoding = 'EUC-JP'
1169
+ stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1170
+ res = conn.exec(stmt, [], 0)
1171
+ out_string = res[0]['column1']
1172
+ end
1173
+ expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1174
+ expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1175
+ end
1176
+
1177
+ it "returns the results in the correct encoding even if the client_encoding has " +
1178
+ "changed since the results were fetched" do
1179
+ out_string = nil
1180
+ @conn.transaction do |conn|
1181
+ conn.internal_encoding = 'EUC-JP'
1182
+ stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1183
+ res = conn.exec(stmt, [], 0)
1184
+ conn.internal_encoding = 'utf-8'
1185
+ out_string = res[0]['column1']
1186
+ end
1187
+ expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1188
+ expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1189
+ end
1190
+
1191
+ it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
1192
+ @conn.exec "SET client_encoding TO SQL_ASCII"
1193
+ expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
1194
+ end
1195
+
1196
+ it "uses the client encoding for escaped string" do
1197
+ original = "Möhre to\0 escape".encode( "utf-16be" )
1198
+ @conn.set_client_encoding( "euc_jp" )
1199
+ escaped = @conn.escape( original )
1200
+ expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1201
+ expect( escaped ).to eq( "Möhre to".encode(Encoding::EUC_JP) )
1202
+ end
1203
+
1204
+ it "uses the client encoding for escaped literal", :postgresql_90 do
1205
+ original = "Möhre to\0 escape".encode( "utf-16be" )
1206
+ @conn.set_client_encoding( "euc_jp" )
1207
+ escaped = @conn.escape_literal( original )
1208
+ expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1209
+ expect( escaped ).to eq( "'Möhre to'".encode(Encoding::EUC_JP) )
1210
+ end
1211
+
1212
+ it "uses the client encoding for escaped identifier", :postgresql_90 do
1213
+ original = "Möhre to\0 escape".encode( "utf-16le" )
1214
+ @conn.set_client_encoding( "euc_jp" )
1215
+ escaped = @conn.escape_identifier( original )
1216
+ expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1217
+ expect( escaped ).to eq( "\"Möhre to\"".encode(Encoding::EUC_JP) )
1218
+ end
1219
+
1220
+ it "uses the client encoding for quote_ident" do
1221
+ original = "Möhre to\0 escape".encode( "utf-16le" )
1222
+ @conn.set_client_encoding( "euc_jp" )
1223
+ escaped = @conn.quote_ident( original )
1224
+ expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1225
+ expect( escaped ).to eq( "\"Möhre to\"".encode(Encoding::EUC_JP) )
1226
+ end
1227
+
1228
+ it "uses the previous string encoding for escaped string" do
1229
+ original = "Möhre to\0 escape".encode( "iso-8859-1" )
1230
+ @conn.set_client_encoding( "euc_jp" )
1231
+ escaped = described_class.escape( original )
1232
+ expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1233
+ expect( escaped ).to eq( "Möhre to".encode(Encoding::ISO8859_1) )
1234
+ end
1235
+
1236
+ it "uses the previous string encoding for quote_ident" do
1237
+ original = "Möhre to\0 escape".encode( "iso-8859-1" )
1238
+ @conn.set_client_encoding( "euc_jp" )
1239
+ escaped = described_class.quote_ident( original )
1240
+ expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
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']] )
1270
+ end
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
1329
+ end
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
1349
+
1350
+ describe "Ruby 1.9.x default_internal encoding" do
1351
+
1352
+ it "honors the Encoding.default_internal if it's set and the synchronous interface is used" do
1353
+ @conn.transaction do |txn_conn|
1354
+ txn_conn.internal_encoding = Encoding::ISO8859_1
1355
+ txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
1356
+ txn_conn.exec( "INSERT INTO defaultinternaltest VALUES ('Grün und Weiß')" )
1357
+ end
1358
+
1359
+ begin
1360
+ prev_encoding = Encoding.default_internal
1361
+ Encoding.default_internal = Encoding::ISO8859_2
1362
+
1363
+ conn = PG.connect( @conninfo )
1364
+ expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
1365
+ res = conn.exec( "SELECT foo FROM defaultinternaltest" )
1366
+ expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
1367
+ ensure
1368
+ conn.exec( "DROP TABLE defaultinternaltest" )
1369
+ conn.finish if conn
1370
+ Encoding.default_internal = prev_encoding
1371
+ end
1372
+ end
1373
+
1374
+ it "allows users of the async interface to set the client_encoding to the default_internal" do
1375
+ begin
1376
+ prev_encoding = Encoding.default_internal
1377
+ Encoding.default_internal = Encoding::KOI8_R
1378
+
1379
+ @conn.set_default_encoding
1380
+
1381
+ expect( @conn.internal_encoding ).to eq( Encoding::KOI8_R )
1382
+ ensure
1383
+ Encoding.default_internal = prev_encoding
1384
+ end
1385
+ end
1386
+
1387
+ end
1388
+
1389
+
1390
+ it "encodes exception messages with the connection's encoding (#96)", :without_transaction do
1391
+ # Use a new connection so the client_encoding isn't set outside of this example
1392
+ conn = PG.connect( @conninfo )
1393
+ conn.client_encoding = 'iso-8859-15'
1394
+
1395
+ conn.transaction do
1396
+ conn.exec "CREATE TABLE foo (bar TEXT)"
1397
+
1398
+ begin
1399
+ query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
1400
+ conn.exec( query )
1401
+ rescue => err
1402
+ expect( err.message.encoding ).to eq( Encoding::ISO8859_15 )
1403
+ else
1404
+ fail "No exception raised?!"
1405
+ end
1406
+ end
1407
+
1408
+ conn.finish if conn
1409
+ end
1410
+
1411
+ it "handles clearing result in or after set_notice_receiver", :postgresql_90 do
1412
+ r = nil
1413
+ @conn.set_notice_receiver do |result|
1414
+ r = result
1415
+ expect( r.cleared? ).to eq(false)
1416
+ end
1417
+ @conn.exec "do $$ BEGIN RAISE NOTICE 'foo'; END; $$ LANGUAGE plpgsql;"
1418
+ sleep 0.2
1419
+ expect( r ).to be_a( PG::Result )
1420
+ expect( r.cleared? ).to eq(true)
1421
+ expect( r.autoclear? ).to eq(true)
1422
+ r.clear
1423
+ @conn.set_notice_receiver
1424
+ end
1425
+
1426
+ it "receives properly encoded messages in the notice callbacks", :postgresql_90 do
1427
+ [:receiver, :processor].each do |kind|
1428
+ notices = []
1429
+ @conn.internal_encoding = 'utf-8'
1430
+ if kind == :processor
1431
+ @conn.set_notice_processor do |msg|
1432
+ notices << msg
1433
+ end
1434
+ else
1435
+ @conn.set_notice_receiver do |result|
1436
+ notices << result.error_message
1437
+ end
1438
+ end
1439
+
1440
+ 3.times do
1441
+ @conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
1442
+ end
1443
+
1444
+ expect( notices.length ).to eq( 3 )
1445
+ notices.each do |notice|
1446
+ expect( notice ).to match( /^NOTICE:.*世界線航跡蔵/ )
1447
+ expect( notice.encoding ).to eq( Encoding::UTF_8 )
1448
+ end
1449
+ @conn.set_notice_receiver
1450
+ @conn.set_notice_processor
1451
+ end
1452
+ end
1453
+
1454
+ it "receives properly encoded text from wait_for_notify", :postgresql_90 do
1455
+ @conn.internal_encoding = 'utf-8'
1456
+ @conn.exec( 'ROLLBACK' )
1457
+ @conn.exec( 'LISTEN "Möhre"' )
1458
+ @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1459
+ event, pid, msg = nil
1460
+ @conn.wait_for_notify( 10 ) do |*args|
1461
+ event, pid, msg = *args
1462
+ end
1463
+ @conn.exec( 'UNLISTEN "Möhre"' )
1464
+
1465
+ expect( event ).to eq( "Möhre" )
1466
+ expect( event.encoding ).to eq( Encoding::UTF_8 )
1467
+ expect( msg ).to eq( '世界線航跡蔵' )
1468
+ expect( msg.encoding ).to eq( Encoding::UTF_8 )
1469
+ end
1470
+
1471
+ it "returns properly encoded text from notifies", :postgresql_90 do
1472
+ @conn.internal_encoding = 'utf-8'
1473
+ @conn.exec( 'ROLLBACK' )
1474
+ @conn.exec( 'LISTEN "Möhre"' )
1475
+ @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1476
+ @conn.exec( 'UNLISTEN "Möhre"' )
1477
+
1478
+ notification = @conn.notifies
1479
+ expect( notification[:relname] ).to eq( "Möhre" )
1480
+ expect( notification[:relname].encoding ).to eq( Encoding::UTF_8 )
1481
+ expect( notification[:extra] ).to eq( '世界線航跡蔵' )
1482
+ expect( notification[:extra].encoding ).to eq( Encoding::UTF_8 )
1483
+ expect( notification[:be_pid] ).to be > 0
1484
+ end
1485
+ end
1486
+
1487
+ context "OS thread support", :ruby_19 do
1488
+ it "Connection#exec shouldn't block a second thread" do
1489
+ t = Thread.new do
1490
+ @conn.exec( "select pg_sleep(1)" )
1491
+ end
1492
+
1493
+ sleep 0.5
1494
+ expect( t ).to be_alive()
1495
+ t.join
1496
+ end
1497
+
1498
+ it "Connection.new shouldn't block a second thread" do
1499
+ serv = nil
1500
+ t = Thread.new do
1501
+ serv = TCPServer.new( '127.0.0.1', 54320 )
1502
+ expect {
1503
+ described_class.new( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
1504
+ }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly/)
1505
+ end
1506
+
1507
+ sleep 0.5
1508
+ expect( t ).to be_alive()
1509
+ serv.close
1510
+ t.join
1511
+ end
1512
+ end
1513
+
1514
+ describe "type casting" do
1515
+ it "should raise an error on invalid param mapping" do
1516
+ expect{
1517
+ @conn.exec_params( "SELECT 1", [], nil, :invalid )
1518
+ }.to raise_error(TypeError)
1519
+ end
1520
+
1521
+ it "should return nil if no type mapping is set" do
1522
+ expect( @conn.type_map_for_queries ).to be_kind_of(PG::TypeMapAllStrings)
1523
+ expect( @conn.type_map_for_results ).to be_kind_of(PG::TypeMapAllStrings)
1524
+ end
1525
+
1526
+ it "shouldn't type map params unless requested" do
1527
+ expect{
1528
+ @conn.exec_params( "SELECT $1", [5] )
1529
+ }.to raise_error(PG::IndeterminateDatatype)
1530
+ end
1531
+
1532
+ it "should raise an error on invalid encoder to put_copy_data" do
1533
+ expect{
1534
+ @conn.put_copy_data [1], :invalid
1535
+ }.to raise_error(TypeError)
1536
+ end
1537
+
1538
+ it "can type cast parameters to put_copy_data with explicit encoder" do
1539
+ tm = PG::TypeMapByColumn.new [nil]
1540
+ row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1541
+
1542
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1543
+ res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1544
+ @conn.put_copy_data [1], row_encoder
1545
+ @conn.put_copy_data ["2"], row_encoder
1546
+ end
1547
+
1548
+ res2 = @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1549
+ @conn.put_copy_data [3]
1550
+ @conn.put_copy_data ["4"]
1551
+ end
1552
+
1553
+ res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
1554
+ expect( res.values ).to eq( [["1"], ["2"], ["3"], ["4"]] )
1555
+ end
1556
+
1557
+ context "with default query type map" do
1558
+ before :each do
1559
+ @conn2 = described_class.new(@conninfo)
1560
+ tm = PG::TypeMapByClass.new
1561
+ tm[Integer] = PG::TextEncoder::Integer.new oid: 20
1562
+ @conn2.type_map_for_queries = tm
1563
+
1564
+ row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1565
+ @conn2.encoder_for_put_copy_data = row_encoder
1566
+ end
1567
+ after :each do
1568
+ @conn2.close
1569
+ end
1570
+
1571
+ it "should respect a type mapping for params and it's OID and format code" do
1572
+ res = @conn2.exec_params( "SELECT $1", [5] )
1573
+ expect( res.values ).to eq( [["5"]] )
1574
+ expect( res.ftype(0) ).to eq( 20 )
1575
+ end
1576
+
1577
+ it "should return the current type mapping" do
1578
+ expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByClass)
1579
+ end
1580
+
1581
+ it "should work with arbitrary number of params in conjunction with type casting" do
1582
+ begin
1583
+ 3.step( 12, 0.2 ) do |exp|
1584
+ num_params = (2 ** exp).to_i
1585
+ sql = num_params.times.map{|n| "$#{n+1}" }.join(",")
1586
+ params = num_params.times.to_a
1587
+ res = @conn2.exec_params( "SELECT #{sql}", params )
1588
+ expect( res.nfields ).to eq( num_params )
1589
+ expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
1590
+ end
1591
+ rescue PG::ProgramLimitExceeded
1592
+ # Stop silently as soon the server complains about too many params
1593
+ end
1594
+ end
1595
+
1596
+ it "can process #copy_data input queries with row encoder and respects character encoding" do
1597
+ @conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1598
+ res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1599
+ @conn2.put_copy_data [1]
1600
+ @conn2.put_copy_data ["Möhre".encode("utf-16le")]
1601
+ end
1602
+
1603
+ res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
1604
+ expect( res.values ).to eq( [["1"], ["Möhre"]] )
1605
+ end
1606
+ end
1607
+
1608
+ context "with default result type map" do
1609
+ before :each do
1610
+ @conn2 = described_class.new(@conninfo)
1611
+ tm = PG::TypeMapByOid.new
1612
+ tm.add_coder PG::TextDecoder::Integer.new oid: 23, format: 0
1613
+ @conn2.type_map_for_results = tm
1614
+
1615
+ row_decoder = PG::TextDecoder::CopyRow.new
1616
+ @conn2.decoder_for_get_copy_data = row_decoder
1617
+ end
1618
+ after :each do
1619
+ @conn2.close
1620
+ end
1621
+
1622
+ it "should respect a type mapping for result" do
1623
+ res = @conn2.exec_params( "SELECT $1::INT", ["5"] )
1624
+ expect( res.values ).to eq( [[5]] )
1625
+ end
1626
+
1627
+ it "should return the current type mapping" do
1628
+ expect( @conn2.type_map_for_results ).to be_kind_of(PG::TypeMapByOid)
1629
+ end
1630
+
1631
+ it "should work with arbitrary number of params in conjunction with type casting" do
1632
+ begin
1633
+ 3.step( 12, 0.2 ) do |exp|
1634
+ num_params = (2 ** exp).to_i
1635
+ sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
1636
+ params = num_params.times.to_a
1637
+ res = @conn2.exec_params( "SELECT #{sql}", params )
1638
+ expect( res.nfields ).to eq( num_params )
1639
+ expect( res.values ).to eq( [num_params.times.to_a] )
1640
+ end
1641
+ rescue PG::ProgramLimitExceeded
1642
+ # Stop silently as soon the server complains about too many params
1643
+ end
1644
+ end
1645
+
1646
+ it "can process #copy_data output with row decoder and respects character encoding" do
1647
+ @conn2.internal_encoding = Encoding::ISO8859_1
1648
+ rows = []
1649
+ res2 = @conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
1650
+ while row=@conn2.get_copy_data
1651
+ rows << row
1652
+ end
1653
+ end
1654
+ expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
1655
+ expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
1656
+ end
1657
+
1658
+ it "can type cast #copy_data output with explicit decoder" do
1659
+ tm = PG::TypeMapByColumn.new [PG::TextDecoder::Integer.new]
1660
+ row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
1661
+ rows = []
1662
+ @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT", row_decoder ) do |res|
1663
+ while row=@conn.get_copy_data
1664
+ rows << row
1665
+ end
1666
+ end
1667
+ @conn.copy_data( "COPY (SELECT 3 UNION ALL SELECT 4) TO STDOUT" ) do |res|
1668
+ while row=@conn.get_copy_data( false, row_decoder )
1669
+ rows << row
1670
+ end
1671
+ end
1672
+ expect( rows ).to eq( [[1], [2], [3], [4]] )
1673
+ end
1674
+ end
1675
+ end
1676
+ end