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,65 @@
|
|
1
|
+
module DBI
|
2
|
+
# Exceptions (borrowed by Python API 2.0)
|
3
|
+
|
4
|
+
# Base class of all other error exceptions. Use this to catch all DBI
|
5
|
+
# errors.
|
6
|
+
class Error < RuntimeError
|
7
|
+
end
|
8
|
+
|
9
|
+
# For important warnings like data truncation, etc.
|
10
|
+
class Warning < RuntimeError
|
11
|
+
end
|
12
|
+
|
13
|
+
# Exception for errors related to the DBI interface rather than the
|
14
|
+
# database itself.
|
15
|
+
class InterfaceError < Error
|
16
|
+
end
|
17
|
+
|
18
|
+
# Exception raised if the DBD driver has not specified a mandatory method.
|
19
|
+
class NotImplementedError < InterfaceError
|
20
|
+
end
|
21
|
+
|
22
|
+
# Exception for errors related to the database.
|
23
|
+
class DatabaseError < Error
|
24
|
+
attr_reader :err, :errstr, :state
|
25
|
+
|
26
|
+
def initialize(errstr="", err=nil, state=nil)
|
27
|
+
super(errstr)
|
28
|
+
@err, @errstr, @state = err, errstr, state
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Exception for errors due to problems with the processed
|
33
|
+
# data such as division by zero, numeric value out of range, etc.
|
34
|
+
class DataError < DatabaseError
|
35
|
+
end
|
36
|
+
|
37
|
+
# Exception for errors related to the database's operation which are not
|
38
|
+
# necessarily under the control of the programmer. This includes such
|
39
|
+
# things as unexpected disconnect, datasource name not found, transaction
|
40
|
+
# could not be processed, a memory allocation error occured during
|
41
|
+
# processing, etc.
|
42
|
+
class OperationalError < DatabaseError
|
43
|
+
end
|
44
|
+
|
45
|
+
# Exception raised when the relational integrity of the database
|
46
|
+
# is affected, e.g. a foreign key check fails.
|
47
|
+
class IntegrityError < DatabaseError
|
48
|
+
end
|
49
|
+
|
50
|
+
# Exception raised when the database encounters an internal error,
|
51
|
+
# e.g. the cursor is not valid anymore, the transaction is out of sync.
|
52
|
+
class InternalError < DatabaseError
|
53
|
+
end
|
54
|
+
|
55
|
+
# Exception raised for programming errors, e.g. table not found
|
56
|
+
# or already exists, syntax error in SQL statement, wrong number
|
57
|
+
# of parameters specified, etc.
|
58
|
+
class ProgrammingError < DatabaseError
|
59
|
+
end
|
60
|
+
|
61
|
+
# Exception raised if e.g. commit() is called for a database which do not
|
62
|
+
# support transactions.
|
63
|
+
class NotSupportedError < DatabaseError
|
64
|
+
end
|
65
|
+
end
|
data/lib/dbi/handles.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#
|
2
|
+
# Dispatch classes (Handle, DriverHandle, DatabaseHandle and StatementHandle)
|
3
|
+
#
|
4
|
+
|
5
|
+
module DBI
|
6
|
+
#
|
7
|
+
# Base class for all handles.
|
8
|
+
#
|
9
|
+
class Handle
|
10
|
+
attr_reader :trace_mode, :trace_output
|
11
|
+
attr_reader :handle
|
12
|
+
attr :convert_types, true
|
13
|
+
|
14
|
+
def initialize(handle, convert_types=true)
|
15
|
+
@handle = handle
|
16
|
+
@trace_mode = @trace_output = nil
|
17
|
+
@convert_types = convert_types
|
18
|
+
end
|
19
|
+
|
20
|
+
# Please seee DBI.trace.
|
21
|
+
def trace(mode=nil, output=nil)
|
22
|
+
# FIXME trace
|
23
|
+
raise InterfaceError, "the trace module has been removed until it actually works."
|
24
|
+
@trace_mode = mode || @trace_mode || DBI::DEFAULT_TRACE_MODE
|
25
|
+
@trace_output = output || @trace_output || DBI::DEFAULT_TRACE_OUTPUT
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Leverage a driver-specific method. The method name will have "__"
|
30
|
+
# prepended to them before calling, and the DBD must define them as
|
31
|
+
# such for them to work.
|
32
|
+
#
|
33
|
+
def func(function, *values)
|
34
|
+
if @handle.respond_to?("__" + function.to_s) then
|
35
|
+
@handle.send("__" + function.to_s, *values)
|
36
|
+
else
|
37
|
+
raise InterfaceError, "Driver specific function <#{function}> not available."
|
38
|
+
end
|
39
|
+
rescue ArgumentError
|
40
|
+
raise InterfaceError, "Wrong # of arguments for driver specific function"
|
41
|
+
end
|
42
|
+
|
43
|
+
# error functions?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
require 'dbi/handles/driver'
|
48
|
+
require 'dbi/handles/database'
|
49
|
+
require 'dbi/handles/statement'
|
@@ -0,0 +1,211 @@
|
|
1
|
+
module DBI
|
2
|
+
# DatabaseHandle is the interface the consumer sees after connecting to the
|
3
|
+
# database via DBI.connect.
|
4
|
+
#
|
5
|
+
# It is strongly discouraged that DBDs inherit from this class directly;
|
6
|
+
# please inherit from the DBI::BaseDatabase instead.
|
7
|
+
#
|
8
|
+
# Note: almost all methods in this class will raise InterfaceError if the
|
9
|
+
# database is not connected.
|
10
|
+
class DatabaseHandle < Handle
|
11
|
+
# This is the driver name as supplied by the DBD's driver_name method.
|
12
|
+
# Its primary utility is in DBI::TypeUtil#convert.
|
13
|
+
def driver_name
|
14
|
+
return @driver_name.dup if @driver_name
|
15
|
+
return nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# Assign the driver name. This can be leveraged to create custom type
|
19
|
+
# management via DBI::TypeUtil#convert.
|
20
|
+
def driver_name=(name)
|
21
|
+
@driver_name = name
|
22
|
+
@driver_name.freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Boolean if we are still connected to the database. See #ping.
|
27
|
+
#
|
28
|
+
def connected?
|
29
|
+
not @handle.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Disconnect from the database. Will raise InterfaceError if this was
|
34
|
+
# already done prior.
|
35
|
+
#
|
36
|
+
def disconnect
|
37
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
38
|
+
@handle.disconnect
|
39
|
+
@handle = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Prepare a StatementHandle and return it. If given a block, it will
|
44
|
+
# supply that StatementHandle as the first argument to the block, and
|
45
|
+
# BaseStatement#finish it when the block is done executing.
|
46
|
+
#
|
47
|
+
def prepare(stmt)
|
48
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
49
|
+
sth = StatementHandle.new(@handle.prepare(stmt), false, true, @convert_types)
|
50
|
+
# FIXME trace sth.trace(@trace_mode, @trace_output)
|
51
|
+
sth.dbh = self
|
52
|
+
|
53
|
+
if block_given?
|
54
|
+
begin
|
55
|
+
yield sth
|
56
|
+
ensure
|
57
|
+
sth.finish unless sth.finished?
|
58
|
+
end
|
59
|
+
else
|
60
|
+
return sth
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Prepare and execute a statement. It has block semantics equivalent to #prepare.
|
66
|
+
#
|
67
|
+
def execute(stmt, *bindvars)
|
68
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
69
|
+
|
70
|
+
if @convert_types
|
71
|
+
bindvars = DBI::Utils::ConvParam.conv_param(driver_name, *bindvars)
|
72
|
+
end
|
73
|
+
|
74
|
+
sth = StatementHandle.new(@handle.execute(stmt, *bindvars), true, false, @convert_types)
|
75
|
+
# FIXME trace sth.trace(@trace_mode, @trace_output)
|
76
|
+
sth.dbh = self
|
77
|
+
|
78
|
+
if block_given?
|
79
|
+
begin
|
80
|
+
yield sth
|
81
|
+
ensure
|
82
|
+
sth.finish unless sth.finished?
|
83
|
+
end
|
84
|
+
else
|
85
|
+
return sth
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Perform a statement. This goes straight to the DBD's implementation
|
91
|
+
# of #do (and consequently, BaseDatabase#do), and does not work like
|
92
|
+
# #execute and #prepare. Should return a row modified count.
|
93
|
+
#
|
94
|
+
def do(stmt, *bindvars)
|
95
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
96
|
+
@handle.do(stmt, *DBI::Utils::ConvParam.conv_param(driver_name, *bindvars))
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# Executes a statement and returns the first row from the result.
|
101
|
+
#
|
102
|
+
def select_one(stmt, *bindvars)
|
103
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
104
|
+
row = nil
|
105
|
+
execute(stmt, *bindvars) do |sth|
|
106
|
+
row = sth.fetch
|
107
|
+
end
|
108
|
+
row
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Executes a statement and returns all rows from the result. If a block
|
113
|
+
# is given, it is executed for each row.
|
114
|
+
#
|
115
|
+
def select_all(stmt, *bindvars, &p)
|
116
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
117
|
+
rows = nil
|
118
|
+
execute(stmt, *bindvars) do |sth|
|
119
|
+
if block_given?
|
120
|
+
sth.each(&p)
|
121
|
+
else
|
122
|
+
rows = sth.fetch_all
|
123
|
+
end
|
124
|
+
end
|
125
|
+
return rows
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Return the tables available to this DatabaseHandle as an array of strings.
|
130
|
+
#
|
131
|
+
def tables
|
132
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
133
|
+
@handle.tables
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Returns the columns of the provided table as an array of ColumnInfo
|
138
|
+
# objects. See BaseDatabase#columns for the minimum parameters that
|
139
|
+
# this method must provide.
|
140
|
+
#
|
141
|
+
def columns( table )
|
142
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
143
|
+
@handle.columns( table ).collect {|col| ColumnInfo.new(col) }
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# Attempt to establish if the database is still connected. While
|
148
|
+
# #connected? returns the state the DatabaseHandle thinks is true, this
|
149
|
+
# is an active operation that will contact the database.
|
150
|
+
#
|
151
|
+
def ping
|
152
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
153
|
+
@handle.ping
|
154
|
+
end
|
155
|
+
|
156
|
+
#
|
157
|
+
# Attempt to escape the value, rendering it suitable for inclusion in a SQL statement.
|
158
|
+
#
|
159
|
+
def quote(value)
|
160
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
161
|
+
@handle.quote(value)
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# Force a commit to the database immediately.
|
166
|
+
#
|
167
|
+
def commit
|
168
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
169
|
+
@handle.commit
|
170
|
+
end
|
171
|
+
|
172
|
+
#
|
173
|
+
# Force a rollback to the database immediately.
|
174
|
+
#
|
175
|
+
def rollback
|
176
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
177
|
+
@handle.rollback
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# Commits, runs the block provided, yielding the DatabaseHandle as it's
|
182
|
+
# argument. If an exception is raised through the block, rollback occurs.
|
183
|
+
# Otherwise, commit occurs.
|
184
|
+
#
|
185
|
+
def transaction
|
186
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
187
|
+
raise InterfaceError, "No block given" unless block_given?
|
188
|
+
|
189
|
+
commit
|
190
|
+
begin
|
191
|
+
yield self
|
192
|
+
commit
|
193
|
+
rescue Exception
|
194
|
+
rollback
|
195
|
+
raise
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Get an attribute from the DatabaseHandle.
|
200
|
+
def [] (attr)
|
201
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
202
|
+
@handle[attr]
|
203
|
+
end
|
204
|
+
|
205
|
+
# Set an attribute on the DatabaseHandle.
|
206
|
+
def []= (attr, val)
|
207
|
+
raise InterfaceError, "Database connection was already closed!" if @handle.nil?
|
208
|
+
@handle[attr] = val
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module DBI
|
2
|
+
# DriverHandles, while not directly exposed, are essentially the backend
|
3
|
+
# for the facade that many DBI root-level methods communicate with.
|
4
|
+
class DriverHandle < Handle
|
5
|
+
|
6
|
+
attr_writer :driver_name
|
7
|
+
|
8
|
+
# Connect to the database. The DSN will have been parsed at this point
|
9
|
+
# and the named parameters should need no explanation.
|
10
|
+
#
|
11
|
+
# If a block is provided to DBI.connect, the connected DatabaseHandle
|
12
|
+
# will be provided as the first argument to the block, and the
|
13
|
+
# DatabaseHandle will be disconnected upon block exit.
|
14
|
+
#
|
15
|
+
def connect(db_args, user, auth, params)
|
16
|
+
|
17
|
+
user = @handle.default_user[0] if user.nil?
|
18
|
+
auth = @handle.default_user[1] if auth.nil?
|
19
|
+
|
20
|
+
# TODO: what if only one of them is nil?
|
21
|
+
#if user.nil? and auth.nil? then
|
22
|
+
# user, auth = @handle.default_user
|
23
|
+
#end
|
24
|
+
|
25
|
+
params ||= {}
|
26
|
+
new_params = @handle.default_attributes
|
27
|
+
params.each {|k,v| new_params[k] = v}
|
28
|
+
|
29
|
+
if params.has_key?(:_convert_types)
|
30
|
+
@convert_types = params[:_convert_types]
|
31
|
+
end
|
32
|
+
|
33
|
+
db = @handle.connect(db_args, user, auth, new_params)
|
34
|
+
dbh = DatabaseHandle.new(db, @convert_types)
|
35
|
+
# FIXME trace
|
36
|
+
# dbh.trace(@trace_mode, @trace_output)
|
37
|
+
dbh.driver_name = @driver_name
|
38
|
+
|
39
|
+
if block_given?
|
40
|
+
begin
|
41
|
+
yield dbh
|
42
|
+
ensure
|
43
|
+
dbh.disconnect if dbh.connected?
|
44
|
+
end
|
45
|
+
else
|
46
|
+
return dbh
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# See BaseDriver#data_sources.
|
51
|
+
def data_sources
|
52
|
+
@handle.data_sources
|
53
|
+
end
|
54
|
+
|
55
|
+
# See BaseDriver#disconnect_all.
|
56
|
+
def disconnect_all
|
57
|
+
@handle.disconnect_all
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,375 @@
|
|
1
|
+
module DBI
|
2
|
+
#
|
3
|
+
# StatementHandle is the interface the consumer sees after successfully
|
4
|
+
# issuing a DatabaseHandle#prepare. They may also be exposed through other
|
5
|
+
# methods that send statements to the database.
|
6
|
+
#
|
7
|
+
# Almost all methods in this class will raise InterfaceError if the
|
8
|
+
# statement is already finished.
|
9
|
+
#
|
10
|
+
class StatementHandle < Handle
|
11
|
+
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
attr_accessor :dbh
|
15
|
+
|
16
|
+
def initialize(handle, fetchable=false, prepared=true, convert_types=true)
|
17
|
+
super(handle)
|
18
|
+
@fetchable = fetchable
|
19
|
+
@prepared = prepared # only false if immediate execute was used
|
20
|
+
@cols = nil
|
21
|
+
@coltypes = nil
|
22
|
+
@convert_types = convert_types
|
23
|
+
|
24
|
+
if @fetchable
|
25
|
+
@row = DBI::Row.new(column_names, column_types, nil, @convert_types)
|
26
|
+
else
|
27
|
+
@row = nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns true if the StatementHandle has had #finish called on it,
|
32
|
+
# explicitly or otherwise.
|
33
|
+
def finished?
|
34
|
+
@handle.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns true if the statement is believed to return data upon #fetch.
|
38
|
+
#
|
39
|
+
# The current reliability of this (and the concept in general) is
|
40
|
+
# suspect.
|
41
|
+
def fetchable?
|
42
|
+
@fetchable
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Instruct successive calls to #fetch to cast the type returned into
|
47
|
+
# `type`, for row position `pos`. Like all bind_* calls, `pos` indexes
|
48
|
+
# starting at 1.
|
49
|
+
#
|
50
|
+
# `type` is an object with the DBI::Type calling convention.
|
51
|
+
#
|
52
|
+
# This call must be called after #execute has successfully ran,
|
53
|
+
# otherwise it will raise InterfaceError.
|
54
|
+
#
|
55
|
+
# Example:
|
56
|
+
# # `foo` is an integer and this statement will return two rows.
|
57
|
+
# sth = dbh.prepare("select foo from bar")
|
58
|
+
# # would raise InterfaceError if called here
|
59
|
+
# sth.execute
|
60
|
+
#
|
61
|
+
# sth.bind_coltype(1, DBI::Type::Varchar)
|
62
|
+
# # would normally use DBI::Type::Integer and return a Fixnum. We'll make it a string.
|
63
|
+
# sth.fetch => ["1"]
|
64
|
+
#
|
65
|
+
# # Here we coerce it to Float.
|
66
|
+
# sth.bind_coltype(1, DBI::Type::Float)
|
67
|
+
# sth.fetch => [1.0]
|
68
|
+
# sth.finish
|
69
|
+
#
|
70
|
+
def bind_coltype(pos, type)
|
71
|
+
raise InterfaceError, "statement must be executed before using this command" unless @executed
|
72
|
+
|
73
|
+
coltypes = column_types
|
74
|
+
|
75
|
+
if (pos - 1) < 1
|
76
|
+
raise InterfaceError, "bind positions index starting at 1"
|
77
|
+
end
|
78
|
+
|
79
|
+
coltypes[pos-1] = type
|
80
|
+
@row = DBI::Row.new(column_names, coltypes, nil, @convert_types)
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# Just like BaseStatement#bind_param, but will attempt to convert the
|
85
|
+
# type if it's supposed to, adhering to the DBD's current ruleset.
|
86
|
+
#
|
87
|
+
def bind_param(param, value, attribs=nil)
|
88
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
89
|
+
raise InterfaceError, "Statement wasn't prepared before." unless @prepared
|
90
|
+
|
91
|
+
if @convert_types
|
92
|
+
value = DBI::Utils::ConvParam.conv_param(dbh.driver_name, value)[0]
|
93
|
+
end
|
94
|
+
|
95
|
+
@handle.bind_param(param, value, attribs)
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# Execute the statement.
|
100
|
+
#
|
101
|
+
# This generally means that the statement will be sent to the database
|
102
|
+
# and some form of result cursor will be obtained, but is ultimately
|
103
|
+
# driver-dependent.
|
104
|
+
#
|
105
|
+
# If arguments are supplied, these are fed to #bind_param.
|
106
|
+
def execute(*bindvars)
|
107
|
+
cancel # cancel before
|
108
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
109
|
+
raise InterfaceError, "Statement wasn't prepared before." unless @prepared
|
110
|
+
|
111
|
+
if @convert_types
|
112
|
+
bindvars = DBI::Utils::ConvParam.conv_param(dbh.driver_name, *bindvars)
|
113
|
+
end
|
114
|
+
|
115
|
+
@handle.bind_params(*bindvars)
|
116
|
+
@handle.execute
|
117
|
+
@fetchable = true
|
118
|
+
@executed = true
|
119
|
+
|
120
|
+
# TODO:?
|
121
|
+
#if @row.nil?
|
122
|
+
@row = DBI::Row.new(column_names, column_types, nil, @convert_types)
|
123
|
+
#end
|
124
|
+
return nil
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# Finish the statement, causing the database to release all assets
|
129
|
+
# related to it (any result cursors, normally).
|
130
|
+
#
|
131
|
+
# StatementHandles that have already been finished will normally be
|
132
|
+
# inoperable and unavailable for further use.
|
133
|
+
#
|
134
|
+
def finish
|
135
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
136
|
+
@handle.finish
|
137
|
+
@handle = nil
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Cancel the query, closing any open result cursors and truncating any result sets.
|
142
|
+
#
|
143
|
+
# The difference between this and #finish is that cancelled statements
|
144
|
+
# may be re-executed.
|
145
|
+
#
|
146
|
+
def cancel
|
147
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
148
|
+
@handle.cancel if @fetchable
|
149
|
+
@fetchable = false
|
150
|
+
end
|
151
|
+
|
152
|
+
#
|
153
|
+
# Obtains the column names for this query as an array.
|
154
|
+
#
|
155
|
+
def column_names
|
156
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
157
|
+
return @cols unless @cols.nil?
|
158
|
+
@cols = @handle.column_info.collect {|col| col['name'] }
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Obtain the type mappings for the columns in this query based on
|
163
|
+
# ColumnInfo data on the query.
|
164
|
+
#
|
165
|
+
# The result will be a position-dependent array of objects that conform
|
166
|
+
# to the DBI::Type calling syntax.
|
167
|
+
#
|
168
|
+
def column_types
|
169
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
170
|
+
return @coltypes unless @coltypes.nil?
|
171
|
+
@coltypes = @handle.column_info.collect do |col|
|
172
|
+
if col['dbi_type']
|
173
|
+
col['dbi_type']
|
174
|
+
else
|
175
|
+
DBI::TypeUtil.type_name_to_module(col['type_name'])
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# See BaseStatement#column_info.
|
182
|
+
#
|
183
|
+
def column_info
|
184
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
185
|
+
@handle.column_info.collect {|col| ColumnInfo.new(col) }
|
186
|
+
end
|
187
|
+
|
188
|
+
#
|
189
|
+
# Should return the row modified count as the result of statement execution.
|
190
|
+
#
|
191
|
+
# However, some low-level drivers do not supply this information or
|
192
|
+
# supply misleading information (> 0 rows for read-only select
|
193
|
+
# statements, f.e.)
|
194
|
+
#
|
195
|
+
def rows
|
196
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
197
|
+
@handle.rows
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
#
|
202
|
+
# See BaseStatement#fetch.
|
203
|
+
#
|
204
|
+
# fetch can also take a block which will be applied to each row in a
|
205
|
+
# similar fashion to Enumerable#collect. See #each.
|
206
|
+
#
|
207
|
+
def fetch(&p)
|
208
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
209
|
+
|
210
|
+
if block_given?
|
211
|
+
while (res = @handle.fetch) != nil
|
212
|
+
@row = @row.dup
|
213
|
+
@row.set_values(res)
|
214
|
+
yield @row
|
215
|
+
end
|
216
|
+
@handle.cancel
|
217
|
+
@fetchable = false
|
218
|
+
return nil
|
219
|
+
else
|
220
|
+
res = @handle.fetch
|
221
|
+
if res.nil?
|
222
|
+
@handle.cancel
|
223
|
+
@fetchable = false
|
224
|
+
else
|
225
|
+
@row = @row.dup
|
226
|
+
@row.set_values(res)
|
227
|
+
res = @row
|
228
|
+
end
|
229
|
+
return res
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
#
|
234
|
+
# Synonym for #fetch with a block.
|
235
|
+
#
|
236
|
+
def each(&p)
|
237
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
238
|
+
raise InterfaceError, "Statement must first be executed" unless @fetchable
|
239
|
+
raise InterfaceError, "No block given" unless block_given?
|
240
|
+
|
241
|
+
fetch(&p)
|
242
|
+
end
|
243
|
+
|
244
|
+
#
|
245
|
+
# Similar to #fetch, but returns Array of Array instead of Array of
|
246
|
+
# DBI::Row objects (and therefore does not perform type mapping). This
|
247
|
+
# is basically a way to get the raw data from the DBD.
|
248
|
+
#
|
249
|
+
def fetch_array
|
250
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
251
|
+
raise InterfaceError, "Statement must first be executed" unless @fetchable
|
252
|
+
|
253
|
+
if block_given?
|
254
|
+
while (res = @handle.fetch) != nil
|
255
|
+
yield res
|
256
|
+
end
|
257
|
+
@handle.cancel
|
258
|
+
@fetchable = false
|
259
|
+
return nil
|
260
|
+
else
|
261
|
+
res = @handle.fetch
|
262
|
+
if res.nil?
|
263
|
+
@handle.cancel
|
264
|
+
@fetchable = false
|
265
|
+
end
|
266
|
+
return res
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
#
|
271
|
+
# Map the columns and results into an Array of Hash resultset.
|
272
|
+
#
|
273
|
+
# No type conversion is performed here. Expect this to change in 0.6.0.
|
274
|
+
#
|
275
|
+
def fetch_hash
|
276
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
277
|
+
raise InterfaceError, "Statement must first be executed" unless @fetchable
|
278
|
+
|
279
|
+
cols = column_names
|
280
|
+
|
281
|
+
if block_given?
|
282
|
+
while (row = @handle.fetch) != nil
|
283
|
+
hash = {}
|
284
|
+
row.each_with_index {|v,i| hash[cols[i]] = v}
|
285
|
+
yield hash
|
286
|
+
end
|
287
|
+
@handle.cancel
|
288
|
+
@fetchable = false
|
289
|
+
return nil
|
290
|
+
else
|
291
|
+
row = @handle.fetch
|
292
|
+
if row.nil?
|
293
|
+
@handle.cancel
|
294
|
+
@fetchable = false
|
295
|
+
return nil
|
296
|
+
else
|
297
|
+
hash = {}
|
298
|
+
row.each_with_index {|v,i| hash[cols[i]] = v}
|
299
|
+
return hash
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
#
|
305
|
+
# Fetch `cnt` rows. Result is array of DBI::Row
|
306
|
+
#
|
307
|
+
def fetch_many(cnt)
|
308
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
309
|
+
raise InterfaceError, "Statement must first be executed" unless @fetchable
|
310
|
+
|
311
|
+
cols = column_names
|
312
|
+
rows = @handle.fetch_many(cnt)
|
313
|
+
if rows.nil?
|
314
|
+
@handle.cancel
|
315
|
+
@fetchable = false
|
316
|
+
return []
|
317
|
+
else
|
318
|
+
return rows.collect{|r| tmp = @row.dup; tmp.set_values(r); tmp }
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
#
|
323
|
+
# Fetch the entire result set. Result is array of DBI::Row.
|
324
|
+
#
|
325
|
+
def fetch_all
|
326
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
327
|
+
raise InterfaceError, "Statement must first be executed" unless @fetchable
|
328
|
+
|
329
|
+
cols = column_names
|
330
|
+
fetched_rows = []
|
331
|
+
|
332
|
+
begin
|
333
|
+
while row = fetch do
|
334
|
+
fetched_rows.push(row)
|
335
|
+
end
|
336
|
+
rescue Exception
|
337
|
+
end
|
338
|
+
|
339
|
+
@handle.cancel
|
340
|
+
@fetchable = false
|
341
|
+
|
342
|
+
return fetched_rows
|
343
|
+
end
|
344
|
+
|
345
|
+
#
|
346
|
+
# See BaseStatement#fetch_scroll.
|
347
|
+
#
|
348
|
+
def fetch_scroll(direction, offset=1)
|
349
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
350
|
+
raise InterfaceError, "Statement must first be executed" unless @fetchable
|
351
|
+
|
352
|
+
row = @handle.fetch_scroll(direction, offset)
|
353
|
+
if row.nil?
|
354
|
+
#@handle.cancel
|
355
|
+
#@fetchable = false
|
356
|
+
return nil
|
357
|
+
else
|
358
|
+
@row.set_values(row)
|
359
|
+
return @row
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# Get an attribute from the StatementHandle object.
|
364
|
+
def [] (attr)
|
365
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
366
|
+
@handle[attr]
|
367
|
+
end
|
368
|
+
|
369
|
+
# Set an attribute on the StatementHandle object.
|
370
|
+
def []= (attr, val)
|
371
|
+
raise InterfaceError, "Statement was already closed!" if @handle.nil?
|
372
|
+
@handle[attr] = val
|
373
|
+
end
|
374
|
+
end # class StatementHandle
|
375
|
+
end
|