akitaonrails-activerecord-sqlserver-adapter 1.1.0 → 1.1.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.
data/README ADDED
@@ -0,0 +1,31 @@
1
+ activerecord-sqlserver-adapter
2
+ ==============================
3
+
4
+ Originally forked from http://svn.rubyonrails.org/rails/adapters/sqlserver/
5
+ revision 9250
6
+
7
+ Incorporates changes from:
8
+
9
+ - Redmine's fixes:
10
+ http://www.redmine.org/attachments/817/sqlserver_adapter.rb
11
+
12
+ - Github's jrafanie:
13
+ http://github.com/jrafanie/rails-sqlserver-adapter/tree/master/sqlserver_adapter_rails.rb
14
+
15
+ - Github's adzap:
16
+ http://github.com/adzap/sqlserver_adapter_mods/tree/master/lib/sqlserver_adapter_mods
17
+
18
+ This gem should support SQL Server 2005 but not 2000 because of type changes.
19
+
20
+ Included ruby-dbi 0.2.2:
21
+ - http://rubyforge.org/frs/download.php/41304/dbi-0.2.2.zip
22
+
23
+ INSTALL
24
+ =======
25
+
26
+ gem install akitaonrails-activerecord-sqlserver-adapter --source=http://gems.github.com
27
+
28
+ Contact:
29
+ ========
30
+
31
+ Fabio Akita (fabioakita@gmail.com)
@@ -0,0 +1,44 @@
1
+ == Creating the test database
2
+
3
+ The default names for the test databases are "activerecord_unittest" and
4
+ "activerecord_unittest2". If you want to use another database name then be sure
5
+ to update the connection adapter setups you want to test with in
6
+ test/connections/<your database>/connection.rb.
7
+
8
+
9
+ == Requirements
10
+
11
+ The tests of this adapter depend on the existence of rails edge. All the tests
12
+ defined by rails edge are re-used. For this to work the following directory
13
+ structure is assumed to exist:
14
+
15
+ #{RAILS_ROOT}/vendor/plugins/adapters/sqlserver
16
+ #{RAILS_ROOT}/vendor/rails/activerecord/test
17
+
18
+ Define a user named 'rails' in SQL Server with all privileges granted. Use an empty
19
+ password for user 'rails', or alternatively use the OSQLPASSWORD environment variable
20
+ which allows you to set a default password for the current session.
21
+
22
+ Then run "rake create_databases".
23
+
24
+
25
+ == Running with Rake
26
+
27
+ The easiest way to run the unit tests is through Rake. Either run "rake test_sqlserver"
28
+ or "rake test_sqlserver_odbc". For more information, checkout the full array
29
+ of rake tasks with "rake -T"
30
+
31
+ Rake can be found at http://rake.rubyforge.org
32
+
33
+
34
+ == Running by hand
35
+
36
+ Unit tests are located in test directory. If you only want to run a single test suite,
37
+ you can do so with:
38
+
39
+ rake test_sqlserver TEST=base_test.rb
40
+
41
+ That'll run the base suite using the SQLServer-Ruby adapter.
42
+
43
+
44
+
@@ -0,0 +1,89 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/packagetask'
5
+ require 'rake/gempackagetask'
6
+ require 'rake/contrib/rubyforgepublisher'
7
+
8
+ PKG_NAME = 'activerecord-sqlserver-adapter'
9
+ PKG_BUILD = (".#{ENV['PKG_BUILD']}" if ENV['PKG_BUILD'])
10
+ PKG_VERSION = "1.0.0#{PKG_BUILD}"
11
+
12
+ spec = Gem::Specification.new do |s|
13
+ s.name = PKG_NAME
14
+ s.summary = 'SQL Server adapter for Active Record'
15
+ s.version = PKG_VERSION
16
+
17
+ s.add_dependency 'activerecord', '>= 1.15.5.7843'
18
+ s.require_path = 'lib'
19
+
20
+ s.files = %w(lib/active_record/connection_adapters/sqlserver_adapter.rb)
21
+
22
+ s.author = 'Tom Ward'
23
+ s.email = 'tom@popdog.net'
24
+ s.homepage = 'http://wiki.rubyonrails.org/rails/pages/SQL+Server'
25
+ s.rubyforge_project = 'activerecord'
26
+ end
27
+
28
+ Rake::GemPackageTask.new(spec) do |p|
29
+ p.gem_spec = spec
30
+ p.need_tar = true
31
+ p.need_zip = true
32
+ end
33
+
34
+ desc "Publish the beta gem"
35
+ task :pgem => :package do
36
+ Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_NAME}-#{PKG_VERSION}.gem").upload
37
+ `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
38
+ end
39
+
40
+ desc "Publish the release files to RubyForge."
41
+ task :release => :package do
42
+ require 'rubyforge'
43
+
44
+ packages = %w(gem tgz zip).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
45
+
46
+ rubyforge = RubyForge.new
47
+ rubyforge.login
48
+ rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
49
+ end
50
+
51
+
52
+ SCHEMA_PATH = File.join(File.dirname(__FILE__), *%w(test fixtures db_definitions))
53
+
54
+ desc 'Create the SQL Server test databases'
55
+ task :create_databases do
56
+ # Define a user named 'rails' in SQL Server with all privileges granted
57
+ # Use an empty password for user 'rails', or alternatively use the OSQLPASSWORD environment variable
58
+ # which allows you to set a default password for the current session.
59
+ %x( osql -S localhost -U rails -Q "create database activerecord_unittest" -P )
60
+ %x( osql -S localhost -U rails -Q "create database activerecord_unittest2" -P )
61
+ %x( osql -S localhost -U rails -d activerecord_unittest -Q "exec sp_grantdbaccess 'rails'" -P )
62
+ %x( osql -S localhost -U rails -d activerecord_unittest2 -Q "exec sp_grantdbaccess 'rails'" -P )
63
+ %x( osql -S localhost -U rails -d activerecord_unittest -Q "grant BACKUP DATABASE, BACKUP LOG, CREATE DEFAULT, CREATE FUNCTION, CREATE PROCEDURE, CREATE RULE, CREATE TABLE, CREATE VIEW to 'rails';" -P )
64
+ %x( osql -S localhost -U rails -d activerecord_unittest2 -Q "grant BACKUP DATABASE, BACKUP LOG, CREATE DEFAULT, CREATE FUNCTION, CREATE PROCEDURE, CREATE RULE, CREATE TABLE, CREATE VIEW to 'rails';" -P )
65
+ end
66
+
67
+ desc 'Drop the SQL Server test databases'
68
+ task :drop_databases do
69
+ %x( osql -S localhost -U rails -Q "drop database activerecord_unittest" -P )
70
+ %x( osql -S localhost -U rails -Q "drop database activerecord_unittest2" -P )
71
+ end
72
+
73
+ desc 'Recreate the SQL Server test databases'
74
+ task :recreate_databases => [:drop_databases, :create_databases]
75
+
76
+
77
+ for adapter in %w( sqlserver sqlserver_odbc )
78
+ Rake::TestTask.new("test_#{adapter}") { |t|
79
+ t.libs << "test"
80
+ t.libs << "test/connections/native_#{adapter}"
81
+ t.libs << "../../../rails/activerecord/test/"
82
+ t.pattern = ["test/**/*_test_sqlserver.rb", "../../../rails/activerecord/test/**/*_test.rb"]
83
+ t.verbose = true
84
+ }
85
+
86
+ namespace adapter do
87
+ task :test => "test_#{adapter}"
88
+ end
89
+ end
@@ -0,0 +1,15 @@
1
+ spec = Gem::Specification.new do |s|
2
+ s.name = "activerecord-sqlserver-adapter"
3
+ s.summary = 'SQL Server adapter for Active Record'
4
+ s.version = "1.1.1"
5
+
6
+ s.add_dependency 'activerecord', '>= 1.15.5.7843'
7
+ s.require_path = 'lib'
8
+
9
+ s.files = ["activerecord-sqlserver-adapter.gemspec", "lib", "lib/active_record", "lib/active_record/connection_adapters", "lib/active_record/connection_adapters/sqlserver_adapter.rb", "lib/activerecord-sqlserver-adapter.rb", "lib/dbd", "lib/dbd/ADO.rb", "lib/dbi", "lib/dbi/columninfo.rb", "lib/dbi/row.rb", "lib/dbi/sql.rb", "lib/dbi/trace.rb", "lib/dbi/utils.rb", "lib/dbi/version.rb", "lib/dbi.rb", "lib/rails_fcgi", "lib/rails_fcgi/fixes.rb", "lib/rails_fcgi.rb", "Rakefile", "README", "RUNNING_UNIT_TESTS", "test", "test/aaaa_create_tables_test_sqlserver.rb", "test/affected_rows_test_sqlserver.rb", "test/connections", "test/connections/native_sqlserver", "test/connections/native_sqlserver/connection.rb", "test/connections/native_sqlserver_odbc", "test/connections/native_sqlserver_odbc/connection.rb", "test/fixtures", "test/fixtures/db_definitions", "test/fixtures/db_definitions/sqlserver.drop.sql", "test/fixtures/db_definitions/sqlserver.sql", "test/fixtures/db_definitions/sqlserver2.drop.sql", "test/fixtures/db_definitions/sqlserver2.sql"]
10
+
11
+ s.author = 'Tom Ward'
12
+ s.email = 'tom@popdog.net'
13
+ s.homepage = 'http://wiki.rubyonrails.org/rails/pages/SQL+Server'
14
+ s.rubyforge_project = 'activerecord'
15
+ end
@@ -0,0 +1,3 @@
1
+ require 'dbi.rb'
2
+ require "dbd/ADO.rb"
3
+ require "active_record/connection_adapters/sqlserver_adapter.rb"
@@ -0,0 +1,229 @@
1
+ #
2
+ # DBD::ADO
3
+ #
4
+ # Copyright (c) 2001, 2002 Michael Neumann <neumann@s-direktnet.de>
5
+ #
6
+ # All rights reserved.
7
+ #
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions
10
+ # are met:
11
+ # 1. Redistributions of source code must retain the above copyright
12
+ # notice, this list of conditions and the following disclaimer.
13
+ # 2. Redistributions in binary form must reproduce the above copyright
14
+ # notice, this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ # 3. The name of the author may not be used to endorse or promote products
17
+ # derived from this software without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
21
+ # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22
+ # THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25
+ # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
27
+ # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
28
+ # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+ #
30
+ # $Id$
31
+ #
32
+
33
+ require "win32ole"
34
+
35
+ module DBI
36
+ module DBD
37
+ module ADO
38
+
39
+ VERSION = "0.1"
40
+ USED_DBD_VERSION = "0.1"
41
+
42
+ class Driver < DBI::BaseDriver
43
+
44
+ def initialize
45
+ super(USED_DBD_VERSION)
46
+ end
47
+
48
+ def connect(dbname, user, auth, attr)
49
+ # connect to database
50
+
51
+ handle = WIN32OLE.new('ADODB.Connection')
52
+ handle.Open(dbname)
53
+ handle.BeginTrans() # start new Transaction
54
+
55
+ return Database.new(handle, attr)
56
+ rescue RuntimeError => err
57
+ raise DBI::DatabaseError.new(err.message)
58
+ end
59
+
60
+ end
61
+
62
+ class Database < DBI::BaseDatabase
63
+
64
+ def disconnect
65
+ @handle.RollbackTrans()
66
+ @handle.Close()
67
+ rescue RuntimeError => err
68
+ raise DBI::DatabaseError.new(err.message)
69
+ end
70
+
71
+ def prepare(statement)
72
+ # TODO: create Command instead?
73
+ Statement.new(@handle, statement, self)
74
+ end
75
+
76
+ def commit
77
+ # TODO: raise error if AutoCommit on => better in DBI?
78
+ @handle.CommitTrans()
79
+ @handle.BeginTrans()
80
+ rescue RuntimeError => err
81
+ raise DBI::DatabaseError.new(err.message)
82
+ end
83
+
84
+ def rollback
85
+ # TODO: raise error if AutoCommit on => better in DBI?
86
+ @handle.RollbackTrans()
87
+ @handle.BeginTrans()
88
+ rescue RuntimeError => err
89
+ raise DBI::DatabaseError.new(err.message)
90
+ end
91
+
92
+ def []=(attr, value)
93
+ if attr == 'AutoCommit' then
94
+ # TODO: commit current transaction?
95
+ @attr[attr] = value
96
+ else
97
+ super
98
+ end
99
+ end
100
+
101
+
102
+ end # class Database
103
+
104
+
105
+ class Statement < DBI::BaseStatement
106
+ include SQL::BasicBind
107
+ include SQL::BasicQuote
108
+
109
+ def initialize(handle, statement, db)
110
+ @handle = handle
111
+ @statement = statement
112
+ @params = []
113
+ @db = db
114
+ end
115
+
116
+ def bind_param(param, value, attribs)
117
+ raise InterfaceError, "only ? parameters supported" unless param.is_a? Fixnum
118
+
119
+ @params[param-1] = value
120
+ end
121
+
122
+ def execute
123
+ # TODO: use Command and Parameter
124
+ # TODO: substitute all ? by the parametes
125
+ sql = bind(self, @statement, @params)
126
+ @res_handle = @handle.Execute(sql)
127
+
128
+ # TODO: SELECT and AutoCommit finishes the result-set
129
+ # what to do?
130
+ if @db['AutoCommit'] == true and not SQL.query?(@statement) then
131
+ @db.commit
132
+ end
133
+
134
+ rescue RuntimeError => err
135
+ raise DBI::DatabaseError.new(err.message)
136
+ end
137
+
138
+ def finish
139
+ # if DCL, DDL or INSERT UPDATE and DELETE, this gives an Error
140
+ # because no Result-Set is available
141
+ if @res_handle.Fields.Count() != 0 then
142
+ @res_handle.Close()
143
+ end
144
+ rescue RuntimeError => err
145
+ raise DBI::DatabaseError.new(err.message)
146
+ end
147
+
148
+ def fetch
149
+ retval = fetch_currentrow
150
+ @res_handle.MoveNext() unless retval.nil?
151
+ retval
152
+ rescue RuntimeError => err
153
+ raise DBI::DatabaseError.new(err.message)
154
+ end
155
+
156
+
157
+
158
+ def fetch_scroll(direction, offset)
159
+ case direction
160
+ when DBI::SQL_FETCH_NEXT
161
+ return fetch
162
+ when DBI::SQL_FETCH_PRIOR
163
+ # TODO: check if already the first?
164
+ #return nil if @res_handle.AbsolutePosition()
165
+ @res_handle.MovePrevious()
166
+ return fetch_currentrow
167
+ when DBI::SQL_FETCH_FIRST
168
+ @res_handle.MoveFirst()
169
+ return fetch_currentrow
170
+ when DBI::SQL_FETCH_LAST
171
+ @res_handle.MoveLast()
172
+ return fetch_currentrow
173
+ when DBI::SQL_FETCH_RELATIVE
174
+ @res_handle.Move(offset)
175
+ return fetch_currentrow
176
+ when DBI::SQL_FETCH_ABSOLUTE
177
+ ap = @res_handle.AbsolutePositon()
178
+ @res_handle.Move(offset-ap)
179
+ return fetch_currentrow
180
+ else
181
+ raise DBI::InterfaceError
182
+ end
183
+ rescue RuntimeError => err
184
+ raise DBI::DatabaseError.new(err.message)
185
+ end
186
+
187
+ def column_info
188
+ num_cols = @res_handle.Fields().Count()
189
+ retval = Array.new(num_cols)
190
+
191
+ for i in 0...num_cols do
192
+ retval[i] = {'name' => @res_handle.Fields(i).Name()}
193
+ end
194
+
195
+ retval
196
+ rescue RuntimeError => err
197
+ raise DBI::DatabaseError.new(err.message)
198
+ end
199
+
200
+ def rows
201
+ # TODO: how to get the RPC in ADO?
202
+ nil
203
+ end
204
+
205
+
206
+ private
207
+
208
+ def fetch_currentrow
209
+ return nil if @res_handle.EOF() or @res_handle.BOF()
210
+
211
+ # TODO: don't create new Array each time
212
+ num_cols = @res_handle.Fields().Count()
213
+ retval = Array.new(num_cols)
214
+
215
+ for i in 0...num_cols do
216
+ retval[i] = @res_handle.Fields(i).Value()
217
+ end
218
+
219
+ retval
220
+ end
221
+
222
+
223
+ end
224
+
225
+
226
+ end # module ADO
227
+ end # module DBD
228
+ end # module DBI
229
+
@@ -0,0 +1,1043 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ #
3
+ # Ruby/DBI
4
+ #
5
+ # Copyright (c) 2001, 2002, 2003 Michael Neumann <mneumann@ntecs.de>
6
+ #
7
+ # All rights reserved.
8
+ #
9
+ # Redistribution and use in source and binary forms, with or without
10
+ # modification, are permitted provided that the following conditions
11
+ # are met:
12
+ # 1. Redistributions of source code must retain the above copyright
13
+ # notice, this list of conditions and the following disclaimer.
14
+ # 2. Redistributions in binary form must reproduce the above copyright
15
+ # notice, this list of conditions and the following disclaimer in the
16
+ # documentation and/or other materials provided with the distribution.
17
+ # 3. The name of the author may not be used to endorse or promote products
18
+ # derived from this software without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
21
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
22
+ # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
23
+ # THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
26
+ # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
28
+ # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
29
+ # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+ #
31
+ # $Id: dbi.rb,v 1.8 2006/09/03 04:05:29 pdubois Exp $
32
+ #
33
+
34
+ require "find"
35
+ require "dbi/row"
36
+ require "dbi/utils"
37
+ require "dbi/sql"
38
+ require "dbi/columninfo"
39
+ require "date"
40
+ require "thread"
41
+ require 'monitor'
42
+
43
+ module DBI
44
+ VERSION = "0.2.2"
45
+
46
+ module DBD
47
+ DIR = "DBD"
48
+ API_VERSION = "0.3"
49
+ end
50
+
51
+ # Constants
52
+
53
+ # Constants for fetch_scroll
54
+ #
55
+ SQL_FETCH_NEXT = 1
56
+ SQL_FETCH_PRIOR = 2
57
+ SQL_FETCH_FIRST = 3
58
+ SQL_FETCH_LAST = 4
59
+ SQL_FETCH_ABSOLUTE = 5
60
+ SQL_FETCH_RELATIVE = 6
61
+
62
+ # SQL type constants
63
+ #
64
+ SQL_CHAR = 1
65
+ SQL_NUMERIC = 2
66
+ SQL_DECIMAL = 3
67
+ SQL_INTEGER = 4
68
+ SQL_SMALLINT = 5
69
+ SQL_FLOAT = 6
70
+ SQL_REAL = 7
71
+ SQL_DOUBLE = 8
72
+ SQL_DATE = 9 # 91
73
+ SQL_TIME = 10 # 92
74
+ SQL_TIMESTAMP = 11 # 93
75
+ SQL_VARCHAR = 12
76
+ SQL_BOOLEAN = 13
77
+
78
+ SQL_LONGVARCHAR = -1
79
+ SQL_BINARY = -2
80
+ SQL_VARBINARY = -3
81
+ SQL_LONGVARBINARY = -4
82
+ SQL_BIGINT = -5
83
+ SQL_TINYINT = -6
84
+ SQL_BIT = -7
85
+
86
+ # TODO
87
+ # Find types for these (XOPEN?)
88
+ #SQL_ARRAY =
89
+ SQL_BLOB = -10 # TODO
90
+ SQL_CLOB = -11 # TODO
91
+ #SQL_DISTINCT =
92
+ #SQL_OBJECT =
93
+ #SQL_NULL =
94
+ SQL_OTHER = 100
95
+ #SQL_REF =
96
+ #SQL_STRUCT =
97
+
98
+ SQL_TYPE_NAMES = {
99
+ SQL_BIT => 'BIT',
100
+ SQL_TINYINT => 'TINYINT',
101
+ SQL_SMALLINT => 'SMALLINT',
102
+ SQL_INTEGER => 'INTEGER',
103
+ SQL_BIGINT => 'BIGINT',
104
+ SQL_FLOAT => 'FLOAT',
105
+ SQL_REAL => 'REAL',
106
+ SQL_DOUBLE => 'DOUBLE',
107
+ SQL_NUMERIC => 'NUMERIC',
108
+ SQL_DECIMAL => 'DECIMAL',
109
+ SQL_CHAR => 'CHAR',
110
+ SQL_VARCHAR => 'VARCHAR',
111
+ SQL_LONGVARCHAR => 'LONG VARCHAR',
112
+ SQL_DATE => 'DATE',
113
+ SQL_TIME => 'TIME',
114
+ SQL_TIMESTAMP => 'TIMESTAMP',
115
+ SQL_BINARY => 'BINARY',
116
+ SQL_VARBINARY => 'VARBINARY',
117
+ SQL_LONGVARBINARY => 'LONG VARBINARY',
118
+ SQL_BLOB => 'BLOB',
119
+ SQL_CLOB => 'CLOB',
120
+ SQL_OTHER => nil,
121
+ SQL_BOOLEAN => 'BOOLEAN',
122
+
123
+ }
124
+
125
+ # Exceptions (borrowed by Python API 2.0)
126
+
127
+ # Base class of all other error exceptions. Use this to catch all DBI
128
+ # errors.
129
+ class Error < RuntimeError
130
+ end
131
+
132
+ # For important warnings like data truncation, etc.
133
+ class Warning < RuntimeError
134
+ end
135
+
136
+ # Exception for errors related to the DBI interface rather than the
137
+ # database itself.
138
+ class InterfaceError < Error
139
+ end
140
+
141
+ # Exception raised if the DBD driver has not specified a mandatory method.
142
+ class NotImplementedError < InterfaceError
143
+ end
144
+
145
+ # Exception for errors related to the database.
146
+ class DatabaseError < Error
147
+ attr_reader :err, :errstr, :state
148
+
149
+ def initialize(errstr="", err=nil, state=nil)
150
+ super(errstr)
151
+ @err, @errstr, @state = err, errstr, state
152
+ end
153
+ end
154
+
155
+ # Exception for errors due to problems with the processed
156
+ # data such as division by zero, numeric value out of range, etc.
157
+ class DataError < DatabaseError
158
+ end
159
+
160
+ # Exception for errors related to the database's operation which are not
161
+ # necessarily under the control of the programmer. This includes such
162
+ # things as unexpected disconnect, datasource name not found, transaction
163
+ # could not be processed, a memory allocation error occured during
164
+ # processing, etc.
165
+ class OperationalError < DatabaseError
166
+ end
167
+
168
+ # Exception raised when the relational integrity of the database
169
+ # is affected, e.g. a foreign key check fails.
170
+ class IntegrityError < DatabaseError
171
+ end
172
+
173
+ # Exception raised when the database encounters an internal error,
174
+ # e.g. the cursor is not valid anymore, the transaction is out of sync.
175
+ class InternalError < DatabaseError
176
+ end
177
+
178
+ # Exception raised for programming errors, e.g. table not found
179
+ # or already exists, syntax error in SQL statement, wrong number
180
+ # of parameters specified, etc.
181
+ class ProgrammingError < DatabaseError
182
+ end
183
+
184
+ # Exception raised if e.g. commit() is called for a database which do not
185
+ # support transactions.
186
+ class NotSupportedError < DatabaseError
187
+ end
188
+
189
+ # Datatypes
190
+
191
+ # TODO: do we need Binary?
192
+ # perhaps easier to call #bind_param(1, binary_string, 'type' => SQL_BLOB)
193
+ class Binary
194
+ attr_accessor :data
195
+ def initialize(data)
196
+ @data = data
197
+ end
198
+
199
+ def to_s
200
+ @data
201
+ end
202
+ end
203
+
204
+ # Module functions (of DBI)
205
+ DEFAULT_TRACE_MODE = 2
206
+ DEFAULT_TRACE_OUTPUT = STDERR
207
+
208
+ # TODO: Is using class variables within a module such a wise idea? - Dan B.
209
+ @@driver_map = Hash.new
210
+ @@driver_monitor = ::Monitor.new()
211
+ @@trace_mode = DEFAULT_TRACE_MODE
212
+ @@trace_output = DEFAULT_TRACE_OUTPUT
213
+
214
+ class << self
215
+
216
+ # Establish a database connection. This is mostly a facade for the
217
+ # DBD's connect method.
218
+ def connect(driver_url, user=nil, auth=nil, params=nil, &p)
219
+ dr, db_args = _get_full_driver(driver_url)
220
+ dh = dr[0] # driver-handle
221
+ dh.connect(db_args, user, auth, params, &p)
222
+ end
223
+
224
+ # Load a DBD and returns the DriverHandle object
225
+ def get_driver(driver_url)
226
+ _get_full_driver(driver_url)[0][0] # return DriverHandle
227
+ end
228
+
229
+ # Extracts the db_args from driver_url and returns the correspondeing
230
+ # entry of the @@driver_map.
231
+ def _get_full_driver(driver_url)
232
+ db_driver, db_args = parse_url(driver_url)
233
+ db_driver = load_driver(db_driver)
234
+ dr = @@driver_map[db_driver]
235
+ [dr, db_args]
236
+ end
237
+
238
+ def trace(mode=nil, output=nil)
239
+ @@trace_mode = mode || @@trace_mode || DBI::DEFAULT_TRACE_MODE
240
+ @@trace_output = output || @@trace_output || DBI::DEFAULT_TRACE_OUTPUT
241
+ end
242
+
243
+ # Returns a list of the currently available drivers on your system in
244
+ # 'dbi:driver:' format.
245
+ def available_drivers
246
+ drivers = []
247
+ #path = File.dirname(File.dirname(__FILE__)) + "/" + DBD::DIR
248
+ path = File.dirname(__FILE__) + "/" + DBD::DIR
249
+ Find.find(path){ |f|
250
+ if File.file?(f)
251
+ driver = File.basename(f, ".rb")
252
+ drivers.push("dbi:#{driver}:")
253
+ end
254
+ }
255
+ drivers
256
+ end
257
+
258
+ def data_sources(driver)
259
+ db_driver, = parse_url(driver)
260
+ db_driver = load_driver(db_driver)
261
+ dh = @@driver_map[db_driver][0]
262
+ dh.data_sources
263
+ end
264
+
265
+ def disconnect_all( driver = nil )
266
+ if driver.nil?
267
+ @@driver_map.each {|k,v| v[0].disconnect_all}
268
+ else
269
+ db_driver, = parse_url(driver)
270
+ @@driver_map[db_driver][0].disconnect_all
271
+ end
272
+ end
273
+
274
+
275
+ private
276
+
277
+ ##
278
+ # extended by John Gorman <jgorman@webbysoft.com> for
279
+ # case insensitive DBD names
280
+ #
281
+ def load_driver(driver_name)
282
+ @@driver_monitor.synchronize do
283
+ if @@driver_map[driver_name].nil?
284
+
285
+ dc = driver_name.downcase
286
+
287
+ # caseless look for drivers already loaded
288
+ found = @@driver_map.keys.find {|key| key.downcase == dc}
289
+ return found if found
290
+
291
+ if $SAFE >= 1
292
+ # case-sensitive in safe mode
293
+ require "#{DBD::DIR}/#{driver_name}/#{driver_name}"
294
+ else
295
+ # FIXME this whole require scheme is so horribly stupid I
296
+ # can't even begin to think about how much I'd enjoy what
297
+ # was smoked when this was thought up.
298
+ #
299
+ # Here's a hack to get tests to work.
300
+ # try a quick load and then a caseless scan
301
+ begin
302
+ begin
303
+ require "dbd/#{driver_name}"
304
+ rescue LoadError
305
+ require "#{DBD::DIR}/#{driver_name}/#{driver_name}"
306
+ end
307
+ rescue LoadError
308
+ $LOAD_PATH.each do |dir|
309
+ path = "#{dir}/dbd"
310
+ if FileTest.directory?(path)
311
+ require "#{path}/#{driver_name}"
312
+ break
313
+ end
314
+
315
+ path = "#{dir}/#{DBD::DIR}"
316
+ next unless FileTest.directory?(path)
317
+ found = Dir.entries(path).find {|e| e.downcase == dc}
318
+ next unless found
319
+
320
+ require "#{path}/#{found}/#{found}"
321
+ break
322
+ end
323
+ end
324
+ end
325
+
326
+ found ||= driver_name
327
+
328
+ # On a filesystem that is not case-sensitive (e.g., HFS+ on Mac OS X),
329
+ # the initial require attempt that loads the driver may succeed even
330
+ # though the lettercase of driver_name doesn't match the actual
331
+ # filename. If that happens, const_get will fail and it become
332
+ # necessary to look though the list of constants and look for a
333
+ # caseless match. The result of this match provides the constant
334
+ # with the proper lettercase -- which can be used to generate the
335
+ # driver handle.
336
+
337
+ dr = nil
338
+ begin
339
+ dr = DBI::DBD.const_get(found.intern)
340
+ rescue NameError
341
+ # caseless look for constants to find actual constant
342
+ found = found.downcase
343
+ found = DBI::DBD.constants.find { |e| e.downcase == found }
344
+ dr = DBI::DBD.const_get(found.intern) unless found.nil?
345
+ end
346
+
347
+ # If dr is nil at this point, it means the underlying driver
348
+ # failed to load. This usually means it's not installed, but
349
+ # can fail for other reasons.
350
+ if dr.nil?
351
+ err = "Unable to load driver '#{driver_name}'"
352
+ raise DBI::InterfaceError, err
353
+ end
354
+
355
+ dbd_dr = dr::Driver.new
356
+ drh = DBI::DriverHandle.new(dbd_dr)
357
+ drh.trace(@@trace_mode, @@trace_output)
358
+ @@driver_map[found] = [drh, dbd_dr]
359
+ return found
360
+ else
361
+ return driver_name
362
+ end
363
+ end
364
+ rescue LoadError, NameError
365
+ if $SAFE >= 1
366
+ raise InterfaceError, "Could not load driver (#{$!.message}). Note that in SAFE mode >= 1, driver URLs have to be case sensitive!"
367
+ else
368
+ raise InterfaceError, "Could not load driver (#{$!.message})"
369
+ end
370
+ end
371
+
372
+ # Splits a DBI URL into two components - the database driver name
373
+ # and the datasource (along with any options, if any) and returns
374
+ # a two element array, e.g. 'dbi:foo:bar' would return ['foo','bar'].
375
+ #
376
+ # A regular expression is used instead of a simple split to validate
377
+ # the proper format for the URL. If it isn't correct, an Interface
378
+ # error is raised.
379
+ def parse_url(driver_url)
380
+ if driver_url =~ /^(DBI|dbi):([^:]+)(:(.*))$/
381
+ [$2, $4]
382
+ else
383
+ raise InterfaceError, "Invalid Data Source Name"
384
+ end
385
+ end
386
+
387
+ end # self
388
+
389
+
390
+
391
+ #----------------------------------------------------
392
+ # Dispatch classes
393
+ #----------------------------------------------------
394
+
395
+
396
+ ##
397
+ # Dispatch classes (Handle, DriverHandle, DatabaseHandle and StatementHandle)
398
+ #
399
+
400
+ class Handle
401
+ attr_reader :trace_mode, :trace_output
402
+ attr_reader :handle
403
+
404
+ def initialize(handle)
405
+ @handle = handle
406
+ @trace_mode = @trace_output = nil
407
+ end
408
+
409
+ def trace(mode=nil, output=nil)
410
+ @trace_mode = mode || @trace_mode || DBI::DEFAULT_TRACE_MODE
411
+ @trace_output = output || @trace_output || DBI::DEFAULT_TRACE_OUTPUT
412
+ end
413
+
414
+
415
+ ##
416
+ # call a driver specific function
417
+ #
418
+ def func(function, *values)
419
+ if @handle.respond_to?("__" + function.to_s) then
420
+ @handle.send("__" + function.to_s, *values)
421
+ else
422
+ raise InterfaceError, "Driver specific function <#{function}> not available."
423
+ end
424
+ rescue ArgumentError
425
+ raise InterfaceError, "Wrong # of arguments for driver specific function"
426
+ end
427
+
428
+ # error functions?
429
+ end
430
+
431
+ class DriverHandle < Handle
432
+
433
+ def connect(db_args, user, auth, params)
434
+
435
+ user = @handle.default_user[0] if user.nil?
436
+ auth = @handle.default_user[1] if auth.nil?
437
+
438
+ # TODO: what if only one of them is nil?
439
+ #if user.nil? and auth.nil? then
440
+ # user, auth = @handle.default_user
441
+ #end
442
+
443
+ params ||= {}
444
+ new_params = @handle.default_attributes
445
+ params.each {|k,v| new_params[k] = v}
446
+
447
+
448
+ db = @handle.connect(db_args, user, auth, new_params)
449
+ dbh = DatabaseHandle.new(db)
450
+ dbh.trace(@trace_mode, @trace_output)
451
+
452
+ if block_given?
453
+ begin
454
+ yield dbh
455
+ ensure
456
+ dbh.disconnect if dbh.connected?
457
+ end
458
+ else
459
+ return dbh
460
+ end
461
+ end
462
+
463
+ def data_sources
464
+ @handle.data_sources
465
+ end
466
+
467
+ def disconnect_all
468
+ @handle.disconnect_all
469
+ end
470
+ end
471
+
472
+ class DatabaseHandle < Handle
473
+
474
+ include DBI::Utils::ConvParam
475
+
476
+ def connected?
477
+ not @handle.nil?
478
+ end
479
+
480
+ def disconnect
481
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
482
+ @handle.disconnect
483
+ @handle = nil
484
+ end
485
+
486
+ def prepare(stmt)
487
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
488
+ sth = StatementHandle.new(@handle.prepare(stmt), false)
489
+ sth.trace(@trace_mode, @trace_output)
490
+
491
+ if block_given?
492
+ begin
493
+ yield sth
494
+ ensure
495
+ sth.finish unless sth.finished?
496
+ end
497
+ else
498
+ return sth
499
+ end
500
+ end
501
+
502
+ def execute(stmt, *bindvars)
503
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
504
+ sth = StatementHandle.new(@handle.execute(stmt, *conv_param(*bindvars)), true, false)
505
+ sth.trace(@trace_mode, @trace_output)
506
+
507
+ if block_given?
508
+ begin
509
+ yield sth
510
+ ensure
511
+ sth.finish unless sth.finished?
512
+ end
513
+ else
514
+ return sth
515
+ end
516
+ end
517
+
518
+ def do(stmt, *bindvars)
519
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
520
+ @handle.do(stmt, *conv_param(*bindvars))
521
+ end
522
+
523
+ def select_one(stmt, *bindvars)
524
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
525
+ row = nil
526
+ execute(stmt, *bindvars) do |sth|
527
+ row = sth.fetch
528
+ end
529
+ row
530
+ end
531
+
532
+ def select_all(stmt, *bindvars, &p)
533
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
534
+ rows = nil
535
+ execute(stmt, *bindvars) do |sth|
536
+ if block_given?
537
+ sth.each(&p)
538
+ else
539
+ rows = sth.fetch_all
540
+ end
541
+ end
542
+ return rows
543
+ end
544
+
545
+ def tables
546
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
547
+ @handle.tables
548
+ end
549
+
550
+ def columns( table )
551
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
552
+ @handle.columns( table ).collect {|col| ColumnInfo.new(col) }
553
+ end
554
+
555
+ def ping
556
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
557
+ @handle.ping
558
+ end
559
+
560
+ def quote(value)
561
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
562
+ @handle.quote(value)
563
+ end
564
+
565
+ def commit
566
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
567
+ @handle.commit
568
+ end
569
+
570
+ def rollback
571
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
572
+ @handle.rollback
573
+ end
574
+
575
+ def transaction
576
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
577
+ raise InterfaceError, "No block given" unless block_given?
578
+
579
+ commit
580
+ begin
581
+ yield self
582
+ commit
583
+ rescue Exception
584
+ rollback
585
+ raise
586
+ end
587
+ end
588
+
589
+
590
+ def [] (attr)
591
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
592
+ @handle[attr]
593
+ end
594
+
595
+ def []= (attr, val)
596
+ raise InterfaceError, "Database connection was already closed!" if @handle.nil?
597
+ @handle[attr] = val
598
+ end
599
+
600
+ end
601
+
602
+ class StatementHandle < Handle
603
+
604
+ include Enumerable
605
+ include DBI::Utils::ConvParam
606
+
607
+ def initialize(handle, fetchable=false, prepared=true)
608
+ super(handle)
609
+ @fetchable = fetchable
610
+ @prepared = prepared # only false if immediate execute was used
611
+ @cols = nil
612
+
613
+ # TODO: problems with other DB's?
614
+ #@row = DBI::Row.new(column_names,nil)
615
+ if @fetchable
616
+ @row = DBI::Row.new(column_names,nil)
617
+ else
618
+ @row = nil
619
+ end
620
+ end
621
+
622
+ def finished?
623
+ @handle.nil?
624
+ end
625
+
626
+ def fetchable?
627
+ @fetchable
628
+ end
629
+
630
+ def bind_param(param, value, attribs=nil)
631
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
632
+ raise InterfaceError, "Statement wasn't prepared before." unless @prepared
633
+ @handle.bind_param(param, conv_param(value)[0], attribs)
634
+ end
635
+
636
+ def execute(*bindvars)
637
+ cancel # cancel before
638
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
639
+ raise InterfaceError, "Statement wasn't prepared before." unless @prepared
640
+ @handle.bind_params(*conv_param(*bindvars))
641
+ @handle.execute
642
+ @fetchable = true
643
+
644
+ # TODO:?
645
+ #if @row.nil?
646
+ @row = DBI::Row.new(column_names,nil)
647
+ #end
648
+ return nil
649
+ end
650
+
651
+ def finish
652
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
653
+ @handle.finish
654
+ @handle = nil
655
+ end
656
+
657
+ def cancel
658
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
659
+ @handle.cancel if @fetchable
660
+ @fetchable = false
661
+ end
662
+
663
+ def column_names
664
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
665
+ return @cols unless @cols.nil?
666
+ @cols = @handle.column_info.collect {|col| col['name'] }
667
+ end
668
+
669
+ def column_info
670
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
671
+ @handle.column_info.collect {|col| ColumnInfo.new(col) }
672
+ end
673
+
674
+ def rows
675
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
676
+ @handle.rows
677
+ end
678
+
679
+
680
+ def fetch(&p)
681
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
682
+
683
+ if block_given?
684
+ while (res = @handle.fetch) != nil
685
+ @row.set_values(res)
686
+ yield @row
687
+ end
688
+ @handle.cancel
689
+ @fetchable = false
690
+ return nil
691
+ else
692
+ res = @handle.fetch
693
+ if res.nil?
694
+ @handle.cancel
695
+ @fetchable = false
696
+ else
697
+ @row.set_values(res)
698
+ res = @row
699
+ end
700
+ return res
701
+ end
702
+ end
703
+
704
+ def each(&p)
705
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
706
+ raise InterfaceError, "Statement must first be executed" unless @fetchable
707
+ raise InterfaceError, "No block given" unless block_given?
708
+
709
+ fetch(&p)
710
+ end
711
+
712
+ def fetch_array
713
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
714
+ raise InterfaceError, "Statement must first be executed" unless @fetchable
715
+
716
+ if block_given?
717
+ while (res = @handle.fetch) != nil
718
+ yield res
719
+ end
720
+ @handle.cancel
721
+ @fetchable = false
722
+ return nil
723
+ else
724
+ res = @handle.fetch
725
+ if res.nil?
726
+ @handle.cancel
727
+ @fetchable = false
728
+ end
729
+ return res
730
+ end
731
+ end
732
+
733
+ def fetch_hash
734
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
735
+ raise InterfaceError, "Statement must first be executed" unless @fetchable
736
+
737
+ cols = column_names
738
+
739
+ if block_given?
740
+ while (row = @handle.fetch) != nil
741
+ hash = {}
742
+ row.each_with_index {|v,i| hash[cols[i]] = v}
743
+ yield hash
744
+ end
745
+ @handle.cancel
746
+ @fetchable = false
747
+ return nil
748
+ else
749
+ row = @handle.fetch
750
+ if row.nil?
751
+ @handle.cancel
752
+ @fetchable = false
753
+ return nil
754
+ else
755
+ hash = {}
756
+ row.each_with_index {|v,i| hash[cols[i]] = v}
757
+ return hash
758
+ end
759
+ end
760
+ end
761
+
762
+ def fetch_many(cnt)
763
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
764
+ raise InterfaceError, "Statement must first be executed" unless @fetchable
765
+
766
+ cols = column_names
767
+ rows = @handle.fetch_many(cnt)
768
+ if rows.nil?
769
+ @handle.cancel
770
+ @fetchable = false
771
+ return []
772
+ else
773
+ return rows.collect{|r| Row.new(cols, r)}
774
+ end
775
+ end
776
+
777
+ def fetch_all
778
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
779
+ raise InterfaceError, "Statement must first be executed" unless @fetchable
780
+
781
+ cols = column_names
782
+ rows = @handle.fetch_all
783
+ if rows.nil?
784
+ @handle.cancel
785
+ @fetchable = false
786
+ return []
787
+ else
788
+ return rows.collect{|r| Row.new(cols, r)}
789
+ end
790
+ end
791
+
792
+ def fetch_scroll(direction, offset=1)
793
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
794
+ raise InterfaceError, "Statement must first be executed" unless @fetchable
795
+
796
+ row = @handle.fetch_scroll(direction, offset)
797
+ if row.nil?
798
+ #@handle.cancel
799
+ #@fetchable = false
800
+ return nil
801
+ else
802
+ @row.set_values(row)
803
+ return @row
804
+ end
805
+ end
806
+
807
+ def [] (attr)
808
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
809
+ @handle[attr]
810
+ end
811
+
812
+ def []= (attr, val)
813
+ raise InterfaceError, "Statement was already closed!" if @handle.nil?
814
+ @handle[attr] = val
815
+ end
816
+
817
+ end # class StatementHandle
818
+
819
+
820
+
821
+
822
+
823
+ #----------------------------------------------------
824
+ # Fallback classes
825
+ #----------------------------------------------------
826
+
827
+
828
+ ##
829
+ # Fallback classes for default behavior of DBD driver
830
+ # must be inherited by the DBD driver classes
831
+ #
832
+
833
+ class Base
834
+ end
835
+
836
+
837
+ class BaseDriver < Base
838
+
839
+ def initialize(dbd_version)
840
+ major, minor = dbd_version.split(".")
841
+ unless major.to_i == DBD::API_VERSION.split(".")[0].to_i
842
+ raise InterfaceError, "Wrong DBD API version used"
843
+ end
844
+ end
845
+
846
+ def connect(dbname, user, auth, attr)
847
+ raise NotImplementedError
848
+ end
849
+
850
+ def default_user
851
+ ['', '']
852
+ end
853
+
854
+ def default_attributes
855
+ {}
856
+ end
857
+
858
+ def data_sources
859
+ []
860
+ end
861
+
862
+ def disconnect_all
863
+ raise NotImplementedError
864
+ end
865
+
866
+ end # class BaseDriver
867
+
868
+ class BaseDatabase < Base
869
+
870
+ def initialize(handle, attr)
871
+ @handle = handle
872
+ @attr = {}
873
+ attr.each {|k,v| self[k] = v}
874
+ end
875
+
876
+ def disconnect
877
+ raise NotImplementedError
878
+ end
879
+
880
+ def ping
881
+ raise NotImplementedError
882
+ end
883
+
884
+ def prepare(statement)
885
+ raise NotImplementedError
886
+ end
887
+
888
+ #============================================
889
+ # OPTIONAL
890
+ #============================================
891
+
892
+ def commit
893
+ raise NotSupportedError
894
+ end
895
+
896
+ def rollback
897
+ raise NotSupportedError
898
+ end
899
+
900
+ def tables
901
+ []
902
+ end
903
+
904
+ def columns(table)
905
+ []
906
+ end
907
+
908
+
909
+ def execute(statement, *bindvars)
910
+ stmt = prepare(statement)
911
+ stmt.bind_params(*bindvars)
912
+ stmt.execute
913
+ stmt
914
+ end
915
+
916
+ def do(statement, *bindvars)
917
+ stmt = execute(statement, *bindvars)
918
+ res = stmt.rows
919
+ stmt.finish
920
+ return res
921
+ end
922
+
923
+ # includes quote
924
+ include DBI::SQL::BasicQuote
925
+
926
+ def [](attr)
927
+ @attr[attr]
928
+ end
929
+
930
+ def []=(attr, value)
931
+ raise NotSupportedError
932
+ end
933
+
934
+ end # class BaseDatabase
935
+
936
+
937
+ class BaseStatement < Base
938
+
939
+ def initialize(attr=nil)
940
+ @attr = attr || {}
941
+ end
942
+
943
+
944
+
945
+ def bind_param(param, value, attribs)
946
+ raise NotImplementedError
947
+ end
948
+
949
+ def execute
950
+ raise NotImplementedError
951
+ end
952
+
953
+ def finish
954
+ raise NotImplementedError
955
+ end
956
+
957
+ def fetch
958
+ raise NotImplementedError
959
+ end
960
+
961
+ ##
962
+ # returns result-set column information as array
963
+ # of hashs, where each hash represents one column
964
+ def column_info
965
+ raise NotImplementedError
966
+ end
967
+
968
+ #============================================
969
+ # OPTIONAL
970
+ #============================================
971
+
972
+ def bind_params(*bindvars)
973
+ bindvars.each_with_index {|val,i| bind_param(i+1, val, nil) }
974
+ self
975
+ end
976
+
977
+ def cancel
978
+ end
979
+
980
+ def fetch_scroll(direction, offset)
981
+ case direction
982
+ when SQL_FETCH_NEXT
983
+ return fetch
984
+ when SQL_FETCH_LAST
985
+ last_row = nil
986
+ while (row=fetch) != nil
987
+ last_row = row
988
+ end
989
+ return last_row
990
+ when SQL_FETCH_RELATIVE
991
+ raise NotSupportedError if offset <= 0
992
+ row = nil
993
+ offset.times { row = fetch; break if row.nil? }
994
+ return row
995
+ else
996
+ raise NotSupportedError
997
+ end
998
+ end
999
+
1000
+ def fetch_many(cnt)
1001
+ rows = []
1002
+ cnt.times do
1003
+ row = fetch
1004
+ break if row.nil?
1005
+ rows << row.dup
1006
+ end
1007
+
1008
+ if rows.empty?
1009
+ nil
1010
+ else
1011
+ rows
1012
+ end
1013
+ end
1014
+
1015
+ def fetch_all
1016
+ rows = []
1017
+ loop do
1018
+ row = fetch
1019
+ break if row.nil?
1020
+ rows << row.dup
1021
+ end
1022
+
1023
+ if rows.empty?
1024
+ nil
1025
+ else
1026
+ rows
1027
+ end
1028
+ end
1029
+
1030
+ def [](attr)
1031
+ @attr[attr]
1032
+ end
1033
+
1034
+ def []=(attr, value)
1035
+ raise NotSupportedError
1036
+ end
1037
+
1038
+ end # class BaseStatement
1039
+
1040
+
1041
+ end # module DBI
1042
+
1043
+