dbi 0.4.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/ChangeLog +3694 -0
- data/LICENSE +25 -0
- data/README +271 -0
- data/bin/dbi +518 -0
- data/build/Rakefile.dbi.rb +57 -0
- data/examples/test1.pl +39 -0
- data/examples/test1.rb +20 -0
- data/examples/xmltest.rb +8 -0
- data/lib/dbi.rb +323 -0
- data/lib/dbi/base_classes.rb +12 -0
- data/lib/dbi/base_classes/database.rb +135 -0
- data/lib/dbi/base_classes/driver.rb +47 -0
- data/lib/dbi/base_classes/statement.rb +167 -0
- data/lib/dbi/binary.rb +25 -0
- data/lib/dbi/columninfo.rb +106 -0
- data/lib/dbi/exceptions.rb +65 -0
- data/lib/dbi/handles.rb +49 -0
- data/lib/dbi/handles/database.rb +211 -0
- data/lib/dbi/handles/driver.rb +60 -0
- data/lib/dbi/handles/statement.rb +375 -0
- data/lib/dbi/row.rb +249 -0
- data/lib/dbi/sql.rb +23 -0
- data/lib/dbi/sql/preparedstatement.rb +115 -0
- data/lib/dbi/sql_type_constants.rb +75 -0
- data/lib/dbi/trace.rb +91 -0
- data/lib/dbi/types.rb +158 -0
- data/lib/dbi/typeutil.rb +108 -0
- data/lib/dbi/utils.rb +60 -0
- data/lib/dbi/utils/date.rb +59 -0
- data/lib/dbi/utils/tableformatter.rb +112 -0
- data/lib/dbi/utils/time.rb +52 -0
- data/lib/dbi/utils/timestamp.rb +96 -0
- data/lib/dbi/utils/xmlformatter.rb +73 -0
- data/test/dbi/tc_columninfo.rb +94 -0
- data/test/dbi/tc_date.rb +88 -0
- data/test/dbi/tc_dbi.rb +178 -0
- data/test/dbi/tc_row.rb +256 -0
- data/test/dbi/tc_sqlbind.rb +168 -0
- data/test/dbi/tc_statementhandle.rb +16 -0
- data/test/dbi/tc_time.rb +77 -0
- data/test/dbi/tc_timestamp.rb +142 -0
- data/test/dbi/tc_types.rb +220 -0
- data/test/dbi/trace.rb +26 -0
- data/test/ts_dbi.rb +15 -0
- metadata +108 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
#--
|
2
|
+
# Fallback classes for default behavior of DBD driver
|
3
|
+
# must be inherited by the DBD driver classes
|
4
|
+
#++
|
5
|
+
module DBI
|
6
|
+
class Base #:nodoc:
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'dbi/base_classes/driver'
|
11
|
+
require 'dbi/base_classes/database'
|
12
|
+
require 'dbi/base_classes/statement'
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module DBI
|
2
|
+
# Provides the core-level functionality for DatabaseHandles.
|
3
|
+
#
|
4
|
+
# If the method description says "DBD Required", it's the DBD's
|
5
|
+
# responsibility to create this method.
|
6
|
+
#
|
7
|
+
# Required methods unimplemented by the DBD will raise
|
8
|
+
# DBD::NotImplementedError.
|
9
|
+
#
|
10
|
+
# "DBD Optional" methods are methods that do not have a default
|
11
|
+
# implementation but are optional due to the fact that many databases may
|
12
|
+
# not support these features (and emulating them would be prohibitive).
|
13
|
+
#
|
14
|
+
# These methods raise DBI::NotSupportedError.
|
15
|
+
#
|
16
|
+
# Otherwise, DBI will provide a general alternative which should meet the
|
17
|
+
# expectations of the documentation. However, DBDs can override every
|
18
|
+
# method in this class.
|
19
|
+
#
|
20
|
+
class BaseDatabase < Base
|
21
|
+
def initialize(handle, attr)
|
22
|
+
@handle = handle
|
23
|
+
@attr = {}
|
24
|
+
attr.each {|k,v| self[k] = v}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Disconnect from the database. DBD Required.
|
28
|
+
def disconnect
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
# Ping the database to ensure the connection is still alive. Boolean
|
33
|
+
# return, true for success. DBD Required.
|
34
|
+
def ping
|
35
|
+
raise NotImplementedError
|
36
|
+
end
|
37
|
+
|
38
|
+
# Prepare a cached statement, returning a StatementHandle. DBD
|
39
|
+
# Required.
|
40
|
+
def prepare(statement)
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Return a map of the columns that exist in the provided table name.
|
46
|
+
# DBD Required.
|
47
|
+
#
|
48
|
+
# The result should be an array of DBI::ColumnInfo objects which have,
|
49
|
+
# at minimum, the following fields:
|
50
|
+
#
|
51
|
+
# * name:: the name of the column.
|
52
|
+
# * type:: This is not a field name in itself. You have two options:
|
53
|
+
# * type_name:: The name of the type as returned by the database
|
54
|
+
# * dbi_type:: A DBI::Type-conforming class that can be used to convert to a native type.
|
55
|
+
# * precision:: the precision (generally length) of the column
|
56
|
+
# * scale:: the scale (generally a secondary attribute to precision
|
57
|
+
# that helps indicate length) of the column
|
58
|
+
#
|
59
|
+
def columns(table)
|
60
|
+
raise NotImplementedError
|
61
|
+
end
|
62
|
+
|
63
|
+
#============================================
|
64
|
+
# OPTIONAL
|
65
|
+
#============================================
|
66
|
+
|
67
|
+
# Schedule a commit to the database immediately. DBD Optional.
|
68
|
+
def commit
|
69
|
+
raise NotSupportedError
|
70
|
+
end
|
71
|
+
|
72
|
+
# Schedule a rollback to the database immediately. DBD Optional.
|
73
|
+
def rollback
|
74
|
+
raise NotSupportedError
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return the tables available to the database connection.
|
78
|
+
#
|
79
|
+
# Note:: the basic implementation returns an empty array.
|
80
|
+
def tables
|
81
|
+
[]
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Execute a statement with the binds provided. Returns the statement
|
86
|
+
# handle unfinished.
|
87
|
+
#
|
88
|
+
# This is roughly equivalent to:
|
89
|
+
#
|
90
|
+
# sth = dbh.prepare("my statement")
|
91
|
+
# sth.execute(my, bind, vars)
|
92
|
+
#
|
93
|
+
def execute(statement, *bindvars)
|
94
|
+
stmt = prepare(statement)
|
95
|
+
stmt.bind_params(*bindvars)
|
96
|
+
stmt.execute
|
97
|
+
stmt
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Execute and complete the statement with the binds provided. Returns
|
102
|
+
# the row modified count (via BaseStatement#rows). Finishes the
|
103
|
+
# statement handle for you.
|
104
|
+
#
|
105
|
+
# Roughly equivalent to:
|
106
|
+
#
|
107
|
+
# sth = dbh.prepare("my statement)
|
108
|
+
# sth.execute(my, bind, vars)
|
109
|
+
# result = sth.rows
|
110
|
+
# sth.finish
|
111
|
+
#
|
112
|
+
# Returning the value stored in `result`.
|
113
|
+
def do(statement, *bindvars)
|
114
|
+
stmt = execute(statement, *bindvars)
|
115
|
+
res = stmt.rows
|
116
|
+
stmt.finish
|
117
|
+
return res
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Get an attribute from the DatabaseHandle. These are DBD specific and
|
122
|
+
# embody things like Auto-Commit support for transactional databases.
|
123
|
+
#
|
124
|
+
# DBD Authors:: This messes with @attr directly.
|
125
|
+
#
|
126
|
+
def [](attr)
|
127
|
+
@attr[attr]
|
128
|
+
end
|
129
|
+
|
130
|
+
# Set an attribute on the DatabaseHandle. DBD Optional.
|
131
|
+
def []=(attr, value)
|
132
|
+
raise NotSupportedError
|
133
|
+
end
|
134
|
+
end # class BaseDatabase
|
135
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module DBI
|
2
|
+
|
3
|
+
# Implements the basic functionality that constitutes a Driver
|
4
|
+
#
|
5
|
+
# Drivers do not have a direct interface exposed to the user; these methods
|
6
|
+
# are mostly for DBD authors.
|
7
|
+
#
|
8
|
+
# As with DBI::BaseDatabase, "DBD Required" and "DBD Optional" will be used
|
9
|
+
# to explain the same requirements.
|
10
|
+
#
|
11
|
+
class BaseDriver < Base
|
12
|
+
def initialize(dbi_version)
|
13
|
+
major, minor = dbi_version.split(".").collect { |x| x.to_i }
|
14
|
+
dbi_major, dbi_minor = DBI::VERSION.split(".").collect { |x| x.to_i }
|
15
|
+
unless major == dbi_major and minor == dbi_minor
|
16
|
+
raise InterfaceError, "Wrong DBD API version used"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Connect to the database. DBD Required.
|
21
|
+
def connect(dbname, user, auth, attr)
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
# Default u/p information in an array.
|
26
|
+
def default_user
|
27
|
+
['', '']
|
28
|
+
end
|
29
|
+
|
30
|
+
# Default attributes to set on the DatabaseHandle.
|
31
|
+
def default_attributes
|
32
|
+
{}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return the data sources available to this driver. Returns an empty
|
36
|
+
# array per default.
|
37
|
+
def data_sources
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Disconnect all DatabaseHandles. DBD Required.
|
42
|
+
def disconnect_all
|
43
|
+
raise NotImplementedError
|
44
|
+
end
|
45
|
+
|
46
|
+
end # class BaseDriver
|
47
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module DBI
|
2
|
+
#
|
3
|
+
# StatementHandles are used to encapsulate the process of managing a
|
4
|
+
# statement (DDL or DML) and its parameters, sending it to the database,
|
5
|
+
# and gathering any results from the execution of that statement.
|
6
|
+
#
|
7
|
+
# As with the other `Base` classes, the terms "DBD Required" and "DBD
|
8
|
+
# Optional" are defined in DBI::BaseDatabase.
|
9
|
+
#
|
10
|
+
class BaseStatement < Base
|
11
|
+
def initialize(attr=nil)
|
12
|
+
@attr = attr || {}
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Bind a parameter to the statement. DBD Required.
|
17
|
+
#
|
18
|
+
# The parameter number is numeric and indexes starting at 1. This
|
19
|
+
# corresponds to the question marks (?) in the statement from the
|
20
|
+
# left-most part of the statement moving forward.
|
21
|
+
#
|
22
|
+
# The value may be any ruby type. How these are handled is
|
23
|
+
# DBD-dependent, but the vast majority of DBDs will convert these to
|
24
|
+
# string inside the query.
|
25
|
+
#
|
26
|
+
def bind_param(param, value, attribs)
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Execute the statement with the known binds. DBD Required.
|
32
|
+
#
|
33
|
+
def execute
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Close the statement and any result cursors. DBD Required.
|
39
|
+
#
|
40
|
+
# Note:: Most implementations will fail miserably if you forget to
|
41
|
+
# finish your statement handles.
|
42
|
+
def finish
|
43
|
+
raise NotImplementedError
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Fetch the next row in the result set. DBD Required.
|
48
|
+
#
|
49
|
+
# DBI::Row is responsible for formatting the data the DBD provides it.
|
50
|
+
#
|
51
|
+
def fetch
|
52
|
+
raise NotImplementedError
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# returns result-set column information as array
|
57
|
+
# of hashs, where each hash represents one column. See
|
58
|
+
# BaseDatabase#columns. DBD Required.
|
59
|
+
#
|
60
|
+
def column_info
|
61
|
+
raise NotImplementedError
|
62
|
+
end
|
63
|
+
|
64
|
+
#============================================
|
65
|
+
# OPTIONAL
|
66
|
+
#============================================
|
67
|
+
|
68
|
+
#
|
69
|
+
# Take a list of bind variables and bind them successively using bind_param.
|
70
|
+
#
|
71
|
+
def bind_params(*bindvars)
|
72
|
+
bindvars.each_with_index {|val,i| bind_param(i+1, val, nil) }
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Cancel any result cursors. DBD Optional, but intentionally does not
|
78
|
+
# raise any exception as it's used internally to maintain consistency.
|
79
|
+
#
|
80
|
+
def cancel
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# fetch_scroll is provided with a direction and offset and works
|
85
|
+
# similar to how seek() is used on files.
|
86
|
+
#
|
87
|
+
# The constants available for direction are as follows:
|
88
|
+
#
|
89
|
+
# * SQL_FETCH_NEXT: fetch the next result.
|
90
|
+
# * SQL_FETCH_LAST: fetch the last result, period.
|
91
|
+
# * SQL_FETCH_RELATIVE: fetch the result at the offset.
|
92
|
+
#
|
93
|
+
# Other constants can be used, but if this method is not supplied by
|
94
|
+
# the driver, they will result in a raise of DBI::NotSupportedError.
|
95
|
+
#
|
96
|
+
|
97
|
+
def fetch_scroll(direction, offset)
|
98
|
+
case direction
|
99
|
+
when SQL_FETCH_NEXT
|
100
|
+
return fetch
|
101
|
+
when SQL_FETCH_LAST
|
102
|
+
last_row = nil
|
103
|
+
while (row=fetch) != nil
|
104
|
+
last_row = row
|
105
|
+
end
|
106
|
+
return last_row
|
107
|
+
when SQL_FETCH_RELATIVE
|
108
|
+
raise NotSupportedError if offset <= 0
|
109
|
+
row = nil
|
110
|
+
offset.times { row = fetch; break if row.nil? }
|
111
|
+
return row
|
112
|
+
else
|
113
|
+
raise NotSupportedError
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# fetch x rows. The result is Array of DBI::Row.
|
119
|
+
#
|
120
|
+
def fetch_many(cnt)
|
121
|
+
rows = []
|
122
|
+
cnt.times do
|
123
|
+
row = fetch
|
124
|
+
break if row.nil?
|
125
|
+
rows << row.dup
|
126
|
+
end
|
127
|
+
|
128
|
+
if rows.empty?
|
129
|
+
nil
|
130
|
+
else
|
131
|
+
rows
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# Fetch all available rows. Result is Array of DBI::Row.
|
137
|
+
#
|
138
|
+
def fetch_all
|
139
|
+
rows = []
|
140
|
+
loop do
|
141
|
+
row = fetch
|
142
|
+
break if row.nil?
|
143
|
+
rows << row.dup
|
144
|
+
end
|
145
|
+
|
146
|
+
if rows.empty?
|
147
|
+
nil
|
148
|
+
else
|
149
|
+
rows
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# Get statement attributes.
|
155
|
+
#
|
156
|
+
def [](attr)
|
157
|
+
@attr[attr]
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
# Set statement attributes. DBD Optional.
|
162
|
+
#
|
163
|
+
def []=(attr, value)
|
164
|
+
raise NotSupportedError
|
165
|
+
end
|
166
|
+
end # class BaseStatement
|
167
|
+
end
|
data/lib/dbi/binary.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module DBI
|
2
|
+
#
|
3
|
+
# Encapsulates the concept of a CLOB/BLOB, which can then be passed as a
|
4
|
+
# bind via BaseStatement#bind_param.
|
5
|
+
#
|
6
|
+
# This is similar to a DBI::Type class and will eventually find its way
|
7
|
+
# there.
|
8
|
+
#
|
9
|
+
# See #new for usage.
|
10
|
+
class Binary
|
11
|
+
attr_accessor :data
|
12
|
+
|
13
|
+
# Construct a new DBI::Binary object with the data supplied as string.
|
14
|
+
# This object can then be used in bound variables to represent a CLOB
|
15
|
+
# or BLOB type.
|
16
|
+
def initialize(data)
|
17
|
+
@data = data
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return the string representation of the DBI::Binary object.
|
21
|
+
def to_s
|
22
|
+
@data
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'rubygems'
|
3
|
+
begin
|
4
|
+
gem 'deprecated'
|
5
|
+
rescue LoadError => e
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'deprecated'
|
9
|
+
|
10
|
+
module DBI
|
11
|
+
# This represents metadata for columns within a given table, such as the
|
12
|
+
# data type, whether or not the the column is a primary key, etc.
|
13
|
+
#
|
14
|
+
# ColumnInfo is a delegate of Hash, but represents its keys indifferently,
|
15
|
+
# coercing all strings to symbols. It also has ostruct-like features, f.e.:
|
16
|
+
#
|
17
|
+
# h = ColumnInfo.new({ "foo" => "bar" })
|
18
|
+
# h[:foo] => "bar"
|
19
|
+
# h["foo"] => "bar"
|
20
|
+
# h.foo => "bar"
|
21
|
+
#
|
22
|
+
# All of these forms have assignment forms as well.
|
23
|
+
#
|
24
|
+
class ColumnInfo < DelegateClass(Hash)
|
25
|
+
|
26
|
+
# Create a new ColumnInfo object.
|
27
|
+
#
|
28
|
+
# If no Hash is provided, one will be created for you. The hash will be
|
29
|
+
# shallow cloned for storage inside the object, and an attempt will be
|
30
|
+
# made to convert all string keys to symbols.
|
31
|
+
#
|
32
|
+
# In the event that both string and symbol keys are provided in the
|
33
|
+
# initial hash, we cannot safely route around collisions and therefore
|
34
|
+
# a TypeError is raised.
|
35
|
+
#
|
36
|
+
def initialize(hash=nil)
|
37
|
+
@hash = hash.dup rescue nil
|
38
|
+
@hash ||= Hash.new
|
39
|
+
|
40
|
+
# coerce all strings to symbols
|
41
|
+
@hash.each_key do |x|
|
42
|
+
if x.kind_of? String
|
43
|
+
sym = x.to_sym
|
44
|
+
if @hash.has_key? sym
|
45
|
+
raise ::TypeError,
|
46
|
+
"#{self.class.name} may construct from a hash keyed with strings or symbols, but not both"
|
47
|
+
end
|
48
|
+
@hash[sym] = @hash[x]
|
49
|
+
@hash.delete(x)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
super(@hash)
|
54
|
+
end
|
55
|
+
|
56
|
+
def [](key)
|
57
|
+
@hash[key.to_sym]
|
58
|
+
end
|
59
|
+
|
60
|
+
def []=(key, value)
|
61
|
+
@hash[key.to_sym] = value
|
62
|
+
end
|
63
|
+
|
64
|
+
def default() # :nodoc; XXX hack to get around Hash#default
|
65
|
+
method_missing(:default)
|
66
|
+
end
|
67
|
+
|
68
|
+
def method_missing(sym, value=nil)
|
69
|
+
if sym.to_s =~ /=$/
|
70
|
+
sym = sym.to_s.sub(/=$/, '').to_sym
|
71
|
+
@hash[sym] = value
|
72
|
+
elsif sym.to_s =~ /\?$/
|
73
|
+
sym = sym.to_s.sub(/\?$/, '').to_sym
|
74
|
+
@hash[sym]
|
75
|
+
else
|
76
|
+
@hash[sym]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Aliases - XXX soon to be deprecated
|
81
|
+
def self.deprecated_alias(target, source) # :nodoc:
|
82
|
+
define_method(target) { |*args| method_missing(source, *args) }
|
83
|
+
deprecate target
|
84
|
+
end
|
85
|
+
|
86
|
+
deprecated_alias :is_nullable?, :nullable
|
87
|
+
deprecated_alias :can_be_null?, :nullable
|
88
|
+
|
89
|
+
deprecated_alias :is_indexed?, :indexed
|
90
|
+
|
91
|
+
deprecated_alias :is_primary?, :primary
|
92
|
+
|
93
|
+
deprecated_alias :is_unique, :unique
|
94
|
+
|
95
|
+
deprecated_alias :size, :precision
|
96
|
+
deprecated_alias :size=, :precision=
|
97
|
+
deprecated_alias :length, :precision
|
98
|
+
deprecated_alias :length=, :precision=
|
99
|
+
|
100
|
+
deprecated_alias :decimal_digits, :scale
|
101
|
+
deprecated_alias :decimal_digits=, :scale=
|
102
|
+
|
103
|
+
deprecated_alias :default_value, :default
|
104
|
+
deprecated_alias :default_value=, :default=
|
105
|
+
end
|
106
|
+
end
|