ruby-oci8 1.0.2-i386-mswin32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/ChangeLog +569 -0
  2. data/Makefile +51 -0
  3. data/NEWS +322 -0
  4. data/README +415 -0
  5. data/VERSION +1 -0
  6. data/dist-files +70 -0
  7. data/doc/api.en.html +527 -0
  8. data/doc/api.en.rd +554 -0
  9. data/doc/api.ja.html +525 -0
  10. data/doc/api.ja.rd +557 -0
  11. data/doc/manual.css +35 -0
  12. data/ext/oci8/oci8lib.so +0 -0
  13. data/lib/DBD/OCI8/OCI8.rb +549 -0
  14. data/lib/oci8.rb +1605 -0
  15. data/lib/oci8.rb.in +1605 -0
  16. data/metaconfig +142 -0
  17. data/pre-distclean.rb +7 -0
  18. data/ruby-oci8.gemspec +54 -0
  19. data/ruby-oci8.spec +62 -0
  20. data/setup.rb +1331 -0
  21. data/support/README +4 -0
  22. data/support/runit/assert.rb +281 -0
  23. data/support/runit/cui/testrunner.rb +101 -0
  24. data/support/runit/error.rb +4 -0
  25. data/support/runit/method_mappable.rb +20 -0
  26. data/support/runit/robserver.rb +25 -0
  27. data/support/runit/setuppable.rb +15 -0
  28. data/support/runit/teardownable.rb +16 -0
  29. data/support/runit/testcase.rb +113 -0
  30. data/support/runit/testfailure.rb +25 -0
  31. data/support/runit/testresult.rb +121 -0
  32. data/support/runit/testsuite.rb +43 -0
  33. data/support/runit/version.rb +3 -0
  34. data/test/README +4 -0
  35. data/test/config.rb +129 -0
  36. data/test/test_all.rb +43 -0
  37. data/test/test_bind_raw.rb +53 -0
  38. data/test/test_bind_time.rb +191 -0
  39. data/test/test_break.rb +81 -0
  40. data/test/test_clob.rb +101 -0
  41. data/test/test_connstr.rb +80 -0
  42. data/test/test_dbi.rb +317 -0
  43. data/test/test_dbi_clob.rb +56 -0
  44. data/test/test_describe.rb +137 -0
  45. data/test/test_metadata.rb +243 -0
  46. data/test/test_oci8.rb +273 -0
  47. data/test/test_oradate.rb +263 -0
  48. data/test/test_oranumber.rb +149 -0
  49. metadata +97 -0
