ydbd-pg 0.5.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.
- checksums.yaml +7 -0
- data/ChangeLog +3703 -0
- data/LICENSE +25 -0
- data/lib/dbd/Pg.rb +188 -0
- data/lib/dbd/pg/database.rb +516 -0
- data/lib/dbd/pg/exec.rb +47 -0
- data/lib/dbd/pg/statement.rb +160 -0
- data/lib/dbd/pg/tuples.rb +121 -0
- data/lib/dbd/pg/type.rb +209 -0
- data/readme.md +274 -0
- data/test/DBD_TESTS +50 -0
- data/test/dbd/general/test_database.rb +206 -0
- data/test/dbd/general/test_statement.rb +326 -0
- data/test/dbd/general/test_types.rb +296 -0
- data/test/dbd/postgresql/base.rb +31 -0
- data/test/dbd/postgresql/down.sql +31 -0
- data/test/dbd/postgresql/test_arrays.rb +179 -0
- data/test/dbd/postgresql/test_async.rb +121 -0
- data/test/dbd/postgresql/test_blob.rb +36 -0
- data/test/dbd/postgresql/test_bytea.rb +87 -0
- data/test/dbd/postgresql/test_ping.rb +10 -0
- data/test/dbd/postgresql/test_timestamp.rb +77 -0
- data/test/dbd/postgresql/test_transactions.rb +58 -0
- data/test/dbd/postgresql/testdbipg.rb +307 -0
- data/test/dbd/postgresql/up.sql +60 -0
- data/test/ts_dbd.rb +131 -0
- metadata +100 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
class TestPostgresPing < DBDConfig.testbase(:postgresql)
|
2
|
+
def test_ping
|
3
|
+
config = DBDConfig.get_config['postgresql']
|
4
|
+
dbh = DBI.connect("dbi:Pg:#{config['dbname']}", config['username'], config['password'])
|
5
|
+
assert dbh
|
6
|
+
assert dbh.ping
|
7
|
+
dbh.disconnect
|
8
|
+
assert_raises(DBI::InterfaceError) { dbh.ping }
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
class TestPostgresTimestamp < DBDConfig.testbase(:postgresql)
|
2
|
+
def get_tstamp
|
3
|
+
DateTime.parse("2008-03-08 10:39:01.012300")
|
4
|
+
end
|
5
|
+
|
6
|
+
# XXX DateTime is not allowed to modify the sec_fraction component, so this test is moot.
|
7
|
+
# perhaps we need to fix DateTime.
|
8
|
+
def skip_test_timestamp_altered_fraction
|
9
|
+
ts = nil
|
10
|
+
|
11
|
+
assert_nothing_raised do
|
12
|
+
@sth = @dbh.prepare("insert into timestamp_test (mytimestamp) values (?)")
|
13
|
+
ts = DateTime.parse(Time.now.to_s)
|
14
|
+
ts.sec_fraction = 22200000
|
15
|
+
@sth.execute(ts)
|
16
|
+
@sth.finish
|
17
|
+
end
|
18
|
+
|
19
|
+
assert_nothing_raised do
|
20
|
+
@sth = @dbh.prepare("select * from timestamp_test")
|
21
|
+
@sth.execute
|
22
|
+
row = @sth.fetch
|
23
|
+
@sth.finish
|
24
|
+
assert_equal ts.sec_fraction, row[0].sec_fraction
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_current_timestamp
|
29
|
+
assert @dbh
|
30
|
+
# syntax db-specific (e.g., "from dual", "...timestamp()", etc.)
|
31
|
+
ts = @dbh.select_one("SELECT CURRENT_TIMESTAMP")[0]
|
32
|
+
assert_kind_of DateTime, ts
|
33
|
+
assert_not_nil ts.sec_fraction
|
34
|
+
end
|
35
|
+
|
36
|
+
# Just like the 'general' test, but checking for fractional seconds
|
37
|
+
def test_timestamp_fractional
|
38
|
+
assert @dbh
|
39
|
+
@sth = nil
|
40
|
+
t = get_tstamp
|
41
|
+
assert_nothing_raised do
|
42
|
+
@sth = @dbh.prepare("insert into timestamp_test (mytimestamp) values (?)")
|
43
|
+
@sth.execute(t)
|
44
|
+
@sth.finish
|
45
|
+
end
|
46
|
+
|
47
|
+
assert_nothing_raised do
|
48
|
+
@sth = @dbh.prepare("select * from timestamp_test")
|
49
|
+
@sth.execute
|
50
|
+
row = @sth.fetch
|
51
|
+
assert_kind_of DateTime, row[0]
|
52
|
+
assert_equal t.year, row[0].year
|
53
|
+
assert_equal t.month, row[0].month
|
54
|
+
assert_equal t.day, row[0].day
|
55
|
+
assert_equal t.hour, row[0].hour
|
56
|
+
assert_equal t.min, row[0].min
|
57
|
+
assert_equal t.sec, row[0].sec
|
58
|
+
assert_not_nil row[0].sec_fraction
|
59
|
+
assert_equal t.sec_fraction, row[0].sec_fraction
|
60
|
+
@sth.finish
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Is our DBI::Timestamp equivalent to its canonical string literal
|
65
|
+
# form cast appropriately?
|
66
|
+
def test_timestamp_from_cast
|
67
|
+
assert @dbh
|
68
|
+
sql_ts = "SELECT CAST('2008-03-08 10:39:01.012300' AS TIMESTAMP)"
|
69
|
+
|
70
|
+
row = @dbh.select_one(sql_ts)
|
71
|
+
assert_not_nil row
|
72
|
+
assert_equal 1, row.size
|
73
|
+
|
74
|
+
assert_kind_of DateTime, row[0]
|
75
|
+
assert_equal row[0], get_tstamp
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class TestPostgresTransaction < DBDConfig.testbase(:postgresql)
|
2
|
+
def test_rollback
|
3
|
+
dbh = get_dbh
|
4
|
+
dbh["AutoCommit"] = false
|
5
|
+
@sth = dbh.prepare('insert into names (name, age) values (?, ?)')
|
6
|
+
@sth.execute("Foo", 51)
|
7
|
+
dbh.rollback
|
8
|
+
assert_equal 1, @sth.rows
|
9
|
+
@sth.finish
|
10
|
+
|
11
|
+
|
12
|
+
@sth = dbh.prepare('select name, age from names where name=?')
|
13
|
+
@sth.execute("Foo")
|
14
|
+
assert !@sth.fetch
|
15
|
+
@sth.finish
|
16
|
+
dbh.disconnect
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_commit
|
20
|
+
dbh = get_dbh
|
21
|
+
dbh["AutoCommit"] = false
|
22
|
+
@sth = dbh.prepare('insert into names (name, age) values (?, ?)')
|
23
|
+
@sth.execute("Foo", 51)
|
24
|
+
dbh.commit
|
25
|
+
assert_equal 1, @sth.rows
|
26
|
+
@sth.finish
|
27
|
+
|
28
|
+
@sth = dbh.prepare('select name, age from names where name=?')
|
29
|
+
@sth.execute("Foo")
|
30
|
+
row = @sth.fetch
|
31
|
+
assert row
|
32
|
+
assert_equal "Foo", row[0]
|
33
|
+
assert_equal 51, row[1]
|
34
|
+
@sth.finish
|
35
|
+
dbh.disconnect
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_select_transaction
|
39
|
+
# per bug #10466
|
40
|
+
dbh = get_dbh
|
41
|
+
dbh["AutoCommit"] = false
|
42
|
+
@sth = dbh.prepare('select * from test_insert(?, ?)');
|
43
|
+
@sth.execute("Foo", 51)
|
44
|
+
dbh.rollback
|
45
|
+
@sth.finish
|
46
|
+
|
47
|
+
@sth = dbh.prepare('select name, age from names where name=?')
|
48
|
+
@sth.execute("Foo")
|
49
|
+
assert !@sth.fetch
|
50
|
+
@sth.finish
|
51
|
+
dbh.disconnect
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_dbh
|
55
|
+
config = DBDConfig.get_config
|
56
|
+
DBI.connect("dbi:Pg:#{config['postgresql']['dbname']}", config['postgresql']['username'], config['postgresql']['password'])
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
require 'dbd/Pg'
|
2
|
+
|
3
|
+
module DBI
|
4
|
+
class ForcedError < ::DBI::Error
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
######################################################################
|
9
|
+
# Test the PostgreSql DBD driver. This test exercises options
|
10
|
+
# difficult to test through the standard DBI interface.
|
11
|
+
#
|
12
|
+
class TestDbdPostgres < DBDConfig.testbase(:postgresql)
|
13
|
+
|
14
|
+
# FIXME this is a feature that should be there, but currently isn't.
|
15
|
+
# def test_connect
|
16
|
+
# dbd = get_dbd
|
17
|
+
# assert_not_nil dbd.connection
|
18
|
+
# assert_equal 'localhost', dbd.connection.host
|
19
|
+
# assert_equal 'erikh', dbd.connection.user
|
20
|
+
# assert_equal 'rubytest', dbd.connection.db
|
21
|
+
# assert_equal 5432, dbd.connection.port
|
22
|
+
# ensure
|
23
|
+
# dbd.disconnect if dbd
|
24
|
+
# end
|
25
|
+
|
26
|
+
# this monkeypatch is used for the following test... NEVER integrate this into DBI proper.
|
27
|
+
class DBI::StatementHandle < DBI::Handle
|
28
|
+
def stmt_name
|
29
|
+
@handle.instance_variable_get(:@stmt_name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_database_name
|
34
|
+
assert_nothing_raised do
|
35
|
+
assert_equal DBDConfig.get_config[dbtype]['dbname'], @dbh.database_name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_enum_type
|
40
|
+
assert_nothing_raised do
|
41
|
+
assert(@dbh.convert_types)
|
42
|
+
@sth = @dbh.prepare("insert into enum_type_test values (?)")
|
43
|
+
@sth.execute("one")
|
44
|
+
@sth.finish
|
45
|
+
|
46
|
+
@sth = @dbh.prepare("select foo from enum_type_test")
|
47
|
+
@sth.execute
|
48
|
+
assert_equal(@sth.fetch, ['one'])
|
49
|
+
@sth.finish
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_statement_finish_deallocates_sth
|
54
|
+
assert_nothing_raised do
|
55
|
+
@sth = @dbh.prepare("select * from names")
|
56
|
+
@sth.execute
|
57
|
+
sth_internal_name = @sth.stmt_name
|
58
|
+
assert(sth_internal_name)
|
59
|
+
assert(!sth_internal_name.empty?)
|
60
|
+
@sth.finish
|
61
|
+
|
62
|
+
# at this point, the statement name should no longer exist
|
63
|
+
#
|
64
|
+
# XXX this is a potentially horrible way of doing it since it'll
|
65
|
+
# create another prepared statement, but *at this time*, I don't
|
66
|
+
# see any drawbacks and the alternative is considerably uglier.
|
67
|
+
|
68
|
+
@sth = @dbh.prepare("select count(*) from pg_prepared_statements where name = ?")
|
69
|
+
@sth.execute(sth_internal_name)
|
70
|
+
assert_equal([0], @sth.fetch)
|
71
|
+
@sth.finish
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_binding
|
76
|
+
assert(@dbh["pg_native_binding"])
|
77
|
+
|
78
|
+
assert_raises(DBI::ProgrammingError) do
|
79
|
+
@sth = @dbh.prepare("select * from names where age IS NOT ?")
|
80
|
+
@sth.execute("NULL")
|
81
|
+
@sth.finish
|
82
|
+
end
|
83
|
+
|
84
|
+
assert_nothing_raised do
|
85
|
+
@dbh["pg_native_binding"] = false
|
86
|
+
@sth = @dbh.prepare("select * from names where age IS NOT ? order by age")
|
87
|
+
@sth.execute("NULL")
|
88
|
+
assert_equal(
|
89
|
+
[
|
90
|
+
["Joe", 19],
|
91
|
+
["Bob", 21],
|
92
|
+
["Jim", 30],
|
93
|
+
],
|
94
|
+
@sth.fetch_all
|
95
|
+
)
|
96
|
+
|
97
|
+
@sth.finish
|
98
|
+
|
99
|
+
@sth = @dbh.prepare("select * from names where age = ?")
|
100
|
+
@sth.execute(19)
|
101
|
+
assert_equal(
|
102
|
+
[
|
103
|
+
["Joe", 19]
|
104
|
+
],
|
105
|
+
@sth.fetch_all
|
106
|
+
)
|
107
|
+
|
108
|
+
@sth.finish
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_function_multiple_return_values
|
113
|
+
@sth = @dbh.prepare("SELECT age, select_subproperty(age, NULL), select_subproperty(age, 1) FROM names WHERE age = 19")
|
114
|
+
@sth.execute
|
115
|
+
assert_equal([[19, nil, 19]], @sth.fetch_all)
|
116
|
+
@sth.finish
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_columns
|
120
|
+
assert_equal(
|
121
|
+
[
|
122
|
+
{
|
123
|
+
:name =>"age",
|
124
|
+
:default =>nil,
|
125
|
+
:primary =>nil,
|
126
|
+
:scale =>nil,
|
127
|
+
:sql_type =>4,
|
128
|
+
:nullable =>true,
|
129
|
+
:indexed =>false,
|
130
|
+
:precision =>4,
|
131
|
+
:type_name =>"integer",
|
132
|
+
:unique =>nil,
|
133
|
+
:array_of_type =>nil
|
134
|
+
},
|
135
|
+
{
|
136
|
+
:name =>"name",
|
137
|
+
:default =>nil,
|
138
|
+
:primary =>nil,
|
139
|
+
:scale =>nil,
|
140
|
+
:sql_type =>12,
|
141
|
+
:nullable =>true,
|
142
|
+
:indexed =>false,
|
143
|
+
:precision =>255,
|
144
|
+
:type_name =>"character varying",
|
145
|
+
:unique =>nil,
|
146
|
+
:array_of_type =>nil
|
147
|
+
}
|
148
|
+
], @dbh.columns("names").sort_by { |x| x["name"] })
|
149
|
+
|
150
|
+
assert_equal(2, @dbh.columns("names").size) # make sure this works before the search path change
|
151
|
+
|
152
|
+
assert_equal(0, @dbh.columns("tbl").size) # tbl doesn't exist in public
|
153
|
+
|
154
|
+
@dbh.do('SET search_path TO schema1,schema2,"$user",public')
|
155
|
+
|
156
|
+
assert_equal(1, @dbh.columns('tbl').size);
|
157
|
+
assert_equal(
|
158
|
+
[
|
159
|
+
{
|
160
|
+
:name =>"foo",
|
161
|
+
:default =>nil,
|
162
|
+
:primary =>nil,
|
163
|
+
:scale =>nil,
|
164
|
+
:sql_type =>4,
|
165
|
+
:nullable =>true,
|
166
|
+
:indexed =>false,
|
167
|
+
:precision =>4,
|
168
|
+
:type_name =>"integer",
|
169
|
+
:unique =>nil,
|
170
|
+
:array_of_type =>nil
|
171
|
+
|
172
|
+
}
|
173
|
+
],
|
174
|
+
@dbh.columns('tbl')
|
175
|
+
)
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_statement_name_uniqueness
|
180
|
+
5000.times do
|
181
|
+
begin
|
182
|
+
@dbh.prepare('SELECT 1').execute()
|
183
|
+
raise DBI::ForcedError
|
184
|
+
sth.finish # never reached
|
185
|
+
rescue DBI::ProgrammingError => e
|
186
|
+
# ERROR: prepared statement "ruby-dbi:Pg:-604926268" already exists
|
187
|
+
# This should never happen
|
188
|
+
raise e
|
189
|
+
rescue DBI::ForcedError
|
190
|
+
# no-op
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_connect_errors
|
196
|
+
dbd = nil
|
197
|
+
ex = assert_raises(DBI::OperationalError) {
|
198
|
+
dbd = DBI::DBD::Pg::Database.new('rubytest:1234', 'jim', nil, {})
|
199
|
+
}
|
200
|
+
ex = assert_raises(DBI::OperationalError) {
|
201
|
+
dbd = DBI::DBD::Pg::Database.new('bad_db_name', 'jim', nil, {})
|
202
|
+
}
|
203
|
+
|
204
|
+
# this corresponds to the test_parse_url_expected_errors test in tc_dbi.rb
|
205
|
+
assert_raises(DBI::InterfaceError) do
|
206
|
+
DBI.connect("dbi:Pg").disconnect
|
207
|
+
end
|
208
|
+
|
209
|
+
ensure
|
210
|
+
dbd.disconnect if dbd
|
211
|
+
end
|
212
|
+
|
213
|
+
def skip_test_type_map
|
214
|
+
dbd = get_dbd
|
215
|
+
def dbd.type_map
|
216
|
+
@type_map
|
217
|
+
end
|
218
|
+
assert dbd.type_map
|
219
|
+
assert_equal 21, dbd.convert("21", 23)
|
220
|
+
assert_equal "21", dbd.convert("21", 1043)
|
221
|
+
assert_equal 21.5, dbd.convert("21.5", 701)
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_simple_command
|
225
|
+
dbd = get_dbd
|
226
|
+
res = dbd.do("INSERT INTO names (name, age) VALUES('Dan', 16)")
|
227
|
+
assert_equal 1, res
|
228
|
+
|
229
|
+
@sth = get_dbi.prepare("SELECT name FROM names WHERE age=16")
|
230
|
+
@sth.execute
|
231
|
+
assert @sth.fetchable?
|
232
|
+
# XXX FIXME This is a bug in the DBD. #rows should equal 1 for select statements.
|
233
|
+
assert_equal 0, @sth.rows
|
234
|
+
ensure
|
235
|
+
dbd.do("DELETE FROM names WHERE age < 20")
|
236
|
+
dbd.disconnect if dbd
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_bad_command
|
240
|
+
dbd = get_dbd
|
241
|
+
assert_raises(DBI::ProgrammingError) {
|
242
|
+
dbd.do("INSERT INTO bad_table (name, age) VALUES('Dave', 12)")
|
243
|
+
}
|
244
|
+
ensure
|
245
|
+
dbd.disconnect if dbd
|
246
|
+
end
|
247
|
+
|
248
|
+
def test_query_single
|
249
|
+
dbd = get_dbi
|
250
|
+
res = dbd.prepare("SELECT name, age FROM names WHERE age=21;")
|
251
|
+
assert res
|
252
|
+
res.execute
|
253
|
+
fields = res.column_info
|
254
|
+
assert_equal 2, fields.length
|
255
|
+
assert_equal 'name', fields[0]['name']
|
256
|
+
assert_equal 'varchar', fields[0]['type_name']
|
257
|
+
assert_equal 'age', fields[1]['name']
|
258
|
+
assert_equal 'int4', fields[1]['type_name']
|
259
|
+
|
260
|
+
row = res.fetch
|
261
|
+
|
262
|
+
assert_equal 'Bob', row[0]
|
263
|
+
assert_equal 21, row[1]
|
264
|
+
|
265
|
+
row = res.fetch
|
266
|
+
assert_nil row
|
267
|
+
|
268
|
+
res.finish
|
269
|
+
ensure
|
270
|
+
dbd.disconnect if dbd
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_query_multi
|
274
|
+
dbd = get_dbd
|
275
|
+
res = dbd.prepare("SELECT name, age FROM names WHERE age > 20;")
|
276
|
+
|
277
|
+
expected_list = ['Jim', 'Bob', 'Charlie']
|
278
|
+
res.execute
|
279
|
+
while row=res.fetch
|
280
|
+
expected = expected_list.shift
|
281
|
+
assert_equal expected, row[0]
|
282
|
+
end
|
283
|
+
|
284
|
+
res.finish
|
285
|
+
ensure
|
286
|
+
dbd.disconnect if dbd
|
287
|
+
end
|
288
|
+
|
289
|
+
def test_tables_call
|
290
|
+
# per bug #1082, views do not show up in tables listing.
|
291
|
+
assert get_dbi.tables.include?("view_names")
|
292
|
+
end
|
293
|
+
|
294
|
+
def get_dbi
|
295
|
+
config = DBDConfig.get_config
|
296
|
+
DBI.connect("dbi:Pg:#{config['postgresql']['dbname']}", config['postgresql']['username'], config['postgresql']['password'])
|
297
|
+
end
|
298
|
+
|
299
|
+
def get_dbd
|
300
|
+
config = DBDConfig.get_config['postgresql']
|
301
|
+
result = DBI::DBD::Pg::Database.new(config['dbname'], config['username'], config['password'], {})
|
302
|
+
result['AutoCommit'] = true
|
303
|
+
result
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# --------------------------------------------------------------------
|