ironruby-dbi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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