data/doc/manual.css ADDED
@@ -0,0 +1,35 @@
1
+ h1 {
2
+ color: #000080;
3
+ background: #e0e0ff;
4
+ border-color: #8080d0;
5
+ border-style: solid;
6
+ border-top-style: none;
7
+ border-left-style: none;
8
+ border-bottom-width: thick;
9
+ border-right-width: thin;
10
+ padding-top: 0.2em;
11
+ padding-bottom: 0.2em;
12
+ text-align: center
13
+ }
14
+ h2 {
15
+ color: #000080;
16
+ background: #e0e0ff;
17
+ border-color: #8080d0;
18
+ border-style: solid;
19
+ border-top-style: none;
20
+ border-left-style: none;
21
+ border-bottom-width: thick;
22
+ border-right-width: thin;
23
+ }
24
+ code {
25
+ color: blue
26
+ }
27
+ var {
28
+ color: brown
29
+ }
30
+ dt {
31
+ color: red
32
+ }
33
+ pre {
34
+ background: #e0ffff
35
+ }
Binary file
@@ -0,0 +1,549 @@
1
+ #
2
+ # DBD::OCI8
3
+ #
4
+ # Copyright (c) 2002-2007 KUBO Takehiro <kubo@jiubao.org>
5
+ #
6
+ # copied some code from DBD::Oracle.
7
+ # DBD::Oracle's copyright is as follows:
8
+ # --------------------- begin -------------------
9
+ #
10
+ # Copyright (c) 2001, 2002, 2003, 2004 Michael Neumann <mneumann@ntecs.de>
11
+ #
12
+ # All rights reserved.
13
+ #
14
+ # Redistribution and use in source and binary forms, with or without
15
+ # modification, are permitted provided that the following conditions
16
+ # are met:
17
+ # 1. Redistributions of source code must retain the above copyright
18
+ # notice, this list of conditions and the following disclaimer.
19
+ # 2. Redistributions in binary form must reproduce the above copyright
20
+ # notice, this list of conditions and the following disclaimer in the
21
+ # documentation and/or other materials provided with the distribution.
22
+ # 3. The name of the author may not be used to endorse or promote products
23
+ # derived from this software without specific prior written permission.
24
+ #
25
+ # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
26
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
27
+ # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
28
+ # THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
31
+ # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
32
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
33
+ # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
34
+ # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+ #
36
+ # --------------------- end -------------------
37
+
38
+ require 'oci8'
39
+
40
+ module DBI # :nodoc:
41
+ module DBD # :nodoc:
42
+ module OCI8
43
+
44
+ VERSION = "0.1"
45
+ USED_DBD_VERSION = "0.2"
46
+
47
+ module Util
48
+
49
+ ERROR_MAP = {
50
+ 1 => DBI::IntegrityError, # unique constraint violated
51
+ 900 => DBI::ProgrammingError, # invalid SQL statement
52
+ 904 => DBI::ProgrammingError, # invalid identifier
53
+ 905 => DBI::ProgrammingError, # missing keyword
54
+ 923 => DBI::ProgrammingError, # FROM keyword not found where expected
55
+ 936 => DBI::ProgrammingError, # missing expression
56
+ 942 => DBI::ProgrammingError, # table or view does not exist
57
+ 2290 => DBI::IntegrityError, # check constraint violated
58
+ 2291 => DBI::IntegrityError, # parent key not found
59
+ 2292 => DBI::IntegrityError, # child record found
60
+ 2293 => DBI::IntegrityError, # check constraint violated
61
+ }
62
+
63
+ def raise_dbierror(err) # :nodoc:
64
+ if err.is_a? OCIError
65
+ exc = ERROR_MAP[err.code] || DBI::DatabaseError
66
+ raise exc.new(err.message, err.code)
67
+ else
68
+ raise DBI::DatabaseError.new(err.message, -1)
69
+ end
70
+ rescue DBI::DatabaseError => exc
71
+ exc.set_backtrace(err.backtrace)
72
+ raise
73
+ end
74
+
75
+ def column_metadata_to_column_info(col)
76
+ sql_type, type_name, precision, scale =
77
+ case col.data_type
78
+ when :char
79
+ [SQL_CHAR, col.charset_form == :nchar ? "NCHAR" : "CHAR", col.data_size, nil]
80
+ when :varchar2
81
+ [SQL_VARCHAR, col.charset_form == :nchar ? "NVARCHAR2" : "VARCHAR2", col.data_size, nil]
82
+ when :raw
83
+ [SQL_VARBINARY, "RAW", col.data_size, nil]
84
+ when :long
85
+ [SQL_LONGVARCHAR, "LONG", 4000, nil]
86
+ when :long_raw
87
+ [SQL_LONGVARBINARY, "LONG RAW", 4000, nil]
88
+ when :clob
89
+ [SQL_CLOB, col.charset_form == :nchar ? "NCLOB" : "CLOB", 4000, nil]
90
+ when :blob
91
+ [SQL_BLOB, "BLOB", 4000, nil]
92
+ when :bfile
93
+ [SQL_BLOB, "BFILE", 4000, nil]
94
+ when :number
95
+ if col.scale == -127 && col.precision != 0
96
+ # To convert from binary to decimal precision, multiply n by 0.30103.
97
+ [SQL_FLOAT, "FLOAT", (col.precision * 0.30103).ceil , nil]
98
+ elsif col.precision == 0
99
+ # NUMBER or calculated value (eg. col * 1.2).
100
+ [SQL_NUMERIC, "NUMBER", 38, nil]
101
+ else
102
+ [SQL_NUMERIC, "NUMBER", col.precision, col.scale]
103
+ end
104
+ when :binary_float
105
+ # (23 * 0.30103).ceil => 7
106
+ [SQL_FLOAT, "BINARY_FLOAT", 7, nil]
107
+ when :binary_double
108
+ # (52 * 0.30103).ceil => 16
109
+ [SQL_DOUBLE, "BINARY_DOUBLE", 16, nil]
110
+ when :date
111
+ # yyyy-mm-dd hh:mi:ss
112
+ [SQL_DATE, "DATE", 19, nil]
113
+ when :timestamp
114
+ # yyyy-mm-dd hh:mi:ss.SSSS
115
+ [SQL_TIMESTAMP, "TIMESTAMP", 20 + col.fsprecision, nil]
116
+ when :timestamp_tz
117
+ # yyyy-mm-dd hh:mi:ss.SSSS +HH:MM
118
+ [SQL_TIMESTAMP, "TIMESTAMP WITH TIME ZONE", 27 + col.fsprecision, nil]
119
+ when :timestamp_ltz
120
+ # yyyy-mm-dd hh:mi:ss.SSSS
121
+ [SQL_TIMESTAMP, "TIMESTAMP WITH LOCAL TIME ZONE", 20 + col.fsprecision, nil]
122
+ when :interval_ym
123
+ # yyyy-mm
124
+ [SQL_OTHER, 'INTERVAL YEAR TO MONTH', col.lfprecision + 3, nil]
125
+ when :interval_ds
126
+ # dd hh:mi:ss.SSSSS
127
+ [SQL_OTHER, 'INTERVAL DAY TO SECOND', col.lfprecision + 10 + col.fsprecision, nil]
128
+ else
129
+ [SQL_OTHER, col.data_type.to_s, nil, nil]
130
+ end
131
+ {'name' => col.name,
132
+ 'sql_type' => sql_type,
133
+ 'type_name' => type_name,
134
+ 'nullable' => col.nullable?,
135
+ 'precision' => precision,
136
+ 'scale' => scale,
137
+ }
138
+ end
139
+ private :column_metadata_to_column_info
140
+ end
141
+
142
+ class Driver < DBI::BaseDriver # :nodoc:
143
+ include Util
144
+
145
+ def initialize
146
+ super(USED_DBD_VERSION)
147
+ end
148
+
149
+ # external OS authentication
150
+ # (contributed by Dan Fitch)
151
+ def default_user
152
+ [nil, nil]
153
+ end
154
+
155
+ def connect( dbname, user, auth, attr )
156
+ handle = ::OCI8.new(user, auth, dbname, attr['Privilege'])
157
+ handle.non_blocking = true if attr['NonBlocking']
158
+ return Database.new(handle, attr)
159
+ rescue OCIException => err
160
+ raise_dbierror(err)
161
+ end
162
+ end
163
+
164
+ class Database < DBI::BaseDatabase
165
+ include Util
166
+
167
+ def disconnect
168
+ @handle.logoff
169
+ rescue OCIException => err
170
+ raise_dbierror(err)
171
+ end
172
+
173
+ def prepare( statement )
174
+ # convert ?-style parameters to :1, :2 etc.
175
+ prep_statement = DBI::SQL::PreparedStatement.new(DummyQuoter.new, statement)
176
+ if prep_statement.unbound.size > 0
177
+ arr = (1..(prep_statement.unbound.size)).collect{|i| ":#{i}"}
178
+ statement = prep_statement.bind( arr )
179
+ end
180
+ cursor = @handle.parse(statement)
181
+ Statement.new(cursor)
182
+ rescue OCIException => err
183
+ raise_dbierror(err)
184
+ end
185
+
186
+ def ping
187
+ @handle.exec("BEGIN NULL; END;")
188
+ true
189
+ rescue
190
+ false
191
+ end
192
+
193
+ def commit
194
+ @handle.commit()
195
+ rescue OCIException => err
196
+ raise_dbierror(err)
197
+ end
198
+
199
+ def rollback
200
+ @handle.rollback()
201
+ rescue OCIException => err
202
+ raise_dbierror(err)
203
+ end
204
+
205
+ def tables
206
+ stmt = execute("SELECT object_name FROM user_objects where object_type in ('TABLE', 'VIEW')")
207
+ rows = stmt.fetch_all || []
208
+ stmt.finish
209
+ rows.collect {|row| row[0]}
210
+ end
211
+
212
+ # SQLs are copied from DBD::Oracle.
213
+ def columns(table)
214
+ tab = @handle.describe_table(table)
215
+ cols = tab.columns
216
+ cols.collect! do |col|
217
+ column_metadata_to_column_info(col)
218
+ end
219
+
220
+ dbh = DBI::DatabaseHandle.new(self)
221
+
222
+ pk_index_name = nil
223
+ dbh.select_all(<<EOS, tab.obj_schema, tab.obj_name) do |row|
224
+ select index_name
225
+ from all_constraints
226
+ where constraint_type = 'P'
227
+ and owner = :1
228
+ and table_name = :2
229
+ EOS
230
+ pk_index_name = row[0]
231
+ end
232
+
233
+ indices = {}
234
+ primaries = {}
235
+ uniques = {}
236
+ dbh.select_all(<<EOS, tab.obj_schema, tab.obj_name) do |row|
237
+ select a.column_name, a.index_name, b.uniqueness
238
+ from all_ind_columns a, all_indexes b
239
+ where a.index_name = b.index_name
240
+ and a.index_owner = b.owner
241
+ and a.table_owner = :1
242
+ and a.table_name = :2
243
+ EOS
244
+ col_name, index_name, uniqueness = row
245
+ indices[col_name] = true
246
+ primaries[col_name] = true if index_name == pk_index_name
247
+ uniques[col_name] = true if uniqueness == 'UNIQUE'
248
+ end
249
+
250
+ dbh.select_all(<<EOS, tab.obj_schema, tab.obj_name).collect do |row|
251
+ select column_id, column_name, data_default
252
+ from all_tab_columns
253
+ where owner = :1
254
+ and table_name = :2
255
+ EOS
256
+ col_id, col_name, default = row
257
+
258
+ col = cols[col_id.to_i - 1]
259
+ col_name = col['name']
260
+
261
+ if default && default[0] == ?'
262
+ default = default[1..-2].gsub(/''/, "'")
263
+ end
264
+
265
+ col['indexed'] = indices[col_name] || false
266
+ col['primary'] = primaries[col_name] || false
267
+ col['unique'] = uniques[col_name] || false
268
+ col['default'] = default
269
+ col
270
+ end
271
+ rescue OCIException => err
272
+ raise_dbierror(err)
273
+ end
274
+
275
+ def [](attr)
276
+ case attr
277
+ when 'AutoCommit'
278
+ @handle.autocommit?
279
+ end
280
+ end
281
+
282
+ def []=(attr, value)
283
+ case attr
284
+ when 'AutoCommit'
285
+ @handle.autocommit = value
286
+ end
287
+ end
288
+
289
+ private
290
+
291
+ class DummyQuoter # :nodoc:
292
+ # dummy to substitute ?-style parameter markers by :1 :2 etc.
293
+ def quote(str)
294
+ str
295
+ end
296
+ end
297
+ end
298
+
299
+ class Statement < DBI::BaseStatement
300
+ include Util
301
+
302
+ def initialize(cursor)
303
+ @cursor = cursor
304
+ end
305
+
306
+ def bind_param( param, value, attribs)
307
+ if attribs.nil? || attribs['type'].nil?
308
+ if value.nil?
309
+ @cursor.bind_param(param, nil, String, 1)
310
+ else
311
+ @cursor.bind_param(param, value)
312
+ end
313
+ else
314
+ case attribs['type']
315
+ when SQL_BINARY
316
+ type = OCI_TYPECODE_RAW
317
+ else
318
+ type = attribs['type']
319
+ end
320
+ @cursor.bind_param(param, value, type)
321
+ end
322
+ rescue OCIException => err
323
+ raise_dbierror(err)
324
+ end
325
+
326
+ def execute
327
+ @cursor.exec
328
+ rescue OCIException => err
329
+ raise_dbierror(err)
330
+ end
331
+
332
+ def finish
333
+ @cursor.close
334
+ rescue OCIException => err
335
+ raise_dbierror(err)
336
+ end
337
+
338
+ def fetch
339
+ @cursor.fetch
340
+ rescue OCIException => err
341
+ raise_dbierror(err)
342
+ end
343
+
344
+ def column_info
345
+ # minimum implementation.
346
+ @cursor.column_metadata.collect do |md|
347
+ col = column_metadata_to_column_info(md)
348
+ col['indexed'] = nil
349
+ col['primary'] = nil
350
+ col['unique'] = nil
351
+ col['default'] = nil
352
+ col
353
+ end
354
+ rescue OCIException => err
355
+ raise_dbierror(err)
356
+ end
357
+
358
+ def rows
359
+ @cursor.row_count
360
+ rescue OCIException => err
361
+ raise_dbierror(err)
362
+ end
363
+
364
+ def __rowid
365
+ @cursor.rowid
366
+ end
367
+
368
+ def __define(pos, type, length = nil)
369
+ @cursor.define(pos, type, length)
370
+ self
371
+ end
372
+
373
+ def __bind_value(param)
374
+ @cursor[param]
375
+ end
376
+ end
377
+
378
+ if defined? ::OCI8::BindType::Base
379
+ ##
380
+ ## ruby-oci8 2.0 bind classes.
381
+ ##
382
+
383
+ module BindType # :nodoc:
384
+
385
+ # helper class to define/bind DBI::Date.
386
+ class DBIDate < ::OCI8::BindType::OraDate
387
+ def set(val)
388
+ # convert val to an OraDate,
389
+ # then set it to the bind handle.
390
+ super(val && OraDate.new(val.year, val.month, val.day))
391
+ end
392
+ def get()
393
+ # get an Oradate from the bind handle,
394
+ # then convert it to a DBI::Date.
395
+ val = super()
396
+ return nil if val.nil?
397
+ DBI::Date.new(val.year, val.month, val.day)
398
+ end
399
+ end
400
+
401
+ # helper class to define/bind DBI::Timestamp.
402
+ #
403
+ # To fetch all Oracle's DATE columns as DBI::Timestamp:
404
+ # ::OCI8::BindType::Mapping[OCI8::SQLT_DAT] = ::DBI::DBD::OCI8::BindType::DBITimestamp
405
+ #
406
+ class DBITimestamp < ::OCI8::BindType::OraDate
407
+ def set(val)
408
+ # convert val to an OraDate,
409
+ # then set it to the bind handle.
410
+ super(val && OraDate.new(val.year, val.month, val.day,
411
+ val.respond_to?(:hour) ? val.hour : 0,
412
+ val.respond_to?(:min) ? val.min : 0,
413
+ val.respond_to?(:sec) ? val.sec : 0))
414
+ end
415
+ def get()
416
+ # get an Oradate from the bind handle,
417
+ # then convert it to a DBI::Timestamp.
418
+ val = super()
419
+ return nil if val.nil?
420
+ DBI::Timestamp.new(val.year, val.month, val.day, val.hour, val.minute, val.second)
421
+ end
422
+ end
423
+
424
+ # helper class to bind ref cursor as DBI::StatementHandle.
425
+ #
426
+ # # Create package
427
+ # dbh.execute(<<EOS)
428
+ # create or replace package test_pkg is
429
+ # type ref_cursor is ref cursor;
430
+ # procedure tab_table(csr out ref_cursor);
431
+ # end;
432
+ # EOS
433
+ #
434
+ # # Create package body
435
+ # dbh.execute(<<EOS)
436
+ # create or replace package body test_pkg is
437
+ # procedure tab_table(csr out ref_cursor) is
438
+ # begin
439
+ # open csr for select * from tab;
440
+ # end;
441
+ # end;
442
+ # EOS
443
+ #
444
+ # # Execute test_pkg.tab_table.
445
+ # # The first parameter is bound as DBI::StatementHandle.
446
+ # plsql = dbh.execute("begin test_pkg.tab_table(?); end;", DBI::StatementHandle)
447
+ #
448
+ # # Get the first parameter, which is a DBI::StatementHandle.
449
+ # sth = plsql.func(:bind_value, 1)
450
+ #
451
+ # # fetch column data.
452
+ # sth.fetch_all
453
+ #
454
+ class DBIStatementHandle < ::OCI8::BindType::Cursor
455
+ def set(val)
456
+ if val.is_a? DBI::StatementHandle
457
+ # get OCI8::Cursor
458
+ val = val.instance_eval do @handle end
459
+ val = val.instance_eval do @cursor end
460
+ end
461
+ super(val)
462
+ end
463
+ def get()
464
+ val = super
465
+ return nil if val.nil?
466
+ stmt = DBI::DBD::OCI8::Statement.new(val)
467
+ DBI::StatementHandle.new(stmt, true, false)
468
+ end
469
+ end
470
+ end # BindType
471
+
472
+ else
473
+ ##
474
+ ## ruby-oci8 1.0 bind classes.
475
+ ##
476
+
477
+ module BindType # :nodoc:
478
+ DBIDate = Object.new
479
+ class << DBIDate
480
+ def fix_type(env, val, length, precision, scale)
481
+ # bind as an OraDate
482
+ [::OCI8::SQLT_DAT, val, nil]
483
+ end
484
+ def decorate(b)
485
+ def b.set(val)
486
+ # convert val to an OraDate,
487
+ # then set it to the bind handle.
488
+ super(val && OraDate.new(val.year, val.month, val.day))
489
+ end
490
+ def b.get()
491
+ # get an Oradate from the bind handle,
492
+ # then convert it to a DBI::Date.
493
+ (val = super()) && DBI::Date.new(val.year, val.month, val.day)
494
+ end
495
+ end
496
+ end
497
+
498
+ DBITimestamp = Object.new
499
+ class << DBITimestamp
500
+ def fix_type(env, val, length, precision, scale)
501
+ # bind as an OraDate
502
+ [::OCI8::SQLT_DAT, val, nil]
503
+ end
504
+ def decorate(b)
505
+ def b.set(val)
506
+ # convert val to an OraDate,
507
+ # then set it to the bind handle.
508
+ super(val && OraDate.new(val.year, val.month, val.day,
509
+ val.respond_to?(:hour) ? val.hour : 0,
510
+ val.respond_to?(:min) ? val.min : 0,
511
+ val.respond_to?(:sec) ? val.sec : 0))
512
+ end
513
+ def b.get()
514
+ # get an Oradate from the bind handle,
515
+ # then convert it to a DBI::Timestamp.
516
+ (val = super()) && DBI::Timestamp.new(val.year, val.month, val.day, val.hour, val.minute, val.second)
517
+ end
518
+ end
519
+ end
520
+
521
+ DBIStatementHandle = Object.new
522
+ class << DBIStatementHandle
523
+ def fix_type(env, val, length, precision, scale)
524
+ raise NotImplementedError unless val.nil?
525
+ [::OCI8::SQLT_RSET, nil, env.alloc(OCIStmt)]
526
+ end
527
+ def decorate(b)
528
+ def b.set(val)
529
+ raise NotImplementedError
530
+ end
531
+ def b.get()
532
+ val = super
533
+ return val if val.nil?
534
+ cur = ::OCI8::Cursor.new(@env, @svc, @ctx, val)
535
+ stmt = DBI::DBD::OCI8::Statement.new(cur)
536
+ DBI::StatementHandle.new(stmt, true, false)
537
+ end
538
+ end
539
+ end
540
+ end # BindType
541
+ end
542
+
543
+ ::OCI8::BindType::Mapping[DBI::Date] = BindType::DBIDate
544
+ ::OCI8::BindType::Mapping[DBI::Timestamp] = BindType::DBITimestamp
545
+ ::OCI8::BindType::Mapping[DBI::StatementHandle] = BindType::DBIStatementHandle
546
+
547
+ end # module OCI8
548
+ end # module DBD
549
+ end # module DBI