akitaonrails-activerecord-sqlserver-adapter 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
+