autocompl 0.2.1 → 0.2.2

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