DBrb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/DB.rb +245 -0
- data/tests/DbrbTest.rb +204 -0
- metadata +46 -0
data/lib/DB.rb
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# DBrb - database access library for Ruby.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2006 Tim Becker
|
|
4
|
+
#
|
|
5
|
+
# Released under the same terms as Ruby
|
|
6
|
+
# (http://www.ruby-lang.org/en/LICENSE.txt)
|
|
7
|
+
#
|
|
8
|
+
#
|
|
9
|
+
# see DBrb for documentation.
|
|
10
|
+
|
|
11
|
+
require 'dbi'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
#:title: DBrb : RDoc Documenation
|
|
15
|
+
|
|
16
|
+
# +DBrb+ is a wrapper to facilitate working with {Ruby
|
|
17
|
+
# DBI}[http://sourceforge.net/projects/ruby-dbi]. The intention is to
|
|
18
|
+
# make the most commonly used DB functionality (as determined by me) easy and
|
|
19
|
+
# intuitive to use.
|
|
20
|
+
#
|
|
21
|
+
# === Examples
|
|
22
|
+
#
|
|
23
|
+
# db = DBrb.new("my_dbi_driver_string", "usr", "credentials")
|
|
24
|
+
#
|
|
25
|
+
# # return single value or array if a single row is selected
|
|
26
|
+
# i = db.sql("select max(id) from some_table")
|
|
27
|
+
# name, last_name = db.sql("SELECT name, last_name FROM some_name WHERE id = ?", 1000)
|
|
28
|
+
#
|
|
29
|
+
# should the two examples above return more than one row, the first
|
|
30
|
+
# one is used.
|
|
31
|
+
#
|
|
32
|
+
# db.sql("SELECT name, last_name FROM some_name WHERE id < ?", 1000) do |row|
|
|
33
|
+
# puts "#{row.name} #{row.last_name}"
|
|
34
|
+
# # in case of conflicts with existing methods, you can use:
|
|
35
|
+
# row["last_name"]
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# The row that the +sql+ iterates over is the standard DBI::Row, which
|
|
39
|
+
# has been supplemented with accessor methods named after the columns
|
|
40
|
+
# in case these don't conflict with existing methods.
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DBrb
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
def initialize driver, usr=nil, pwd=nil, params=nil
|
|
47
|
+
@driver=driver
|
|
48
|
+
@usr=usr
|
|
49
|
+
@pwd=pwd
|
|
50
|
+
@params=params
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pstatements = {}
|
|
54
|
+
|
|
55
|
+
if block_given?
|
|
56
|
+
yield self
|
|
57
|
+
self.close
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# +sql+ is the method that does all the work. The only required
|
|
63
|
+
# parameter is +stmt+, which passes a string with the SQL
|
|
64
|
+
# statement to be executed. The optional parameters in +args+
|
|
65
|
+
# get bound to '?'-placeholders in the statement.
|
|
66
|
+
#
|
|
67
|
+
# This method can be called in several ways.
|
|
68
|
+
# * Statements not returning results:
|
|
69
|
+
#
|
|
70
|
+
# db.sql "UPDATE sometable SET somevalue=somevalue+1"
|
|
71
|
+
#
|
|
72
|
+
# * If the statement returns a single value, that value is returned by the method call:
|
|
73
|
+
#
|
|
74
|
+
#
|
|
75
|
+
# value = db.sql "SELECT value FROM sometable LIMIT 1"
|
|
76
|
+
#
|
|
77
|
+
# * Statements returning several values in a single row:
|
|
78
|
+
# first_name, last_name =
|
|
79
|
+
# db.sql "SELECT first, last
|
|
80
|
+
# FROM sometable
|
|
81
|
+
# LIMIT 1"
|
|
82
|
+
#
|
|
83
|
+
# (It should go without saying: if a single row result is expected like in the
|
|
84
|
+
# examples above but the statement returns several rows, the first one is used.)
|
|
85
|
+
#
|
|
86
|
+
# * If a block is passed to the method, it can be used to iterate over the results.
|
|
87
|
+
#
|
|
88
|
+
# db.sql("SELECT first, last FROM sometable WHERE last=?", "Smith") do |row|
|
|
89
|
+
# puts "Name: #{row.first} #{row.last}
|
|
90
|
+
# end
|
|
91
|
+
#
|
|
92
|
+
# * or, in case the resulting rows only contain a single column :
|
|
93
|
+
#
|
|
94
|
+
# db.sql "SELECT first FROM sometable" do |firstname|
|
|
95
|
+
# puts "Hello #{firstname}!"
|
|
96
|
+
# end
|
|
97
|
+
|
|
98
|
+
def sql(stmt, *args, &block) #:yields: row
|
|
99
|
+
#optional args and an optional block
|
|
100
|
+
_sql_internal(stmt,false,*args, &block)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Similar to the sql method. This method is meant for `INSERT`
|
|
104
|
+
# and `UPDATE` statements and returns the RPC (row processed
|
|
105
|
+
# count), i.e. the number of rows affected by the statement.
|
|
106
|
+
#
|
|
107
|
+
|
|
108
|
+
def sql_count (stmt, *args, &block)
|
|
109
|
+
_sql_internal(stmt,true,*args,&block)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Closes all resources used by this object instance.
|
|
113
|
+
def close
|
|
114
|
+
@pstatements.each_value {|pstmt|
|
|
115
|
+
pstmt[0].finish
|
|
116
|
+
}
|
|
117
|
+
@pstatements = {}
|
|
118
|
+
@db.disconnect
|
|
119
|
+
@db = nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# _Debug_ _method_. This method dumps statistics about how often
|
|
123
|
+
# each sql statement was executed by this instance. Statistics
|
|
124
|
+
# are reset after calls to `close`
|
|
125
|
+
def dumpStmtStats
|
|
126
|
+
@pstatements.each_pair { |stmt, arr|
|
|
127
|
+
puts "#{stmt} : #{arr[1]}"
|
|
128
|
+
} if @pstatements
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def to_s
|
|
132
|
+
"DBrb: #{@driver}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
protected
|
|
136
|
+
|
|
137
|
+
# Returns a (cached) reference to the DBI driver. Default
|
|
138
|
+
# behaviour is to create a single connection, keeping it cached
|
|
139
|
+
# and reusing it for all DB statements executed by this object
|
|
140
|
+
# instance.
|
|
141
|
+
#
|
|
142
|
+
# This method is used internally by `sql` and could be
|
|
143
|
+
# overridden in case you require different behaviour, e.g.
|
|
144
|
+
# creating a new connection for each executed statement or
|
|
145
|
+
# maintaining several connections.
|
|
146
|
+
|
|
147
|
+
def get_db
|
|
148
|
+
return @db if @db && @db.connected?
|
|
149
|
+
@db = DBI.connect(@driver, @usr, @pwd, @params)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def _sql_internal (stmt, count, *args) #:yields: row
|
|
153
|
+
#optional args and an optional block
|
|
154
|
+
pstmt = get_pstmt stmt
|
|
155
|
+
pstmt.execute *args
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
ret_val = nil
|
|
159
|
+
begin
|
|
160
|
+
if block_given?
|
|
161
|
+
pstmt.each { |row|
|
|
162
|
+
if row.length==1
|
|
163
|
+
yield row[0]
|
|
164
|
+
else
|
|
165
|
+
yield row
|
|
166
|
+
end
|
|
167
|
+
}
|
|
168
|
+
else
|
|
169
|
+
unless count
|
|
170
|
+
ret_val = pstmt.fetch if pstmt.fetchable?
|
|
171
|
+
ret_val = ret_val[0] if ret_val && ret_val.length==1
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
end
|
|
175
|
+
rescue NoMethodError
|
|
176
|
+
# calling fetch for mysql drivers on stmts returning
|
|
177
|
+
# no rows (INSERT...) is fucked.
|
|
178
|
+
|
|
179
|
+
# Message: <"undefined method `fetch_row' for nil:NilClass">
|
|
180
|
+
# ---Backtrace---
|
|
181
|
+
# /usr/local/lib/ruby/site_ruby/1.8/DBD/Mysql/Mysql.rb:424:in `fetch'
|
|
182
|
+
# /usr/local/lib/ruby/site_ruby/1.8/dbi/dbi.rb:811:in `fetch'
|
|
183
|
+
# /usr/local/lib/ruby/site_ruby/1.8/dbi/dbi.rb:836:in `each'
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
ret_val = pstmt.rows if count
|
|
187
|
+
pstmt.cancel # clean up
|
|
188
|
+
return ret_val
|
|
189
|
+
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
private
|
|
195
|
+
|
|
196
|
+
# Maintains a cache of all prepared statments. Each prepared
|
|
197
|
+
# statement executed by `sql` is prepared only once and reused in
|
|
198
|
+
# case the same sql statement is executed a second time.
|
|
199
|
+
#
|
|
200
|
+
# Prepared statements are disposed when `close` is called.
|
|
201
|
+
|
|
202
|
+
def get_pstmt stmt #:doc:
|
|
203
|
+
#@pstatements[stmt] ||= get_db.prepare stmt
|
|
204
|
+
@pstatements[stmt] ||= [nil,0]
|
|
205
|
+
@pstatements[stmt][1] += 1
|
|
206
|
+
@pstatements[stmt][0] ||= get_db.prepare stmt
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
end
|
|
211
|
+
module DBI
|
|
212
|
+
# augments DBI::Row with a +method_missing+ method that lets the
|
|
213
|
+
# row behave as if it had accessor methods for each colum.
|
|
214
|
+
class Row
|
|
215
|
+
def method_missing meth_id
|
|
216
|
+
return self[meth_id]
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
if $0==__FILE__
|
|
222
|
+
sql = DBrb.new('DBI:PG:stats2', 'test', 'test')
|
|
223
|
+
row = sql.sql("SELECT * FROM post WHERE nick=? LIMIT 10", "muppet")
|
|
224
|
+
puts row.nick
|
|
225
|
+
row = sql.sql("SELECT * FROM post WHERE nick=? LIMIT 10", "Philo")
|
|
226
|
+
puts row.nick
|
|
227
|
+
puts sql.sql("SELECT count(*) from post")
|
|
228
|
+
|
|
229
|
+
puts sql.sql("SELECT count(*) from post")
|
|
230
|
+
|
|
231
|
+
puts sql.sql("SELECT nick from post limit 20") {|nick|
|
|
232
|
+
puts nick
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
puts sql.sql("SELECT nick, url from post limit 20") {|row|
|
|
236
|
+
puts row.nick, row.url
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
sql.dumpStmtStats
|
|
240
|
+
|
|
241
|
+
sql.close
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
end
|
data/tests/DbrbTest.rb
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
require 'DB'
|
|
4
|
+
require 'test/unit'
|
|
5
|
+
|
|
6
|
+
# Since each test needs to be executed for several different database
|
|
7
|
+
# engines, DBrbTest maintains an array of instances, each connected to
|
|
8
|
+
# one of the backends. the `test_...` methods iterate over the
|
|
9
|
+
# connections and call the actual test methods, which are named like the
|
|
10
|
+
# `test_...` methods without the `test_` part, with each DBrb instance.
|
|
11
|
+
# E.g. test_create_test_table calls `create_test_table` once with each
|
|
12
|
+
# connection.
|
|
13
|
+
|
|
14
|
+
class DbrbTest < Test::Unit::TestCase
|
|
15
|
+
|
|
16
|
+
# These are the DB logins to test. I'm currently using a
|
|
17
|
+
# postgres server, with DB, user and pwd all set to `test`. In
|
|
18
|
+
# case you'd like to test other databases, just add the
|
|
19
|
+
# necessary connection parameters.
|
|
20
|
+
|
|
21
|
+
@@credentials = [
|
|
22
|
+
['DBI:PG:test', 'test', 'test'],
|
|
23
|
+
['DBI:Mysql:test;socket=/var/run/mysqld/mysqld.sock;database=test', 'test', 'test'] # there must be a better way.
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
def setup
|
|
27
|
+
@time = Time.new
|
|
28
|
+
@connections = []
|
|
29
|
+
@@credentials.each { |cred|
|
|
30
|
+
@connections.push DBrb.new(*cred)
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def teardown
|
|
35
|
+
@connections.each {|conn|
|
|
36
|
+
conn.close
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Each test case follows the same scheme: the same tests are
|
|
41
|
+
# executed for each database and wrapped in a
|
|
42
|
+
# `assert_nothing_raised` assertion that fails in case of
|
|
43
|
+
# database exceptions.
|
|
44
|
+
|
|
45
|
+
def each_ok
|
|
46
|
+
@connections.each {|conn|
|
|
47
|
+
assert_nothing_raised {
|
|
48
|
+
yield conn
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#Set up the example table for the tests.
|
|
54
|
+
def test_a_create_test_table
|
|
55
|
+
each_ok { |conn|
|
|
56
|
+
# This should be as generic as possible to
|
|
57
|
+
# accomodate different DB's
|
|
58
|
+
conn.sql "
|
|
59
|
+
CREATE TABLE test_table (
|
|
60
|
+
col_vc VARCHAR(50),
|
|
61
|
+
col_num NUMERIC,
|
|
62
|
+
col_bool BOOLEAN,
|
|
63
|
+
col_double DOUBLE PRECISION,
|
|
64
|
+
col_int INTEGER,
|
|
65
|
+
col_date DATE,
|
|
66
|
+
col_time TIME,
|
|
67
|
+
col_timestamp TIMESTAMP
|
|
68
|
+
)"
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_b_insert_values
|
|
75
|
+
each_ok {|con|
|
|
76
|
+
%w{zero one two three four five six seven eight
|
|
77
|
+
nine ten}.each_with_index {|num, i|
|
|
78
|
+
con.sql("INSERT INTO test_table
|
|
79
|
+
VALUES (?,?,?,?,?,?,?,?)",
|
|
80
|
+
num, i, i%2==0,"#{i}.#{i}".to_f,i, @time, @time, @time)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_c_select_values
|
|
88
|
+
each_ok { |db|
|
|
89
|
+
|
|
90
|
+
tst = SelectTest.new db, self
|
|
91
|
+
tst.do "one", "SELECT col_vc FROM test_table WHERE col_num=?", 1
|
|
92
|
+
tst.do "2", "SELECT col_num FROM test_table WHERE col_vc=?", "two"
|
|
93
|
+
# postgres: 0 mysql: false
|
|
94
|
+
#tst.do 0, "SELECT col_bool FROM test_table WHERE col_vc=?", "three"
|
|
95
|
+
|
|
96
|
+
#postgres 4.4 mysql "4.4"
|
|
97
|
+
#tst.do 4.4, "SELECT col_double FROM test_table WHERE col_vc=?", "four"
|
|
98
|
+
tst.do 5, "SELECT col_int FROM test_table WHERE col_vc=?", "five"
|
|
99
|
+
tst.do ["six",6],"SELECT col_vc, col_int FROM test_table WHERE col_double=?", 6.6
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Date handling seems to be very implementation specific...
|
|
104
|
+
def test_c_select_date
|
|
105
|
+
each_ok { |db|
|
|
106
|
+
date = db.sql("SELECT col_date FROM test_table LIMIT 1")
|
|
107
|
+
|
|
108
|
+
assert_equal(@time.year, date.year, db.to_s)
|
|
109
|
+
assert_equal(@time.mon, date.month, db.to_s)
|
|
110
|
+
assert_equal(@time.day, date.day, db.to_s)
|
|
111
|
+
|
|
112
|
+
time = db.sql("SELECT col_time FROM test_table LIMIT 1")
|
|
113
|
+
|
|
114
|
+
if time.class != DBI::Time
|
|
115
|
+
# postgres driver doesn't return DBI::Time
|
|
116
|
+
puts "\nWarning, #{db} returning '#{time.class}' instead of DBI::TIME"
|
|
117
|
+
assert_equal(@time.strftime("%H:%M:%S"), time, db)
|
|
118
|
+
else
|
|
119
|
+
assert_equal(@time.hour, time.hour, db)
|
|
120
|
+
assert_equal(@time.min, time.min,db)
|
|
121
|
+
assert_equal(@time.sec, time.sec,db)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
timestamp = db.sql("SELECT col_timestamp FROM test_table LIMIT 1")
|
|
125
|
+
|
|
126
|
+
if timestamp.class != DBI::Timestamp
|
|
127
|
+
# mysql driver doesn't return DBI::Timestamp...
|
|
128
|
+
puts "\nWarning, #{db} returning '#{timestamp.class}' instead of DBI::TIMESTAMP"
|
|
129
|
+
assert_equal(@time.strftime("%Y-%m-%d %H:%M:%S"), timestamp, db.to_s)
|
|
130
|
+
else
|
|
131
|
+
assert_equal(@time.year, timestamp.year, db.to_s)
|
|
132
|
+
assert_equal(@time.mon, timestamp.month, db.to_s)
|
|
133
|
+
assert_equal(@time.day, timestamp.day, db.to_s)
|
|
134
|
+
assert_equal(@time.hour, timestamp.hour, db.to_s)
|
|
135
|
+
assert_equal(@time.min, timestamp.min, db.to_s)
|
|
136
|
+
assert_equal(@time.sec, timestamp.sec, db.to_s)
|
|
137
|
+
end
|
|
138
|
+
}
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def test_c_select_with_block
|
|
142
|
+
each_ok { |db|
|
|
143
|
+
comp_arr = [0,1,2,3,4,5,6,7,8,9,10]
|
|
144
|
+
|
|
145
|
+
arr = []
|
|
146
|
+
db.sql "SELECT col_int FROM test_table ORDER BY col_int" do |i|
|
|
147
|
+
arr.push i
|
|
148
|
+
end
|
|
149
|
+
assert_equal comp_arr, arr
|
|
150
|
+
|
|
151
|
+
arr=[]
|
|
152
|
+
|
|
153
|
+
db.sql "SELECT col_int, col_date FROM test_table ORDER BY col_int" do |row|
|
|
154
|
+
arr.push row.col_int
|
|
155
|
+
end
|
|
156
|
+
assert_equal comp_arr, arr
|
|
157
|
+
}
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_d_update_values
|
|
162
|
+
each_ok do |db|
|
|
163
|
+
db.sql "UPDATE test_table SET col_int = col_int+1"
|
|
164
|
+
comp_arr = [1,2,3,4,5,6,7,8,9,10,11]
|
|
165
|
+
|
|
166
|
+
arr = []
|
|
167
|
+
db.sql "SELECT col_int FROM test_table ORDER BY col_int" do |i|
|
|
168
|
+
arr.push i
|
|
169
|
+
end
|
|
170
|
+
assert_equal comp_arr, arr
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def test_e_count
|
|
175
|
+
each_ok do |db|
|
|
176
|
+
c = db.sql_count "UPDATE test_table SET col_int = col_int+2"
|
|
177
|
+
assert_equal(11, c, db)
|
|
178
|
+
|
|
179
|
+
c = db.sql_count "SELECT * FROM test_table" do |row|
|
|
180
|
+
arr=row
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
assert(c==0||c==11, db)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
#Drop the test table
|
|
188
|
+
def test_z_drop_test_table
|
|
189
|
+
each_ok {|con|
|
|
190
|
+
con.sql "DROP TABLE test_table"
|
|
191
|
+
}
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
class SelectTest
|
|
197
|
+
def initialize db, tcase
|
|
198
|
+
@db=db
|
|
199
|
+
@tcase=tcase
|
|
200
|
+
end
|
|
201
|
+
def do val, sql, *args
|
|
202
|
+
@tcase.assert_equal(val, @db.sql(sql, *args), @db.to_s)
|
|
203
|
+
end
|
|
204
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
rubygems_version: 0.8.11
|
|
3
|
+
specification_version: 1
|
|
4
|
+
name: DBrb
|
|
5
|
+
version: !ruby/object:Gem::Version
|
|
6
|
+
version: 0.1.0
|
|
7
|
+
date: 2006-05-14 00:00:00 +02:00
|
|
8
|
+
summary: Easier access to databases.
|
|
9
|
+
require_paths:
|
|
10
|
+
- lib
|
|
11
|
+
email: tim@kuriositaet.de
|
|
12
|
+
homepage: http://www.kuriositaet.de/ruby/dbrb
|
|
13
|
+
rubyforge_project:
|
|
14
|
+
description: DBrb is a database access layer meant to be easier and more consistant to use than ruby DBI. Currently, and for the forseeable future it's implemented as a wrapper to the DBI lib.
|
|
15
|
+
autorequire: DB.rb
|
|
16
|
+
default_executable:
|
|
17
|
+
bindir: bin
|
|
18
|
+
has_rdoc: true
|
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
- - ">"
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: 0.0.0
|
|
24
|
+
version:
|
|
25
|
+
platform: ruby
|
|
26
|
+
signing_key:
|
|
27
|
+
cert_chain:
|
|
28
|
+
authors:
|
|
29
|
+
- Tim Becker
|
|
30
|
+
files:
|
|
31
|
+
- tests/DbrbTest.rb
|
|
32
|
+
- lib/DB.rb
|
|
33
|
+
test_files: []
|
|
34
|
+
|
|
35
|
+
rdoc_options: []
|
|
36
|
+
|
|
37
|
+
extra_rdoc_files: []
|
|
38
|
+
|
|
39
|
+
executables: []
|
|
40
|
+
|
|
41
|
+
extensions: []
|
|
42
|
+
|
|
43
|
+
requirements:
|
|
44
|
+
- DBI
|
|
45
|
+
dependencies: []
|
|
46
|
+
|