rdo-postgres 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +20 -0
- data/.rspec +1 -0
- data/Gemfile +9 -0
- data/LICENSE +22 -0
- data/README.md +94 -0
- data/Rakefile +14 -0
- data/ext/rdo_postgres/casts.c +134 -0
- data/ext/rdo_postgres/casts.h +16 -0
- data/ext/rdo_postgres/driver.c +169 -0
- data/ext/rdo_postgres/driver.h +20 -0
- data/ext/rdo_postgres/extconf.rb +39 -0
- data/ext/rdo_postgres/macros.h +180 -0
- data/ext/rdo_postgres/params.c +106 -0
- data/ext/rdo_postgres/params.h +15 -0
- data/ext/rdo_postgres/rdo_postgres.c +18 -0
- data/ext/rdo_postgres/statements.c +237 -0
- data/ext/rdo_postgres/statements.h +15 -0
- data/ext/rdo_postgres/tuples.c +85 -0
- data/ext/rdo_postgres/tuples.h +20 -0
- data/lib/rdo-postgres.rb +1 -0
- data/lib/rdo/postgres.rb +16 -0
- data/lib/rdo/postgres/driver.rb +68 -0
- data/lib/rdo/postgres/version.rb +12 -0
- data/rdo-postgres.gemspec +23 -0
- data/spec/postgres/bind_params_spec.rb +902 -0
- data/spec/postgres/driver_spec.rb +285 -0
- data/spec/postgres/type_cast_spec.rb +272 -0
- data/spec/spec_helper.rb +10 -0
- metadata +126 -0
@@ -0,0 +1,285 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "uri"
|
3
|
+
|
4
|
+
describe RDO::Postgres::Driver do
|
5
|
+
let(:options) { connection_uri }
|
6
|
+
let(:connection) { RDO.connect(options) }
|
7
|
+
|
8
|
+
after(:each) { connection.close rescue nil }
|
9
|
+
|
10
|
+
describe "#initialize" do
|
11
|
+
context "with valid settings" do
|
12
|
+
it "opens a connection to the server" do
|
13
|
+
connection.should be_open
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "with invalid settings" do
|
18
|
+
let(:options) { URI.parse(connection_uri).tap{|u| u.user = "bad_user"}.to_s }
|
19
|
+
|
20
|
+
it "raises a RDO::Exception" do
|
21
|
+
expect { connection }.to raise_error(RDO::Exception)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "provides a meaningful error message" do
|
25
|
+
begin
|
26
|
+
connection && fail("RDO::Exception should be raised")
|
27
|
+
rescue RDO::Exception => e
|
28
|
+
e.message.should =~ /\bbad_user\b/
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#close" do
|
35
|
+
it "closes the connection to the database" do
|
36
|
+
connection.close
|
37
|
+
connection.should_not be_open
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns true" do
|
41
|
+
connection.close.should be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
context "called multiple times" do
|
45
|
+
it "has no negative side-effects" do
|
46
|
+
5.times { connection.close }
|
47
|
+
connection.should_not be_open
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#open" do
|
53
|
+
it "re-opens the connection to the database" do
|
54
|
+
connection.close && connection.open
|
55
|
+
connection.should be_open
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns true" do
|
59
|
+
connection.close
|
60
|
+
connection.open.should be_true
|
61
|
+
end
|
62
|
+
|
63
|
+
context "called multiple times" do
|
64
|
+
it "has no negative side-effects" do
|
65
|
+
connection.close
|
66
|
+
5.times { connection.open }
|
67
|
+
connection.should be_open
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "#execute" do
|
73
|
+
after(:each) do
|
74
|
+
connection.execute("DROP SCHEMA IF EXISTS rdo_test CASCADE")
|
75
|
+
end
|
76
|
+
|
77
|
+
context "with DDL" do
|
78
|
+
let(:result) do
|
79
|
+
connection.execute("CREATE SCHEMA rdo_test")
|
80
|
+
end
|
81
|
+
|
82
|
+
it "returns a RDO::Result" do
|
83
|
+
result.should be_a_kind_of(RDO::Result)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "with a bad query" do
|
88
|
+
let(:command) { connection.execute("SOME GIBBERISH") }
|
89
|
+
|
90
|
+
it "raises a RDO::Exception" do
|
91
|
+
expect { command }.to raise_error(RDO::Exception)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "provides a meaningful error message" do
|
95
|
+
begin
|
96
|
+
command && fail("RDO::Exception should be raised")
|
97
|
+
rescue RDO::Exception => e
|
98
|
+
e.message.should =~ /\bSOME\b/
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "with an INSERT" do
|
104
|
+
before(:each) do
|
105
|
+
connection.execute("CREATE SCHEMA rdo_test")
|
106
|
+
connection.execute("SET search_path = rdo_test")
|
107
|
+
connection.execute(
|
108
|
+
"CREATE TABLE users (id serial primary key, name text)"
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
context "returning rows" do
|
113
|
+
let(:result) do
|
114
|
+
connection.execute(
|
115
|
+
"INSERT INTO users (name) VALUES ('bob') RETURNING *"
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "returns a RDO::Result" do
|
120
|
+
result.should be_a_kind_of(RDO::Result)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "provides the return values" do
|
124
|
+
result.first[:id].should == 1
|
125
|
+
result.first[:name].should == "bob"
|
126
|
+
end
|
127
|
+
|
128
|
+
it "provides the #insert_id" do
|
129
|
+
result.insert_id.should == 1
|
130
|
+
end
|
131
|
+
|
132
|
+
it "provides the number of #affected_rows" do
|
133
|
+
result.affected_rows.should == 1
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context "not returning" do
|
138
|
+
let(:result) do
|
139
|
+
connection.execute("INSERT INTO users (name) VALUES ('bob')")
|
140
|
+
end
|
141
|
+
|
142
|
+
it "returns a RDO::Result" do
|
143
|
+
result.should be_a_kind_of(RDO::Result)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "has a nil #insert_id" do
|
147
|
+
result.insert_id.should be_nil
|
148
|
+
end
|
149
|
+
|
150
|
+
it "provides the number of #affected_rows" do
|
151
|
+
result.affected_rows.should == 1
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context "using bind parameters" do
|
156
|
+
let(:result) do
|
157
|
+
connection.execute("INSERT INTO users (name) VALUES (?)", "bob")
|
158
|
+
end
|
159
|
+
|
160
|
+
it "returns a RDO::Result" do
|
161
|
+
result.should be_a_kind_of(RDO::Result)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "provides the number of #affected_rows" do
|
165
|
+
result.affected_rows.should == 1
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context "with a SELECT" do
|
171
|
+
before(:each) do
|
172
|
+
connection.execute("CREATE SCHEMA rdo_test")
|
173
|
+
connection.execute("SET search_path = rdo_test")
|
174
|
+
end
|
175
|
+
|
176
|
+
context "returning no rows" do
|
177
|
+
let(:result) { connection.execute("SELECT unnest('{}'::text[])") }
|
178
|
+
|
179
|
+
it "returns a RDO::Result" do
|
180
|
+
result.should be_a_kind_of(RDO::Result)
|
181
|
+
end
|
182
|
+
|
183
|
+
it "has no tuples" do
|
184
|
+
result.count.should == 0
|
185
|
+
end
|
186
|
+
|
187
|
+
it "can be converted to an empty array" do
|
188
|
+
result.to_a.should == []
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context "returning rows" do
|
193
|
+
before(:each) do
|
194
|
+
connection.execute(
|
195
|
+
"CREATE TABLE users (id serial primary key, name text)"
|
196
|
+
)
|
197
|
+
connection.execute("INSERT INTO users (name) VALUES ('bob'), ('barry')")
|
198
|
+
end
|
199
|
+
|
200
|
+
let(:result) { connection.execute("SELECT * FROM users") }
|
201
|
+
|
202
|
+
it "returns a RDO::Result" do
|
203
|
+
result.should be_a_kind_of(RDO::Result)
|
204
|
+
end
|
205
|
+
|
206
|
+
it "provides the row count" do
|
207
|
+
result.count.should == 2
|
208
|
+
end
|
209
|
+
|
210
|
+
it "allows enumeration of the rows" do
|
211
|
+
rows = []
|
212
|
+
result.each{|row| rows << row }
|
213
|
+
rows.should == [{id: 1, name: "bob"}, {id: 2, name: "barry"}]
|
214
|
+
end
|
215
|
+
|
216
|
+
context "using bind parameters" do
|
217
|
+
let(:result) do
|
218
|
+
connection.execute("SELECT * FROM users WHERE name = ?", "barry")
|
219
|
+
end
|
220
|
+
|
221
|
+
it "returns a RDO::Result" do
|
222
|
+
result.should be_a_kind_of(RDO::Result)
|
223
|
+
end
|
224
|
+
|
225
|
+
it "provides the correct count" do
|
226
|
+
result.count.should == 1
|
227
|
+
end
|
228
|
+
|
229
|
+
it "allows enumeration of the rows" do
|
230
|
+
rows = []
|
231
|
+
result.each{|row| rows << row}
|
232
|
+
rows.should == [{id: 2, name: "barry"}]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe "string encoding" do
|
240
|
+
context "with utf-8" do
|
241
|
+
let(:options) { URI.parse(connection_uri).tap{|u| u.query = "encoding=utf-8"}.to_s }
|
242
|
+
|
243
|
+
it "returns utf-8 strings" do
|
244
|
+
connection.execute("SELECT 'foo'::text").first_value.encoding.should ==
|
245
|
+
Encoding.find("utf-8")
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
context "with iso-8859-1" do
|
250
|
+
let(:options) { URI.parse(connection_uri).tap{|u| u.query = "encoding=iso-8859-1"}.to_s }
|
251
|
+
|
252
|
+
it "returns iso-8859-1 strings" do
|
253
|
+
connection.execute("SELECT 'foo'::text").first_value.encoding.should ==
|
254
|
+
Encoding.find("iso-8859-1")
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context "with latin1" do
|
259
|
+
let(:options) { URI.parse(connection_uri).tap{|u| u.query = "encoding=latin1"}.to_s }
|
260
|
+
|
261
|
+
it "returns ascii-8bit strings (ruby doesn't know this charset)" do
|
262
|
+
connection.execute("SELECT 'foo'::text").first_value.encoding.should ==
|
263
|
+
Encoding.find("binary")
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
describe "#quote" do
|
269
|
+
it "quotes a string literal for insertion into the SQL" do
|
270
|
+
connection.quote("what's this?").should == "what''s this?"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
describe "#prepare" do
|
275
|
+
it "returns a RDO::Statement" do
|
276
|
+
connection.prepare("SELECT ?::text").should be_a_kind_of(RDO::Statement)
|
277
|
+
end
|
278
|
+
|
279
|
+
it "can be executed multiple times" do
|
280
|
+
stmt = connection.prepare("SELECT ?::integer + ?")
|
281
|
+
stmt.execute(4, 7).first_value.should == 11
|
282
|
+
stmt.execute(5, 22).first_value.should == 27
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe RDO::Postgres::Driver, "type casting" do
|
4
|
+
let(:options) { connection_uri }
|
5
|
+
let(:connection) { RDO.connect(options) }
|
6
|
+
let(:value) { connection.execute(sql).first_value }
|
7
|
+
|
8
|
+
after(:each) { connection.close rescue nil }
|
9
|
+
|
10
|
+
describe "null cast" do
|
11
|
+
let(:sql) { "SELECT null" }
|
12
|
+
|
13
|
+
it "returns nil" do
|
14
|
+
value.should be_nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "integer cast" do
|
19
|
+
let(:sql) { "SELECT 42::integer" }
|
20
|
+
|
21
|
+
it "returns a Fixnum" do
|
22
|
+
value.should == 42
|
23
|
+
end
|
24
|
+
|
25
|
+
context "using smallint" do
|
26
|
+
let(:sql) { "SELECT 42::smallint" }
|
27
|
+
|
28
|
+
it "returns a Fixnum" do
|
29
|
+
value.should == 42
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "using bigint" do
|
34
|
+
let(:sql) { "SELECT 42::bigint" }
|
35
|
+
|
36
|
+
it "returns a Fixnum" do
|
37
|
+
value.should == 42
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "text cast" do
|
43
|
+
let(:sql) { "SELECT 42::text" }
|
44
|
+
|
45
|
+
it "returns a String" do
|
46
|
+
value.should == "42"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "varchar cast" do
|
51
|
+
let(:sql) { "SELECT 'a very long string'::varchar(10)" }
|
52
|
+
|
53
|
+
it "returns a String" do
|
54
|
+
value.should == "a very lon"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "char cast" do
|
59
|
+
let(:sql) { "SELECT 'a very long string'::char(10)" }
|
60
|
+
|
61
|
+
it "returns a String" do
|
62
|
+
value.should == "a very lon"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "boolean cast" do
|
67
|
+
describe "true" do
|
68
|
+
let(:sql) { "SELECT true" }
|
69
|
+
|
70
|
+
it "returns true" do
|
71
|
+
value.should == true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "false" do
|
76
|
+
let(:sql) { "SELECT false" }
|
77
|
+
|
78
|
+
it "returns false" do
|
79
|
+
value.should == false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "bytea cast" do
|
85
|
+
let(:sql) { "SELECT decode('00112233', 'hex')::bytea" }
|
86
|
+
|
87
|
+
context "using bytea_output = hex" do
|
88
|
+
before(:each) { connection.execute("SET bytea_output = hex") }
|
89
|
+
|
90
|
+
it "returns a String" do
|
91
|
+
value.should == "\x00\x11\x22\x33"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "using bytea_output = escape" do
|
96
|
+
before(:each) do
|
97
|
+
# don't error on older versions of postgresql
|
98
|
+
connection.execute("SET bytea_output = escape") rescue nil
|
99
|
+
end
|
100
|
+
|
101
|
+
it "returns a String" do
|
102
|
+
value.should == "\x00\x11\x22\x33"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "float cast" do
|
108
|
+
let(:sql) { "SELECT 1.2::float" }
|
109
|
+
|
110
|
+
it "returns a Float" do
|
111
|
+
value.should be_a_kind_of(Float)
|
112
|
+
value.should <= 1.201
|
113
|
+
value.should >= 1.199
|
114
|
+
end
|
115
|
+
|
116
|
+
context "using real" do
|
117
|
+
let(:sql) { "SELECT 1.2::real" }
|
118
|
+
|
119
|
+
it "returns a Float" do
|
120
|
+
value.should be_a_kind_of(Float)
|
121
|
+
value.should <= 1.201
|
122
|
+
value.should >= 1.199
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "using double precision" do
|
127
|
+
let(:sql) { "SELECT 1.2::double precision" }
|
128
|
+
|
129
|
+
it "returns a Float" do
|
130
|
+
value.should be_a_kind_of(Float)
|
131
|
+
value.should <= 1.201
|
132
|
+
value.should >= 1.199
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context "for 'NaN'" do
|
137
|
+
let(:sql) { "SELECT 'NaN'::float" }
|
138
|
+
|
139
|
+
it "returns Float::NAN" do
|
140
|
+
value.should be_a_kind_of(Float)
|
141
|
+
value.should be_nan
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "for 'Infinity'" do
|
146
|
+
let(:sql) { "SELECT 'Infinity'::float" }
|
147
|
+
|
148
|
+
it "returns Float::INFINITY" do
|
149
|
+
value.should == Float::INFINITY
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context "for '-Infinity'" do
|
154
|
+
let(:sql) { "SELECT '-Infinity'::float" }
|
155
|
+
|
156
|
+
it "returns -Float::INFINITY" do
|
157
|
+
value.should == -Float::INFINITY
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context "for '1E-06'" do
|
162
|
+
let(:sql) { "SELECT '1E-06'::float" }
|
163
|
+
|
164
|
+
it "returns 1e-06" do
|
165
|
+
value.should be_a_kind_of(Float)
|
166
|
+
value.should >= Float("1E-06") - 0.01
|
167
|
+
value.should <= Float("1E-06") + 0.01
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "decimal cast" do
|
173
|
+
require "bigdecimal"
|
174
|
+
|
175
|
+
%w[decimal numeric].each do |type|
|
176
|
+
context "as #{type}" do
|
177
|
+
context "using a max precision and scale" do
|
178
|
+
let(:sql) { "SELECT '124.36'::#{type}(5,2)" }
|
179
|
+
|
180
|
+
it "returns a BigDecimal" do
|
181
|
+
value.should == BigDecimal("124.36")
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context "using a max precision only" do
|
186
|
+
let(:sql) { "SELECT '124.36'::#{type}(5)" }
|
187
|
+
|
188
|
+
it "returns a BigDecimal" do
|
189
|
+
value.should == BigDecimal("124")
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context "using no precision or scale" do
|
194
|
+
let(:sql) { "SELECT '124.36'::#{type}" }
|
195
|
+
|
196
|
+
it "returns a BigDecimal" do
|
197
|
+
value.should == BigDecimal("124.36")
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context "for 'NaN'" do
|
202
|
+
let(:sql) { "SELECT 'NaN'::#{type}" }
|
203
|
+
|
204
|
+
it "returns a BigDecimal" do
|
205
|
+
value.should be_a_kind_of(BigDecimal)
|
206
|
+
value.should be_nan
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe "date cast" do
|
214
|
+
context "with YYYY-MM-DD format date" do
|
215
|
+
let(:sql) { "SELECT '2012-09-22'::date" }
|
216
|
+
|
217
|
+
it "returns a Date" do
|
218
|
+
value.should == Date.new(2012, 9, 22)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
context "with YYY-MM-DD AD format date" do
|
223
|
+
let(:sql) { "SELECT '432-09-22 AD'::date" }
|
224
|
+
|
225
|
+
it "returns a Date" do
|
226
|
+
value.should == Date.new(432, 9, 22)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
context "with YYY-MM-DD BC format date" do
|
231
|
+
let(:sql) { "SELECT '432-09-22 BC'::date" }
|
232
|
+
|
233
|
+
it "returns a Date" do
|
234
|
+
value.should == Date.new(-431, 9, 22)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe "timestamp cast" do
|
240
|
+
context "without a time zone" do
|
241
|
+
let(:sql) { "SELECT '2012-09-22 04:26:34'::timestamp" }
|
242
|
+
|
243
|
+
it "returns a DateTime in the local time zone" do
|
244
|
+
value.should == DateTime.new(2012, 9, 22, 4, 26, 34, DateTime.now.zone)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context "with a time zone" do
|
249
|
+
let(:sql) { "SELECT '2012-09-22 04:26:34'::timestamptz" }
|
250
|
+
|
251
|
+
it "returns a DateTime, in the server's time zone" do
|
252
|
+
value.should == DateTime.new(2012, 9, 22, 4, 26, 34, DateTime.now.zone)
|
253
|
+
end
|
254
|
+
|
255
|
+
context "specifying an alternate time zone" do
|
256
|
+
let(:sql) { "SELECT '2012-09-22 04:26:34'::timestamptz at time zone 'UTC'" }
|
257
|
+
|
258
|
+
it "returns a DateTime at the given time zone" do
|
259
|
+
value.should == DateTime.new(2012, 9, 21, 18, 26, 34, DateTime.now.zone)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
context "with a different time zone set on the connection" do
|
264
|
+
before(:each) { connection.execute("SET timezone = 'UTC'") }
|
265
|
+
|
266
|
+
it "returns a DateTime with the conversion done accordingly" do
|
267
|
+
value.should == DateTime.new(2012, 9, 22, 14, 26, 34, DateTime.now.zone)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|