ironruby-dbi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,119 @@
1
+ module DBI
2
+ module DBD
3
+ module MSSQL
4
+
5
+ class Statement < DBI::BaseStatement
6
+
7
+ def initialize(statement, db)
8
+ @connection = db.current_connection;
9
+ @command = @connection.create_command
10
+ @statement = statement.to_s
11
+ @command.command_text = @statement.to_clr_string
12
+ @command.transaction = db.current_transaction if db.has_transaction?
13
+ @current_index = 0
14
+ @db = db
15
+ end
16
+
17
+ def bind_param(name, value, attribs={})
18
+ unless name.to_s.empty?
19
+ parameter = @command.create_parameter
20
+ parm_name = name.to_s.to_clr_string
21
+ parameter.ParameterName = parm_name
22
+ val = value.is_a?(String) ? value.to_clr_string : value #(value || System::DBNull.value)
23
+ parameter.Value = val
24
+ if @command.parameters.contains(parm_name)
25
+ @command.parameters[parm_name] = parameter
26
+ else
27
+ @command.parameters.add parameter
28
+ end
29
+ end
30
+ end
31
+
32
+ def execute
33
+ @current_index = 0
34
+ @rows = []
35
+ @schema = nil
36
+ @reader.close if @reader and not @reader.is_closed
37
+ @reader = @command.execute_reader
38
+ schema
39
+
40
+ unless SQL.query?(@statement.to_s)
41
+ finish
42
+ end
43
+ @reader.records_affected
44
+ rescue RuntimeError, System::Data::SqlClient::SqlException => err
45
+ raise DBI::DatabaseError.new(err.message)
46
+ end
47
+
48
+ def fetch
49
+ res = nil
50
+ if @reader.read
51
+ res = read_row(@reader)
52
+ end
53
+ res
54
+ rescue RuntimeError, System::Data::SqlClient::SqlException => err
55
+ raise DBI::DatabaseError.new(err.message)
56
+ end
57
+
58
+ def finish
59
+ @reader.close if @reader and not @reader.is_closed
60
+
61
+ rescue RuntimeError, System::Data::SqlClient::SqlException => err
62
+ raise DBI::DatabaseError.new(err.message)
63
+ end
64
+
65
+ def cancel
66
+ @reader.close if @reader and not @reader.is_closed
67
+ @command.cancel
68
+ end
69
+
70
+ def schema
71
+
72
+ @schema ||= @reader.get_schema_table || System::Data::DataTable.new
73
+ end
74
+
75
+ def column_info
76
+ infos = schema.rows.collect do |row|
77
+ name = row["ColumnName"]
78
+ def_val_col = row.table.columns[name]
79
+ def_val = def_val_col.nil? ? nil : def_val_col.default_value
80
+ dtn = row["DataTypeName"].to_s.upcase
81
+ {
82
+
83
+ 'name' => name.to_s,
84
+ 'dbi_type' => MSSQL_TYPEMAP[dtn],
85
+ 'mssql_type_name' => dtn,
86
+ 'sql_type' =>MSSQL_TO_XOPEN[dtn][0],
87
+ 'type_name' => DBI::SQL_TYPE_NAMES[MSSQL_TO_XOPEN[dtn][0]],
88
+ 'precision' => %w(varchar nvarchar char nchar text ntext).include?(dtn.downcase) ? row["ColumnSize"] : row["NumericPrecision"],
89
+ 'default' => def_val,
90
+ 'scale' => %w(varchar nvarchar char nchar text ntext).include?(dtn.downcase) ? nil : row["NumericScale"] ,
91
+ 'nullable' => row["AllowDBNull"],
92
+ 'primary' => schema.primary_key.select { |pk| pk.column_name == name }.size > 0,
93
+ 'unique' => row["IsUnique"]
94
+ }
95
+ end
96
+ infos
97
+ rescue RuntimeError, System::Data::SqlClient::SqlException => err
98
+ raise DBI::DatabaseError.new(err.message)
99
+ end
100
+
101
+ def rows
102
+ return 0 if @reader.nil?
103
+ res = @reader.records_affected
104
+ res == -1 ? 0 : res
105
+ end
106
+
107
+ private
108
+
109
+ def read_row(record)
110
+ (0...record.visible_field_count).collect do |i|
111
+ res = record.get_value(i)
112
+ res.is_a?(System::DBNull) ? nil : res
113
+ end
114
+ end
115
+
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,66 @@
1
+ module DBI::DBD::MSSQL::Type
2
+
3
+ #
4
+ # Represents a Decimal with real precision (BigDecimal). Falls back to
5
+ # Float.
6
+ #
7
+ class Decimal < DBI::Type::Float
8
+ def self.parse(obj)
9
+ BigDecimal.new(obj.to_s) rescue super
10
+ end
11
+ end
12
+
13
+ #
14
+ # Represents a SQL NULL.
15
+ #
16
+ class Null
17
+ def self.parse(obj)
18
+ return nil if obj.is_a?(System::DBNull) or obj.to_s.match(/^null$/i)
19
+ return obj
20
+ end
21
+ end
22
+
23
+ #
24
+ # Custom handling for TIMESTAMP and DATETIME types in MySQL. See DBI::Type
25
+ # for more information.
26
+ #
27
+ class Timestamp < DBI::Type::Null
28
+ def self.parse(obj)
29
+ obj = super
30
+ return obj unless obj
31
+
32
+ case obj.class
33
+ when ::DateTime
34
+ return obj
35
+ when ::String
36
+ return ::DateTime.strptime(obj, "%Y-%m-%d %H:%M:%S")
37
+ else
38
+ return ::DateTime.parse(obj.to_s) if obj.respond_to? :to_s
39
+ return ::DateTime.parse(obj.to_str) if obj.respond_to? :to_str
40
+ return obj
41
+ end
42
+ end
43
+ end
44
+
45
+ #
46
+ # Custom handling for DATE types in MySQL. See DBI::Type for more
47
+ # information.
48
+ #
49
+ class Date < DBI::Type::Null
50
+ def self.parse(obj)
51
+ obj = super
52
+ return obj unless obj
53
+
54
+ case obj.class
55
+ when ::Date
56
+ return obj
57
+ when ::String
58
+ return ::Date.strptime(obj, "%Y-%m-%d")
59
+ else
60
+ return ::Date.parse(obj.to_s) if obj.respond_to? :to_s
61
+ return ::Date.parse(obj.to_str) if obj.respond_to? :to_str
62
+ return obj
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,331 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ #
3
+ # DBI - Database Interface for Ruby. Please see the files README, DBI_SPEC,
4
+ # DBD_SPEC for more information.
5
+ #
6
+ module DBI; end
7
+ #--
8
+ # Ruby/DBI
9
+ #
10
+ # Copyright (c) 2001, 2002, 2003 Michael Neumann <mneumann@ntecs.de>
11
+ # Copyright (c) 2008 Erik Hollensbe <erik@hollensbe.org>
12
+ #
13
+ # All rights reserved.
14
+ #
15
+ # Redistribution and use in source and binary forms, with or without
16
+ # modification, are permitted provided that the following conditions
17
+ # are met:
18
+ # 1. Redistributions of source code must retain the above copyright
19
+ # notice, this list of conditions and the following disclaimer.
20
+ # 2. Redistributions in binary form must reproduce the above copyright
21
+ # notice, this list of conditions and the following disclaimer in the
22
+ # documentation and/or other materials provided with the distribution.
23
+ # 3. The name of the author may not be used to endorse or promote products
24
+ # derived from this software without specific prior written permission.
25
+ #
26
+ # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
27
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
28
+ # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
29
+ # THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
31
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
32
+ # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
33
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
34
+ # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35
+ # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36
+ #
37
+
38
+ begin
39
+ require "rubygems"
40
+ gem "deprecated"
41
+ rescue LoadError
42
+ end
43
+
44
+ #
45
+ # NOTE see the end of the file for requires that live in the DBI namespace.
46
+ #
47
+
48
+ require "deprecated"
49
+ require "dbi/row"
50
+ require "dbi/utils"
51
+ require "dbi/sql"
52
+ require "dbi/columninfo"
53
+ require 'dbi/types'
54
+ require 'dbi/typeutil'
55
+ require 'dbi/sql_type_constants'
56
+ require 'dbi/exceptions'
57
+ require 'dbi/binary'
58
+ require 'dbi/handles'
59
+ require 'dbi/base_classes'
60
+ require "date"
61
+ require "thread"
62
+ require 'monitor'
63
+
64
+ class Class
65
+ # Given a Class, returns if the object's (another Class) ancestors contain
66
+ # that class.
67
+ def inherits_from?(klass)
68
+ self.ancestors.include?(klass)
69
+ end
70
+ end
71
+
72
+ Deprecate.set_action(
73
+ proc do |call|
74
+ klass, meth = call.split(/[#.]/)
75
+ klass = klass.split(/::/).inject(Module) { |a,x| a.const_get(x) }
76
+
77
+ case klass
78
+ when DBI::Date, DBI::Time, DBI::Timestamp
79
+ warn "DBI::Date/Time/Timestamp are deprecated and will eventually be removed."
80
+ end
81
+
82
+ if klass.inherits_from?(DBI::ColumnInfo)
83
+ warn "ColumnInfo methods that do not match a component are deprecated and will eventually be removed"
84
+ end
85
+
86
+ warn "You may change the result of calling deprecated code via Deprecate.set_action; Trace follows:"
87
+ warn caller[2..-1].join("\n")
88
+ end
89
+ )
90
+
91
+ #++
92
+ module DBI
93
+ VERSION = "0.4.1"
94
+
95
+ module DBD # :nodoc:
96
+ API_VERSION = "0.4"
97
+ end
98
+
99
+ # Module functions (of DBI)
100
+ DEFAULT_TRACE_MODE = 2
101
+ DEFAULT_TRACE_OUTPUT = STDERR
102
+
103
+ # TODO: Is using class variables within a module such a wise idea? - Dan B.
104
+ @@driver_map = Hash.new
105
+ @@driver_monitor = ::Monitor.new()
106
+ @@trace_mode = DEFAULT_TRACE_MODE
107
+ @@trace_output = DEFAULT_TRACE_OUTPUT
108
+ @@caseless_driver_name_map = nil
109
+ @@convert_types = true
110
+
111
+ # Return the current status of type conversion at this level. This status
112
+ # will be propogated to any new DatabaseHandles created.
113
+ def self.convert_types
114
+ @@convert_types
115
+ end
116
+
117
+ # Set the current status of type conversion at this level. This status
118
+ # will be propogated to any new DatabaseHandles created.
119
+ def self.convert_types=(bool)
120
+ @@convert_types = bool
121
+ end
122
+
123
+ class << self
124
+
125
+ # Establish a database connection.
126
+ #
127
+ # Format goes as such: "dbi:Driver:database_conn_args"
128
+ #
129
+ # * "dbi" is the literal string "dbi". Case is unimportant.
130
+ # * "Driver" is the case-dependent name of your database driver class.
131
+ # The file "dbd/#{Driver}" will be required. If you are using rubygems to
132
+ # control your DBDs and DBI, you must make the gem's file path available
133
+ # via the "gem" command before this will work.
134
+ # * database_conn_args can be:
135
+ # * The database name.
136
+ # * A more complex key/value association (to indicate host, etc). This
137
+ # is driver dependent; you should consult your DBD documentation.
138
+ def connect(driver_url, user=nil, auth=nil, params=nil, &p)
139
+ dr, db_args = _get_full_driver(driver_url)
140
+ dh = dr[0] # driver-handle
141
+ dh.convert_types = @@convert_types
142
+ dh.connect(db_args, user, auth, params, &p)
143
+ end
144
+
145
+ # Load a DBD and returns the DriverHandle object
146
+ def get_driver(driver_url) #:nodoc:
147
+ _get_full_driver(driver_url)[0][0] # return DriverHandle
148
+ end
149
+
150
+ # Extracts the db_args from driver_url and returns the correspondeing
151
+ # entry of the @@driver_map.
152
+ def _get_full_driver(driver_url) #:nodoc:
153
+ db_driver, db_args = parse_url(driver_url)
154
+ db_driver = load_driver(db_driver)
155
+ dr = @@driver_map[db_driver]
156
+ [dr, db_args]
157
+ end
158
+
159
+ #
160
+ # Enable tracing mode. Requires that 'dbi/trace' be required before it does anything.
161
+ #
162
+ # As of 0.4.0, this mode does not do anything either way, so this currently just
163
+ # throws an InterfaceError. This issue is expected to be resolved in the next release.
164
+ #
165
+ def trace(mode=nil, output=nil)
166
+ # FIXME trace
167
+ raise InterfaceError, "the trace module has been removed until it actually works."
168
+ @@trace_mode = mode || @@trace_mode || DBI::DEFAULT_TRACE_MODE
169
+ @@trace_output = output || @@trace_output || DBI::DEFAULT_TRACE_OUTPUT
170
+ end
171
+
172
+ #
173
+ # Return a list (of String) of the available drivers.
174
+ #
175
+ # NOTE:: This is non-functional for gem installations, due to the
176
+ # nature of how it currently works. A better solution for
177
+ # this will be provided in DBI 0.6.0.
178
+ def collect_drivers
179
+ drivers = { }
180
+ # FIXME rewrite this to leverage require and be more intelligent
181
+ path = File.join(File.dirname(__FILE__), "dbd", "*.rb")
182
+ Dir[path].each do |f|
183
+ if File.file?(f)
184
+ driver = File.basename(f, ".rb")
185
+ drivers[driver] = f
186
+ end
187
+ end
188
+
189
+ return drivers
190
+ end
191
+
192
+ # Returns a list (of String) of the currently available drivers on your system in
193
+ # 'dbi:driver:' format.
194
+ #
195
+ # This currently does not work for rubygems installations, please see
196
+ # DBI.collect_drivers for reasons.
197
+ def available_drivers
198
+ drivers = []
199
+ collect_drivers.each do |key, value|
200
+ drivers.push("dbi:#{key}:")
201
+ end
202
+ return drivers
203
+ end
204
+
205
+ # Attempt to collect the available data sources to the driver,
206
+ # specified in DBI.connect format.
207
+ #
208
+ # The result is heavily dependent on the driver's ability to enumerate
209
+ # these sources, and results will vary.
210
+ def data_sources(driver)
211
+ db_driver, = parse_url(driver)
212
+ db_driver = load_driver(db_driver)
213
+ dh = @@driver_map[db_driver][0]
214
+ dh.data_sources
215
+ end
216
+
217
+ #
218
+ # Attempt to disconnect all database handles. If a driver is provided,
219
+ # disconnections will happen under that scope. Otherwise, all loaded
220
+ # drivers (and their handles) will be attempted.
221
+ #
222
+ def disconnect_all( driver = nil )
223
+ if driver.nil?
224
+ @@driver_map.each {|k,v| v[0].disconnect_all}
225
+ else
226
+ db_driver, = parse_url(driver)
227
+ @@driver_map[db_driver][0].disconnect_all
228
+ end
229
+ end
230
+
231
+ private
232
+
233
+ # Given a driver name, locate and load the associated DBD package,
234
+ # generate a DriverHandle and return it.
235
+ def load_driver(driver_name)
236
+ @@driver_monitor.synchronize do
237
+ unless @@driver_map[driver_name]
238
+ dc = driver_name.downcase
239
+
240
+ # caseless look for drivers already loaded
241
+ found = @@driver_map.keys.find {|key| key.downcase == dc}
242
+ return found if found
243
+
244
+ begin
245
+ require "dbd/#{driver_name}"
246
+ rescue LoadError => e1
247
+ # see if you can find it in the path
248
+ unless @@caseless_driver_name_map
249
+ @@caseless_driver_name_map = { }
250
+ collect_drivers.each do |key, value|
251
+ @@caseless_driver_name_map[key.downcase] = value
252
+ end
253
+ end
254
+
255
+ begin
256
+ require @@caseless_driver_name_map[dc] if @@caseless_driver_name_map[dc]
257
+ rescue LoadError => e2
258
+ raise e.class, "Could not find driver #{driver_name} or #{driver_name.downcase} (error: #{e1.message})"
259
+ end
260
+ end
261
+
262
+ # On a filesystem that is not case-sensitive (e.g., HFS+ on Mac OS X),
263
+ # the initial require attempt that loads the driver may succeed even
264
+ # though the lettercase of driver_name doesn't match the actual
265
+ # filename. If that happens, const_get will fail and it become
266
+ # necessary to look though the list of constants and look for a
267
+ # caseless match. The result of this match provides the constant
268
+ # with the proper lettercase -- which can be used to generate the
269
+ # driver handle.
270
+
271
+ dr = nil
272
+ dr_error = nil
273
+ begin
274
+ dr = DBI::DBD.const_get(driver_name.intern)
275
+ rescue NameError => dr_error
276
+ # caseless look for constants to find actual constant
277
+ dc = driver_name.downcase
278
+ found = DBI::DBD.constants.find { |e| e.downcase == dc }
279
+ dr = DBI::DBD.const_get(found.intern) unless found.nil?
280
+ end
281
+
282
+ # If dr is nil at this point, it means the underlying driver
283
+ # failed to load. This usually means it's not installed, but
284
+ # can fail for other reasons.
285
+ if dr.nil?
286
+ err = "Unable to load driver '#{driver_name}'"
287
+
288
+ if dr_error
289
+ err += " (underlying error: #{dr_error.message})"
290
+ else
291
+ err += " (BUG: could not determine underlying error)"
292
+ end
293
+
294
+ raise DBI::InterfaceError, err
295
+ end
296
+
297
+ dbd_dr = dr::Driver.new
298
+ drh = DBI::DriverHandle.new(dbd_dr, @@convert_types)
299
+ drh.driver_name = dr.driver_name
300
+ # FIXME trace
301
+ # drh.trace(@@trace_mode, @@trace_output)
302
+ @@driver_map[driver_name] = [drh, dbd_dr]
303
+ return driver_name
304
+ else
305
+ return driver_name
306
+ end
307
+ end
308
+ rescue LoadError, NameError
309
+ if $SAFE >= 1
310
+ raise InterfaceError, "Could not load driver (#{$!.message}). Note that in SAFE mode >= 1, driver URLs have to be case sensitive!"
311
+ else
312
+ raise InterfaceError, "Could not load driver (#{$!.message})"
313
+ end
314
+ end
315
+
316
+ # Splits a DBI URL into two components - the database driver name
317
+ # and the datasource (along with any options, if any) and returns
318
+ # a two element array, e.g. 'dbi:foo:bar' would return ['foo','bar'].
319
+ #
320
+ # A regular expression is used instead of a simple split to validate
321
+ # the proper format for the URL. If it isn't correct, an Interface
322
+ # error is raised.
323
+ def parse_url(driver_url)
324
+ if driver_url =~ /^(DBI|dbi):([^:]+)(:(.*))$/
325
+ [$2, $4]
326
+ else
327
+ raise InterfaceError, "Invalid Data Source Name"
328
+ end
329
+ end
330
+ end # self
331
+ end # module DBI