odbc-rails 1.2
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/AUTHORS +16 -0
- data/COPYING +21 -0
- data/ChangeLog +89 -0
- data/LICENSE +5 -0
- data/NEWS +12 -0
- data/README +282 -0
- data/lib/active_record/connection_adapters/odbc_adapter.rb +1792 -0
- data/lib/active_record/vendor/odbcext_db2.rb +87 -0
- data/lib/active_record/vendor/odbcext_informix.rb +132 -0
- data/lib/active_record/vendor/odbcext_informix_col.rb +45 -0
- data/lib/active_record/vendor/odbcext_ingres.rb +156 -0
- data/lib/active_record/vendor/odbcext_microsoftsqlserver.rb +185 -0
- data/lib/active_record/vendor/odbcext_microsoftsqlserver_col.rb +40 -0
- data/lib/active_record/vendor/odbcext_mysql.rb +136 -0
- data/lib/active_record/vendor/odbcext_oracle.rb +220 -0
- data/lib/active_record/vendor/odbcext_postgresql.rb +179 -0
- data/lib/active_record/vendor/odbcext_progress.rb +139 -0
- data/lib/active_record/vendor/odbcext_progress89.rb +259 -0
- data/lib/active_record/vendor/odbcext_sybase.rb +212 -0
- data/lib/active_record/vendor/odbcext_sybase_col.rb +49 -0
- data/lib/active_record/vendor/odbcext_virtuoso.rb +146 -0
- data/lib/odbc_adapter.rb +28 -0
- data/support/lib/active_record/connection_adapters/abstract/schema_definitions.rb +259 -0
- data/support/odbc_rails.diff +707 -0
- data/support/pack_odbc.rb +119 -0
- data/support/rake/rails_plugin_package_task.rb +212 -0
- data/support/test/base_test.rb +1349 -0
- data/support/test/migration_test.rb +566 -0
- data/test/connections/native_odbc/connection.rb +95 -0
- data/test/fixtures/db_definitions/db2.drop.sql +30 -0
- data/test/fixtures/db_definitions/db2.sql +217 -0
- data/test/fixtures/db_definitions/db22.drop.sql +2 -0
- data/test/fixtures/db_definitions/db22.sql +5 -0
- data/test/fixtures/db_definitions/informix.drop.sql +30 -0
- data/test/fixtures/db_definitions/informix.sql +205 -0
- data/test/fixtures/db_definitions/informix2.drop.sql +2 -0
- data/test/fixtures/db_definitions/informix2.sql +5 -0
- data/test/fixtures/db_definitions/ingres.drop.sql +62 -0
- data/test/fixtures/db_definitions/ingres.sql +232 -0
- data/test/fixtures/db_definitions/ingres2.drop.sql +2 -0
- data/test/fixtures/db_definitions/ingres2.sql +5 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +30 -0
- data/test/fixtures/db_definitions/mysql.sql +219 -0
- data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
- data/test/fixtures/db_definitions/mysql2.sql +5 -0
- data/test/fixtures/db_definitions/oracle_odbc.drop.sql +64 -0
- data/test/fixtures/db_definitions/oracle_odbc.sql +257 -0
- data/test/fixtures/db_definitions/oracle_odbc2.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle_odbc2.sql +6 -0
- data/test/fixtures/db_definitions/progress.drop.sql +61 -0
- data/test/fixtures/db_definitions/progress.sql +234 -0
- data/test/fixtures/db_definitions/progress2.drop.sql +2 -0
- data/test/fixtures/db_definitions/progress2.sql +6 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +30 -0
- data/test/fixtures/db_definitions/sqlserver.sql +203 -0
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
- data/test/fixtures/db_definitions/sybase.sql +204 -0
- data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
- data/test/fixtures/db_definitions/sybase2.sql +5 -0
- data/test/fixtures/db_definitions/virtuoso.drop.sql +30 -0
- data/test/fixtures/db_definitions/virtuoso.sql +200 -0
- data/test/fixtures/db_definitions/virtuoso2.drop.sql +2 -0
- data/test/fixtures/db_definitions/virtuoso2.sql +5 -0
- metadata +149 -0
data/AUTHORS
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#
|
2
|
+
# List of Authors/Contributors
|
3
|
+
#
|
4
|
+
|
5
|
+
Original Author:
|
6
|
+
----------------
|
7
|
+
Carl Blakeley <cblakeley@openlinksw.co.uk> (cb)
|
8
|
+
|
9
|
+
|
10
|
+
Current Maintainer:
|
11
|
+
-------------------
|
12
|
+
Patrick van Kleef <iodbc@@openlinksw.com> (pvk)
|
13
|
+
|
14
|
+
|
15
|
+
Contributors:
|
16
|
+
-------------
|
data/COPYING
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
OpenLink ODBC Adapter for Ruby on Rails
|
2
|
+
Copyright (C) 2006 OpenLink Software
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject
|
10
|
+
to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
19
|
+
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
20
|
+
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/ChangeLog
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
2006-12-11 12:01 source
|
2
|
+
|
3
|
+
* Updated for final release 1.2
|
4
|
+
|
5
|
+
2006-12-11 12:00 source
|
6
|
+
|
7
|
+
* Added additional documentation for installing gem and plugin version
|
8
|
+
|
9
|
+
2006-12-06 16:16 source
|
10
|
+
|
11
|
+
* Added for gem support
|
12
|
+
|
13
|
+
2006-12-06 16:14 source
|
14
|
+
|
15
|
+
* Added dependency to activerecord
|
16
|
+
|
17
|
+
2006-12-06 15:01 source
|
18
|
+
|
19
|
+
* Fixed version number
|
20
|
+
|
21
|
+
2006-12-06 15:01 source
|
22
|
+
|
23
|
+
* Changed doc into rdoc
|
24
|
+
|
25
|
+
2006-12-06 14:55 source
|
26
|
+
|
27
|
+
* Added support for building plugin
|
28
|
+
* Added support for building gem
|
29
|
+
|
30
|
+
2006-12-06 14:42 source
|
31
|
+
|
32
|
+
* Changed directory structure for building both plugin and gem
|
33
|
+
|
34
|
+
2006-11-22 15:53 source
|
35
|
+
|
36
|
+
* Added support for Progress versions 8 (SQL89 engine), 9 & 10 (SQL92 engine)
|
37
|
+
|
38
|
+
2006-10-18 14:32 source
|
39
|
+
|
40
|
+
* Updated package version to 1.1
|
41
|
+
|
42
|
+
2006-10-10 14:13 source
|
43
|
+
|
44
|
+
* Less files to ignore
|
45
|
+
|
46
|
+
2006-10-10 10:57 source
|
47
|
+
|
48
|
+
* Reindented code (cmb)
|
49
|
+
|
50
|
+
2006-10-09 09:03 source
|
51
|
+
|
52
|
+
* Latest diff for rails code (cmb)
|
53
|
+
|
54
|
+
2006-10-09 07:25 source
|
55
|
+
|
56
|
+
* Added support for DB2 (cmb)
|
57
|
+
|
58
|
+
2006-10-09 07:23 source
|
59
|
+
|
60
|
+
* Renamed sybase_odbc.sql to sybase.sql (cmb)
|
61
|
+
|
62
|
+
2006-09-28 10:31 source
|
63
|
+
|
64
|
+
* Added support for MySQL, Sybase and SQL Server (cb)
|
65
|
+
|
66
|
+
2006-08-23 14:07 source
|
67
|
+
|
68
|
+
* Final documentation patches
|
69
|
+
|
70
|
+
2006-08-22 20:26 source
|
71
|
+
|
72
|
+
Initial checkin
|
73
|
+
|
74
|
+
2006-08-21 23:37 source
|
75
|
+
|
76
|
+
Initial checkin
|
77
|
+
|
78
|
+
2006-08-21 14:00 source
|
79
|
+
|
80
|
+
Initial checkin
|
81
|
+
|
82
|
+
2006-08-21 12:57 source
|
83
|
+
|
84
|
+
Initial checkin
|
85
|
+
|
86
|
+
2006-08-17 16:54 source
|
87
|
+
|
88
|
+
Initial checkin
|
89
|
+
|
data/LICENSE
ADDED
data/NEWS
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
December 11, 2006, V1.2:
|
2
|
+
* Added option to install adapter as a gem
|
3
|
+
* Added option to install adapter as a plugin
|
4
|
+
* Added support for Progress 8 (SQL 89)
|
5
|
+
* Added support for Progress 9 and 10 (SQL 92)
|
6
|
+
|
7
|
+
October 18, 2006, V1.1:
|
8
|
+
* Added support for DB2, MySQL, Sybase and SQL Server
|
9
|
+
* Fixed initial unicode support
|
10
|
+
|
11
|
+
August 23 2006, V1.0:
|
12
|
+
* Initial archive
|
data/README
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
= OpenLink ODBC Data Adapter for Ruby on Rails
|
2
|
+
|
3
|
+
(C) 2006 OpenLink Software
|
4
|
+
|
5
|
+
11-December-2006
|
6
|
+
|
7
|
+
|
8
|
+
== Status
|
9
|
+
|
10
|
+
The adapter accompanying this note is a Generic ODBC Adapter for Ruby on Rails
|
11
|
+
being developed by <B>OpenLink Software</B>[http://www.openlinksw.com].
|
12
|
+
|
13
|
+
The aim of this development is to provide a single ODBC-based adapter
|
14
|
+
capable of supporting the most popular DBMSes, in contrast to the
|
15
|
+
current approach in the Rails community of each database requiring
|
16
|
+
its own adapter.
|
17
|
+
|
18
|
+
It currently supports Ingres r3, Informix 9.3 or later, Oracle 10g,
|
19
|
+
MySQL 5 and OpenLink's Virtuoso
|
20
|
+
(Open Source Edition[http://virtuoso.openlinksw.com]),
|
21
|
+
SQL Server 2000, Sybase ASE 15, DB2 v9 and Progress v8/9/10
|
22
|
+
|
23
|
+
Testing to date has been limited to the ROR 'Expenses' sample
|
24
|
+
application described at http://developer.apple.com/tools/rubyonrails.html
|
25
|
+
and the ActiveRecord test modules base_test.rb and migration_test.rb.
|
26
|
+
|
27
|
+
Testing has been performed on Linux, Windows and Mac OS X using
|
28
|
+
OpenLink's own ODBC drivers and the native Virtuoso ODBC client.
|
29
|
+
|
30
|
+
|
31
|
+
== Pre-Requisites
|
32
|
+
|
33
|
+
The OpenLink ODBC Adapter for Ruby on Rails needs the
|
34
|
+
ActiveRecord[http://wiki.rubyonrails.com/rails/pages/ActiveRecord]
|
35
|
+
package installed.
|
36
|
+
|
37
|
+
The Adapter also requires Christian Werner's
|
38
|
+
Ruby ODBC Bridge[http://www.ch-werner.de/rubyodbc]
|
39
|
+
(release 0.9991 or later), to bridge to an underlying ODBC driver.
|
40
|
+
|
41
|
+
|
42
|
+
== Contents
|
43
|
+
|
44
|
+
In the accompanying sources, the lib directory structure is equivalent
|
45
|
+
to the lib directory located under ACTIVE_RECORD_ROOT in your main
|
46
|
+
Ruby tree.
|
47
|
+
|
48
|
+
On Windows ACTIVE_RECORD_ROOT will be something like:
|
49
|
+
|
50
|
+
c:\ruby\lib\ruby\gems\1.8\gems\activerecord-x.y.z
|
51
|
+
|
52
|
+
On Unix and Mac OS X ACTIVE_RECORD_ROOT will be something like:
|
53
|
+
|
54
|
+
/usr/lib/ruby/gems/1.8/gems/activerecord-x.y.z
|
55
|
+
or
|
56
|
+
/usr/local/lib/ruby/gems/1.8/gems/activerecord-x.y.z
|
57
|
+
|
58
|
+
On Mac OS X using Locomotive ACTIVE_RECORD_ROOT will be
|
59
|
+
something like:
|
60
|
+
|
61
|
+
/Applications/Locomotive2/bundles/rails112.locobundle/i386/lib/ruby/gems/1.8/gems/activerecord-x.y.z
|
62
|
+
|
63
|
+
lib/ contains the files which constitute the new ODBC adapter.
|
64
|
+
|
65
|
+
test/ contains the fixture definitions for testing the adapter.
|
66
|
+
|
67
|
+
support/odbc_rails.diff contains some patches to ActiveRecord.
|
68
|
+
|
69
|
+
These will be submitted to the ActiveRecord source maintainer in
|
70
|
+
due course, if we request that the OpenLink ODBC Adapter for Ruby
|
71
|
+
on Rails be added to the ActiveRecord source distribution to accompany
|
72
|
+
the existing Rails adapters.
|
73
|
+
|
74
|
+
Only one patch in this diff file is really necessary - that to
|
75
|
+
|
76
|
+
lib/active_record/connection_adapters/abstract/schema_definitions.rb
|
77
|
+
|
78
|
+
This fixes what appears to be a bug in Active Record.
|
79
|
+
|
80
|
+
The patches to base_test.rb and migration_test.rb are not essential.
|
81
|
+
However, they modify or bypass certain tests to cope with limitations
|
82
|
+
of particular databases. Other developers have previously modified
|
83
|
+
the tests similarly to cope with limitations of other Rails-supported
|
84
|
+
databases. The patched versions of files touched by odbc_rails.diff can be
|
85
|
+
found in support/lib and support/test.
|
86
|
+
|
87
|
+
== Installation
|
88
|
+
|
89
|
+
There are 3 ways to install the ODBC Adapter package: either as a gem
|
90
|
+
(recommended), a plugin, or by running the custom installation script. Pick
|
91
|
+
one of the following, depending on whether you want the adapter to be
|
92
|
+
available system-wide or just within a particular Rails project.
|
93
|
+
|
94
|
+
=== Installation as a Gem
|
95
|
+
|
96
|
+
Install the odbc-rails gem by running:
|
97
|
+
|
98
|
+
# gem install -r odbc-rails --include-dependencies
|
99
|
+
|
100
|
+
Enable loading of the adapter by editing your Rails project's
|
101
|
+
config/environment.rb script:
|
102
|
+
|
103
|
+
* Add a <tt>require</tt> to your config/environment.rb immediately after
|
104
|
+
the line
|
105
|
+
|
106
|
+
require File.join(File.dirname(__FILE__), 'boot')
|
107
|
+
|
108
|
+
i.e.
|
109
|
+
|
110
|
+
require File.join(File.dirname(__FILE__), 'boot')
|
111
|
+
require 'odbc_adapter'
|
112
|
+
|
113
|
+
|
114
|
+
=== Installation as a Plugin
|
115
|
+
|
116
|
+
Installing the OpenLink ODBC Data Adapter as a plugin makes it available to
|
117
|
+
a particular Rails application only.
|
118
|
+
|
119
|
+
==== Automatic Plugin installation
|
120
|
+
|
121
|
+
The adapter can be automatically installed as a plugin by navigating to the
|
122
|
+
root of your Rails application and typing either:
|
123
|
+
|
124
|
+
script/plugin install odbc-rails
|
125
|
+
or
|
126
|
+
script/plugin install http://odbc-rails.rubyforge.org/plugins/odbc-rails
|
127
|
+
|
128
|
+
On Windows, replace <tt>script/plugin</tt> by <tt>ruby script/plugin</tt>.
|
129
|
+
e.g. <tt>ruby script/plugin install odbc-rails</tt>
|
130
|
+
|
131
|
+
|
132
|
+
==== Manual Plugin Installation
|
133
|
+
|
134
|
+
You can also install the plugin manually by unpacking the sources into the
|
135
|
+
vendor/plugins directory of your Rails application. If you use this method,
|
136
|
+
you must run install.rb immediately afterwards. This need only be done once.
|
137
|
+
|
138
|
+
install.rb modifies your application's config/environment.rb file, adding a
|
139
|
+
line to ensure that the database adapter plugin is initialized before
|
140
|
+
ActiveRecord. i.e.
|
141
|
+
|
142
|
+
require File.join(File.dirname(__FILE__), 'boot')
|
143
|
+
|
144
|
+
# Added by OpenLink ODBC Adapter (odbc-rails) plugin
|
145
|
+
Rails::Initializer.run(:load_plugins)
|
146
|
+
|
147
|
+
Rails::Initializer.run do |config|
|
148
|
+
|
149
|
+
Without this change, Rails attempts to initialize ActiveRecord before it loads
|
150
|
+
any plugins, causing an AdapterNotFound error. install.rb saves a copy of
|
151
|
+
environment.rb before making any changes.
|
152
|
+
|
153
|
+
=== Custom installation script
|
154
|
+
|
155
|
+
When using <tt>rake install</tt> or running the install_odbc.rb script by
|
156
|
+
hand, the script tries to determine the location of the activerecord
|
157
|
+
package within your Ruby installation and allows you to overrule the
|
158
|
+
directory it has found with a directory of your choice. If more than one
|
159
|
+
version of activerecord is found, the default will be the latest version.
|
160
|
+
|
161
|
+
On Mac OS X the installation script is also capable of locating Locomotive,
|
162
|
+
if installed, and will ask to install in there.
|
163
|
+
|
164
|
+
The install_odbc.rb script also verifies that the pre-requisites are
|
165
|
+
installed.
|
166
|
+
|
167
|
+
If all else fails this document also describes the steps to install the
|
168
|
+
ODBC adapter into activerecord manually.
|
169
|
+
|
170
|
+
|
171
|
+
==== Using rake
|
172
|
+
|
173
|
+
The simplest way to install the adapter is by using the following
|
174
|
+
command as root:
|
175
|
+
|
176
|
+
# rake install
|
177
|
+
|
178
|
+
or if your system supports sudo:
|
179
|
+
|
180
|
+
$ sudo rake install
|
181
|
+
|
182
|
+
|
183
|
+
The Rakefile also defines some targets specifically for developers:
|
184
|
+
|
185
|
+
$ rake rdoc # Generate rdoc documentation in ~/doc
|
186
|
+
|
187
|
+
$ rake package # Create distribution package in ~/distrib
|
188
|
+
|
189
|
+
$ rake clean # Remove generated files and directories
|
190
|
+
|
191
|
+
|
192
|
+
==== Running installation script
|
193
|
+
|
194
|
+
The second way of installing the adapter is running the
|
195
|
+
<tt>install_odbc.rb</tt> script as root.
|
196
|
+
|
197
|
+
# ruby install_odbc.rb
|
198
|
+
|
199
|
+
or if your system supports sudo:
|
200
|
+
|
201
|
+
$ sudo ruby install_odbc.rb
|
202
|
+
|
203
|
+
|
204
|
+
=== Installing files manually
|
205
|
+
|
206
|
+
The third way of installing the adapter is by performing the installation
|
207
|
+
steps yourself.
|
208
|
+
|
209
|
+
* Copy odbc_adapter.rb to the ACTIVE_RECORD_ROOT/lib/active_record/connection_adapters/ directory
|
210
|
+
|
211
|
+
* Copy the odbcext_*.rb files to the ACTIVE_RECORD_ROOT/lib/active_record/vendor/ directory
|
212
|
+
|
213
|
+
* Enable loading of the adapter (see below)
|
214
|
+
|
215
|
+
|
216
|
+
== Enabling Loading of the Adapter
|
217
|
+
|
218
|
+
This step is only necessary if the adapter was installed either as a gem or
|
219
|
+
by copying files into your Rails core manually. It is not required if the
|
220
|
+
adapter was installed as a plugin, using <tt>rake install</tt> or using
|
221
|
+
install_odbc.rb.
|
222
|
+
|
223
|
+
There are two ways to enable loading of the adapter:
|
224
|
+
|
225
|
+
1. Modify active_record.rb in your Rails 'core', adding the ODBC adapter to
|
226
|
+
the list of adapters recognized by ActiveRecord:
|
227
|
+
|
228
|
+
* Edit ACTIVE_RECORD_ROOT/lib/active_record.rb, appending `odbc' to the line:
|
229
|
+
|
230
|
+
RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase)
|
231
|
+
|
232
|
+
i.e.
|
233
|
+
|
234
|
+
RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql ... openbase odbc)
|
235
|
+
|
236
|
+
*or*
|
237
|
+
|
238
|
+
2. Modify your Rails application's configuration to <tt>require</tt> the
|
239
|
+
adapter:
|
240
|
+
|
241
|
+
|
242
|
+
* Add a <tt>require</tt> to your config/environment.rb immediately after
|
243
|
+
the line
|
244
|
+
|
245
|
+
require File.join(File.dirname(__FILE__), 'boot')
|
246
|
+
|
247
|
+
i.e.
|
248
|
+
|
249
|
+
require File.join(File.dirname(__FILE__), 'boot')
|
250
|
+
require 'odbc_adapter'
|
251
|
+
|
252
|
+
== Configuring the Adapter
|
253
|
+
|
254
|
+
Examples of the required connection parameters can be found in
|
255
|
+
|
256
|
+
test/connections/native_odbc/connection.rb
|
257
|
+
|
258
|
+
If you enable call-tracing by setting :trace => true, specify the
|
259
|
+
logger output file as illustrated in connection.rb, e.g.
|
260
|
+
|
261
|
+
ActiveRecord::Base.logger = Logger.new("debug_odbc.log")
|
262
|
+
|
263
|
+
|
264
|
+
== License
|
265
|
+
|
266
|
+
The OpenLink ODBC Adapter for Ruby on Rails is released under the
|
267
|
+
MIT license as detailed in the file COPYING.
|
268
|
+
|
269
|
+
|
270
|
+
=== Submitting fixes and enhancements
|
271
|
+
|
272
|
+
Patches and new contributions can be submitted as diffs from the
|
273
|
+
current CVS archive by:
|
274
|
+
|
275
|
+
$ cvs add newfiles
|
276
|
+
|
277
|
+
$ cvs -z3 diff -uN > diffs
|
278
|
+
|
279
|
+
Patches and contributions can be send to the OpenLink iODBC source
|
280
|
+
archive mainainter at mailto:iodbc@openlinksw.com to be included
|
281
|
+
in the next distribution. Please provide accompanying documentation
|
282
|
+
on which bugs are fixed or new features are introduced.
|
@@ -0,0 +1,1792 @@
|
|
1
|
+
#
|
2
|
+
# $Id: odbc_adapter.rb,v 1.2 2006/12/11 12:00:10 source Exp $
|
3
|
+
#
|
4
|
+
# OpenLink ODBC Adapter for Ruby on Rails
|
5
|
+
# Copyright (C) 2006 OpenLink Software
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
# a copy of this software and associated documentation files (the
|
9
|
+
# "Software"), to deal in the Software without restriction, including
|
10
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
# permit persons to whom the Software is furnished to do so, subject
|
13
|
+
# to the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be
|
16
|
+
# included in all copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
21
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
22
|
+
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
23
|
+
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
#
|
26
|
+
|
27
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
28
|
+
|
29
|
+
begin
|
30
|
+
require_library_or_gem 'odbc' unless self.class.const_defined?(:ODBC)
|
31
|
+
#-------------------------------------------------------------------------
|
32
|
+
|
33
|
+
module ActiveRecord
|
34
|
+
class Base # :nodoc:
|
35
|
+
def self.odbc_connection(config) #:nodoc:
|
36
|
+
config = config.symbolize_keys
|
37
|
+
if config.has_key?(:dsn)
|
38
|
+
dsn = config[:dsn]
|
39
|
+
else
|
40
|
+
raise ActiveRecordError, "No data source name (DSN) specified."
|
41
|
+
end
|
42
|
+
username = config[:username] ? config[:username].to_s : nil
|
43
|
+
password = config[:password] ? config[:password].to_s : nil
|
44
|
+
trace = config[:trace] ? config[:trace] : false
|
45
|
+
conv_num_lits = config[:convert_numeric_literals] ? config[:convert_numeric_literals] : false
|
46
|
+
conn = ODBC::connect(dsn, username, password)
|
47
|
+
conn.autocommit = true
|
48
|
+
ConnectionAdapters::ODBCAdapter.new(conn, [dsn, username, password],
|
49
|
+
trace, conv_num_lits, logger)
|
50
|
+
end
|
51
|
+
end # class Base
|
52
|
+
|
53
|
+
module ConnectionAdapters # :nodoc:
|
54
|
+
|
55
|
+
# This is an ODBC adapter for the ActiveRecord framework.
|
56
|
+
#
|
57
|
+
# The ODBC adapter requires the Ruby ODBC module (version 0.9991 or
|
58
|
+
# later), available from http://raa.ruby-lang.org/project/ruby-odbc
|
59
|
+
#
|
60
|
+
# == Status at 11-Dec-2006
|
61
|
+
#
|
62
|
+
# The current adapter supports Ingres r3, Informix 9.3 or later,
|
63
|
+
# Virtuoso (Open-Source Edition) 4.5, Oracle 10g, MySQL 5,
|
64
|
+
# SQL Server 2000, Sybase ASE 15, DB2 v9, Progress 9/10 (SQL-92 engine)
|
65
|
+
# and Progress 8 (SQL-89 engine).
|
66
|
+
#
|
67
|
+
# == Testing Environments
|
68
|
+
#
|
69
|
+
# The adapter has been tested in the following environments:
|
70
|
+
# * Windows XP, Linux Fedora Core, Mac OS X
|
71
|
+
# The iODBC Driver Manager was used on Linux and Mac OS X.
|
72
|
+
#
|
73
|
+
# Databases supported using OpenLink ODBC drivers:
|
74
|
+
# * Informix, Ingres, Oracle, MySQL, SQL Server, Sybase, DB2, Progress
|
75
|
+
# Databases supported using the database's own native ODBC driver:
|
76
|
+
# * Virtuoso, MySQL, Informix
|
77
|
+
#
|
78
|
+
# === Note
|
79
|
+
# * OpenLink ODBC drivers work with v0.998 or later of the Ruby ODBC
|
80
|
+
# bridge.
|
81
|
+
# * The native MySQL driver requires v0.9991 of the Ruby ODBC bridge.
|
82
|
+
#
|
83
|
+
# == Information
|
84
|
+
#
|
85
|
+
# More information can be found at:
|
86
|
+
# * http://rubyforge.org/projects/odbc-rails/
|
87
|
+
# * http://odbc-rails.openlinksw.com
|
88
|
+
# * http://virtuoso.openlinksw.com/wiki/main/OdbcRails/RailsAdapterWeb
|
89
|
+
# * http://sourceforge.net/projects/virtuoso/
|
90
|
+
#
|
91
|
+
# Maintainer: Carl Blakeley (mailto:cblakeley@openlinksw.co.uk)
|
92
|
+
#
|
93
|
+
# == Connection Options
|
94
|
+
#
|
95
|
+
# The following options are supported by the ODBC adapter.
|
96
|
+
#
|
97
|
+
# <tt>:dsn</tt>::
|
98
|
+
# Specifies the ODBC data source name.
|
99
|
+
# <tt>:username</tt>::
|
100
|
+
# Specifies the database user.
|
101
|
+
# <tt>:password</tt>::
|
102
|
+
# Specifies the database password.
|
103
|
+
# <tt>:trace</tt>::
|
104
|
+
# If set to <tt>true</tt>, turns on simple call tracing to the log file
|
105
|
+
# referenced by ActiveRecord::Base.logger. If omitted, <tt>:trace</tt>
|
106
|
+
# defaults to <tt>false</tt>. (We also suggest setting
|
107
|
+
# ActiveRecord::Base.colorize_logging = false).
|
108
|
+
# <tt>:convert_numeric_literals</tt>::
|
109
|
+
# If set to <tt>true</tt>, suppresses quoting of numeric literals.
|
110
|
+
# If omitted, <tt>:convert_numeric_literals</tt> defaults to
|
111
|
+
# <tt>false</tt>.
|
112
|
+
#
|
113
|
+
# == Usage Notes
|
114
|
+
# === Informix
|
115
|
+
# In order to match the formats of Ruby's Date, Time and DateTime types,
|
116
|
+
# the following settings for Informix were used:
|
117
|
+
# * DBDATE=Y4MD-
|
118
|
+
# * DBTIME=%Y-%m-%d %H:%M:%S
|
119
|
+
# To support mixed-case/quoted table names:
|
120
|
+
# * DELIMIDENT=y
|
121
|
+
# To allow embedded newlines in quoted strings:
|
122
|
+
# * set ALLOW_NEWLINE=1 in the ONCONFIG configuration file.
|
123
|
+
#
|
124
|
+
# The adapter relies on an ODBC extension to SQLGetStmtOption implemented
|
125
|
+
# by some ODBC drivers (SQL_LASTSERIAL=1049) to retrieve the primary key
|
126
|
+
# value auto-generated by an insert into a SERIAL column.
|
127
|
+
#
|
128
|
+
# === Ingres
|
129
|
+
# To match the formats of Ruby's Time and DateTime types,
|
130
|
+
# the following settings for Ingres were used:
|
131
|
+
# * II_DATE_FORMAT=SWEDEN
|
132
|
+
#
|
133
|
+
# === Oracle
|
134
|
+
# If using an OpenLink Oracle driver or agent, the 'jetfix' configuration
|
135
|
+
# option must be enabled to obtain the correct type mappings.
|
136
|
+
#
|
137
|
+
# === Sybase
|
138
|
+
# Set the connection option :convert_numeric_literals to <tt>true</tt> to
|
139
|
+
# avoid errors similar to:
|
140
|
+
# "Implicit conversion from datatype 'VARCHAR' to 'INT' is not allowed."
|
141
|
+
#
|
142
|
+
# :boolean columns use the BIT SQL type, which does not allow nulls or
|
143
|
+
# indexes. If a DEFAULT is not specified for #create_table, the
|
144
|
+
# column will be declared with DEFAULT 0.
|
145
|
+
#
|
146
|
+
# Migrations are supported, but for ALTER TABLE commands to
|
147
|
+
# work, the database must have the database option 'select into' set to
|
148
|
+
# 'true' with sp_dboption.
|
149
|
+
#
|
150
|
+
# === DB2
|
151
|
+
# Set the connection option :convert_numeric_literals to <tt>true</tt> to
|
152
|
+
# avoid errors similar to:
|
153
|
+
# "The data types of the operands for the operation "=" are not compatible."
|
154
|
+
#
|
155
|
+
# To obtain the correct type mappings, ensure LongDataCompat is set to 1
|
156
|
+
# in the file db2cli.ini included in the DB2 client.
|
157
|
+
#
|
158
|
+
# Migrations are supported but the following methods are not
|
159
|
+
# implemented because of lack of support in DB2 SQL.
|
160
|
+
# * <tt>change_column, remove_column, rename_column</tt>
|
161
|
+
#
|
162
|
+
# === Progress 9/10 with SQL-92 engine
|
163
|
+
# Connections to Progress v9 and above are assumed to be to the SQL-92
|
164
|
+
# engine. Migrations are supported but the following methods are not
|
165
|
+
# implemented because of lack of support in Progress SQL.
|
166
|
+
# * <tt>rename_table, change_column, remove_column, rename_column</tt>
|
167
|
+
#
|
168
|
+
# === Progress 8 with SQL-89 engine
|
169
|
+
# Set the connection option :convert_numeric_literals to <tt>true</tt>
|
170
|
+
# to avoid errors similar to:
|
171
|
+
# "Incompatible data types in expression or assignment. (223)"
|
172
|
+
#
|
173
|
+
# Migrations are supported but the following methods are not
|
174
|
+
# implemented because of lack of support in Progress SQL.
|
175
|
+
# * <tt>rename_table, change_column, remove_column, rename_column</tt>
|
176
|
+
#
|
177
|
+
|
178
|
+
class ODBCAdapter < AbstractAdapter
|
179
|
+
|
180
|
+
#-------------------------------------------------------------------
|
181
|
+
# DbmsInfo holds DBMS-dependent information which cannot be derived
|
182
|
+
# satisfactorily through ODBC
|
183
|
+
class DbmsInfo # :nodoc: all
|
184
|
+
private_class_method :new
|
185
|
+
@@dbmsInfo = nil
|
186
|
+
@@dbms_lookup_tbl = {
|
187
|
+
# Uses dbmsName as key and dbmsMajorVer as a subkey.
|
188
|
+
:db2 => {
|
189
|
+
:any_version => {
|
190
|
+
:primary_key => "integer generated by default as identity (start with 10000) primary key",
|
191
|
+
:has_autoincrement_col => true,
|
192
|
+
:supports_migrations => true,
|
193
|
+
:supports_schema_names => true,
|
194
|
+
:supports_count_distinct => true
|
195
|
+
}
|
196
|
+
},
|
197
|
+
:informix => {
|
198
|
+
:any_version => {
|
199
|
+
:primary_key => "serial primary key",
|
200
|
+
:has_autoincrement_col => true,
|
201
|
+
:supports_migrations => true,
|
202
|
+
:supports_schema_names => true,
|
203
|
+
:supports_count_distinct => true
|
204
|
+
}
|
205
|
+
},
|
206
|
+
:ingres => {
|
207
|
+
:any_version => {
|
208
|
+
:primary_key => "integer primary key not null",
|
209
|
+
:has_autoincrement_col => false,
|
210
|
+
:supports_migrations => true,
|
211
|
+
:supports_schema_names => true,
|
212
|
+
:supports_count_distinct => true
|
213
|
+
}
|
214
|
+
},
|
215
|
+
:mysql => {
|
216
|
+
:any_version => {
|
217
|
+
:primary_key => "int(11) not null auto_increment primary key",
|
218
|
+
:has_autoincrement_col => true,
|
219
|
+
:supports_migrations => true,
|
220
|
+
:supports_schema_names => false,
|
221
|
+
:supports_count_distinct => true
|
222
|
+
},
|
223
|
+
5 => {
|
224
|
+
:primary_key => "int(11) not null auto_increment primary key",
|
225
|
+
:has_autoincrement_col => true,
|
226
|
+
:supports_migrations => true,
|
227
|
+
:supports_schema_names => false,
|
228
|
+
:supports_count_distinct => true
|
229
|
+
}
|
230
|
+
},
|
231
|
+
:microsoftsqlserver => {
|
232
|
+
:any_version => {
|
233
|
+
:primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
|
234
|
+
:has_autoincrement_col => true,
|
235
|
+
:supports_migrations => true,
|
236
|
+
:supports_schema_names => true,
|
237
|
+
:supports_count_distinct => true
|
238
|
+
},
|
239
|
+
8 => {
|
240
|
+
:primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
|
241
|
+
:has_autoincrement_col => true,
|
242
|
+
:supports_migrations => true,
|
243
|
+
:supports_schema_names => true,
|
244
|
+
:supports_count_distinct => true
|
245
|
+
}
|
246
|
+
},
|
247
|
+
:oracle => {
|
248
|
+
:any_version => {
|
249
|
+
:primary_key => "number(10) PRIMARY KEY NOT NULL",
|
250
|
+
:has_autoincrement_col => false,
|
251
|
+
:supports_migrations => true,
|
252
|
+
:supports_schema_names => true,
|
253
|
+
:supports_count_distinct => true
|
254
|
+
}
|
255
|
+
},
|
256
|
+
:progress => {
|
257
|
+
:any_version => {
|
258
|
+
:primary_key => "integer not null primary key",
|
259
|
+
:has_autoincrement_col => false,
|
260
|
+
:supports_migrations => true,
|
261
|
+
:supports_schema_names => true,
|
262
|
+
:supports_count_distinct => true
|
263
|
+
}
|
264
|
+
},
|
265
|
+
:progress89 => {
|
266
|
+
:any_version => {
|
267
|
+
:primary_key => "integer not null unique",
|
268
|
+
:has_autoincrement_col => false,
|
269
|
+
:supports_migrations => true,
|
270
|
+
:supports_schema_names => false,
|
271
|
+
:supports_count_distinct => true
|
272
|
+
}
|
273
|
+
},
|
274
|
+
:sybase => {
|
275
|
+
:any_version => {
|
276
|
+
:primary_key => "int IDENTITY PRIMARY KEY",
|
277
|
+
:has_autoincrement_col => true,
|
278
|
+
:supports_migrations => true,
|
279
|
+
:supports_schema_names => true,
|
280
|
+
:supports_count_distinct => true
|
281
|
+
}
|
282
|
+
},
|
283
|
+
:virtuoso => {
|
284
|
+
:any_version => {
|
285
|
+
:primary_key => "int NOT NULL IDENTITY PRIMARY KEY",
|
286
|
+
:has_autoincrement_col => true,
|
287
|
+
:supports_migrations => true,
|
288
|
+
:supports_schema_names => true,
|
289
|
+
:supports_count_distinct => true
|
290
|
+
}
|
291
|
+
}
|
292
|
+
}
|
293
|
+
|
294
|
+
def self.create
|
295
|
+
@@dbmsInfo = new unless @@dbmsInfo
|
296
|
+
@@dbmsInfo
|
297
|
+
end
|
298
|
+
|
299
|
+
def get_info(dbms_name, dbms_major_ver, info_type)
|
300
|
+
if (val = @@dbms_lookup_tbl[dbms_name]) then
|
301
|
+
if (val = val[dbms_major_ver] || val = val[:any_version]) then
|
302
|
+
val = val[info_type]
|
303
|
+
end
|
304
|
+
end
|
305
|
+
if val.nil? then
|
306
|
+
raise ActiveRecordError, "Lookup for #{info_type} failed"
|
307
|
+
end
|
308
|
+
val
|
309
|
+
end
|
310
|
+
end # class DbmsInfo
|
311
|
+
|
312
|
+
#---------------------------------------------------------------------
|
313
|
+
# DSInfo holds SQLGetInfo responses from the data source
|
314
|
+
class DSInfo # :nodoc: all
|
315
|
+
attr_reader :info
|
316
|
+
|
317
|
+
# Specifies the miniminum information we need about the data source
|
318
|
+
@@baseInfo =
|
319
|
+
[
|
320
|
+
ODBC::SQL_DBMS_NAME,
|
321
|
+
ODBC::SQL_DBMS_VER,
|
322
|
+
ODBC::SQL_IDENTIFIER_CASE,
|
323
|
+
ODBC::SQL_QUOTED_IDENTIFIER_CASE,
|
324
|
+
ODBC::SQL_IDENTIFIER_QUOTE_CHAR,
|
325
|
+
ODBC::SQL_MAX_IDENTIFIER_LEN,
|
326
|
+
ODBC::SQL_MAX_TABLE_NAME_LEN,
|
327
|
+
ODBC::SQL_USER_NAME
|
328
|
+
]
|
329
|
+
|
330
|
+
def initialize(connection)
|
331
|
+
@connection = connection
|
332
|
+
@info = Hash.new
|
333
|
+
@@baseInfo.each { |i| @info[i] = nil }
|
334
|
+
getBaseInfo(@info)
|
335
|
+
# TODO: HACK! OpenLink's Progress ODBC driver reports
|
336
|
+
# SQL_IDENTIFIER_CASE as SQL_IC_MIXED, but it should be
|
337
|
+
# SQL_IC_UPPER. All the driver's ODBC catalog calls return
|
338
|
+
# identifiers in uppercase.
|
339
|
+
@info[ODBC::SQL_IDENTIFIER_CASE] = ODBC::SQL_IC_UPPER if @info[ODBC::SQL_DBMS_NAME] =~ /progress/i
|
340
|
+
end
|
341
|
+
|
342
|
+
private
|
343
|
+
def getBaseInfo(infoTypes)
|
344
|
+
infoTypes.each_key do |infoType|
|
345
|
+
begin
|
346
|
+
infoTypes[infoType] = @connection.get_info(infoType)
|
347
|
+
rescue ODBC::Error
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
end # class DSInfo
|
353
|
+
|
354
|
+
#---------------------------------------------------------------------
|
355
|
+
|
356
|
+
# ODBC constants missing from Christian Werner's Ruby ODBC driver
|
357
|
+
SQL_NO_NULLS = 0 # :nodoc:
|
358
|
+
SQL_NULLABLE = 1 # :nodoc:
|
359
|
+
SQL_NULLABLE_UNKNOWN = 2 # :nodoc:
|
360
|
+
|
361
|
+
# dbInfo: ref to DSInfo instance
|
362
|
+
attr_reader :dsInfo # :nodoc:
|
363
|
+
|
364
|
+
# The name of DBMS currently connected to.
|
365
|
+
#
|
366
|
+
# Different ODBC drivers might return different names for the same
|
367
|
+
# DBMS; so similar names are mapped to the same symbol.
|
368
|
+
# _dbmsName_ is the SQL_DBMS_NAME returned by ODBC, downcased with
|
369
|
+
# whitespace removed. e.g. <tt>informix</tt>, <tt>ingres</tt>,
|
370
|
+
# <tt>microsoftsqlserver</tt> etc.
|
371
|
+
attr_reader :dbmsName
|
372
|
+
|
373
|
+
# Supports lookups of DBMS-dependent information/settings which
|
374
|
+
# cannot be derived satisfactorily through ODBC
|
375
|
+
@@dbmsLookups = DbmsInfo.create
|
376
|
+
|
377
|
+
@@trace = nil
|
378
|
+
#--
|
379
|
+
|
380
|
+
def initialize(connection, connection_options, trace, convert_numeric_literals, logger = nil)
|
381
|
+
@@trace = trace && logger if !@@trace
|
382
|
+
# Mixins in odbcext_xxx.rb included using Object#extend can't access
|
383
|
+
# @@trace. Why?
|
384
|
+
# (Error message "NameError: uninitialized class variable @@trace".)
|
385
|
+
# So create an equivalent instance variable
|
386
|
+
@trace = @@trace
|
387
|
+
|
388
|
+
super(connection, logger)
|
389
|
+
|
390
|
+
@logger.unknown("ODBCAdapter#initialize>") if @@trace
|
391
|
+
|
392
|
+
@connection, @connection_options = connection, connection_options
|
393
|
+
@convert_numeric_literals = convert_numeric_literals
|
394
|
+
# Caches SQLGetInfo output
|
395
|
+
@dsInfo = DSInfo.new(connection)
|
396
|
+
# Caches SQLGetTypeInfo output
|
397
|
+
@typeInfo = nil
|
398
|
+
# Caches mapping of Rails abstract data types to DBMS native types.
|
399
|
+
@abstract2NativeTypeMap = nil
|
400
|
+
|
401
|
+
# Set @dbmsName and @dbmsMajorVer from SQLGetInfo output.
|
402
|
+
# Each ODBCAdapter instance is associated with only one connection,
|
403
|
+
# so using ODBCAdapter instance variables for DBMS name and version
|
404
|
+
# is OK.
|
405
|
+
|
406
|
+
@dbmsMajorVer = @dsInfo.info[ODBC::SQL_DBMS_VER].split('.')[0].to_i
|
407
|
+
@dbmsName = @dsInfo.info[ODBC::SQL_DBMS_NAME].downcase.gsub(/\s/,'')
|
408
|
+
# Different ODBC drivers might return different names for the same
|
409
|
+
# DBMS. So map similar names to the same symbol.
|
410
|
+
@dbmsName = dbmsNameToSym(@dbmsName, @dbmsMajorVer)
|
411
|
+
|
412
|
+
# Now we know which DBMS we're connected to, extend this ODBCAdapter
|
413
|
+
# instance with the appropriate DBMS specific extensions
|
414
|
+
@odbcExtFile = "active_record/vendor/odbcext_#{@dbmsName}"
|
415
|
+
begin
|
416
|
+
require "#{@odbcExtFile}"
|
417
|
+
self.extend ODBCExt
|
418
|
+
rescue MissingSourceFile
|
419
|
+
puts "ODBCAdapter#initialize> Couldn't find extension #{@odbcExtFile}.rb"
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
#--
|
424
|
+
# ABSTRACT ADAPTER OVERRIDES =======================================
|
425
|
+
#
|
426
|
+
# see abstract_adapter.rb
|
427
|
+
|
428
|
+
# Returns the human-readable name of the adapter.
|
429
|
+
def adapter_name
|
430
|
+
@logger.unknown("ODBCAdapter#adapter_name>") if @@trace
|
431
|
+
'ODBC'
|
432
|
+
end
|
433
|
+
|
434
|
+
# Does this adapter support migrations?
|
435
|
+
def supports_migrations?
|
436
|
+
@logger.unknown("ODBCAdapter#supports_migrations?>") if @@trace
|
437
|
+
@@dbmsLookups.get_info(@dbmsName, @dbmsMajorVer, :supports_migrations)
|
438
|
+
end
|
439
|
+
|
440
|
+
# Does the database support COUNT(DISTINCT) queries?
|
441
|
+
# e.g. <tt>select COUNT(DISTINCT ArtistID) from CDs</tt>
|
442
|
+
def supports_count_distinct?
|
443
|
+
@logger.unknown("ODBCAdapter#supports_count_distinct?>") if @@trace
|
444
|
+
@@dbmsLookups.get_info(@dbmsName, @dbmsMajorVer, :supports_count_distinct)
|
445
|
+
end
|
446
|
+
|
447
|
+
# Should primary key values be selected from their corresponding
|
448
|
+
# sequence before the insert statement? If true, #next_sequence_value
|
449
|
+
# is called before each insert to set the record's primary key.
|
450
|
+
def prefetch_primary_key?(table_name = nil)
|
451
|
+
@logger.unknown("ODBCAdapter#prefetch_primary_key?>") if @@trace
|
452
|
+
# Return true for any DBMS which can't support #last_insert_id.
|
453
|
+
# i.e. doesn't support an autoincrement column type. An
|
454
|
+
# implementation of #next_sequence_value must be provided for any
|
455
|
+
# such database.
|
456
|
+
!@@dbmsLookups.get_info(@dbmsName, @dbmsMajorVer, :has_autoincrement_col)
|
457
|
+
end
|
458
|
+
|
459
|
+
# Returns true if this connection active.
|
460
|
+
def active?
|
461
|
+
@logger.unknown("ODBCAdapter#active?>") if @@trace
|
462
|
+
@connection.connected?
|
463
|
+
end
|
464
|
+
|
465
|
+
# Reconnects to the database.
|
466
|
+
def reconnect!
|
467
|
+
@logger.unknown("ODBCAdapter#reconnect!>") if @@trace
|
468
|
+
@connection.disconnect if @connection.connected?
|
469
|
+
@connection = ODBC::connect(*@connection_options)
|
470
|
+
# There's no need to refresh the data source info in @dsInfo because
|
471
|
+
# we're reconnecting to the same data source.
|
472
|
+
rescue Exception => e
|
473
|
+
@logger.unknown("exception=#{e}") if @@trace
|
474
|
+
raise ActiveRecordError, e.message
|
475
|
+
end
|
476
|
+
|
477
|
+
# Disconnects from the database.
|
478
|
+
def disconnect!
|
479
|
+
@logger.unknown("ODBCAdapter#disconnect!>") if @@trace
|
480
|
+
@connection.disconnect if @connection.connected?
|
481
|
+
rescue Exception => e
|
482
|
+
@logger.unknown("exception=#{e}") if @@trace
|
483
|
+
raise ActiveRecordError, e.message
|
484
|
+
end
|
485
|
+
|
486
|
+
#--
|
487
|
+
# QUOTING OVERRIDES ================================================
|
488
|
+
#
|
489
|
+
# see: abstract/quoting.rb
|
490
|
+
|
491
|
+
# Quotes the column value
|
492
|
+
#--
|
493
|
+
# to help prevent {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
|
494
|
+
#++
|
495
|
+
def quote(value, column = nil)
|
496
|
+
@logger.unknown("ODBCAdapter#quote>") if @@trace
|
497
|
+
@logger.unknown("args=[#{value}]") if @@trace
|
498
|
+
case value
|
499
|
+
when String
|
500
|
+
if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
501
|
+
"'#{quote_string(column.class.string_to_binary(value))}'"
|
502
|
+
elsif (column && [:integer, :float].include?(column.type)) ||
|
503
|
+
(column.nil? && @convert_numeric_literals &&
|
504
|
+
(value =~ /^[-+]?[0-9]+[.]?[0-9]*([eE][-+]?[0-9]+)?$/))
|
505
|
+
value
|
506
|
+
else
|
507
|
+
"'#{quote_string(value)}'" # ' (for ruby-mode)
|
508
|
+
end
|
509
|
+
when NilClass then "NULL"
|
510
|
+
when TrueClass then (column && column.type == :integer ?
|
511
|
+
'1' : quoted_true)
|
512
|
+
when FalseClass then (column && column.type == :integer ?
|
513
|
+
'0' : quoted_false)
|
514
|
+
when Float, Fixnum, Bignum then value.to_s
|
515
|
+
when Date then quoted_date(value)
|
516
|
+
when Time, DateTime then quoted_date(value)
|
517
|
+
else
|
518
|
+
super
|
519
|
+
end
|
520
|
+
rescue Exception => e
|
521
|
+
@logger.unknown("exception=#{e}") if @@trace
|
522
|
+
raise ActiveRecordError, e.message
|
523
|
+
end
|
524
|
+
|
525
|
+
# Quotes a string, escaping any ' (single quote) and \ (backslash)
|
526
|
+
# characters.
|
527
|
+
def quote_string(string)
|
528
|
+
@logger.unknown("ODBCAdapter#quote_string>") if @@trace
|
529
|
+
@logger.unknown("args=[#{string}]") if @@trace
|
530
|
+
string.gsub(/\'/, "''")
|
531
|
+
end
|
532
|
+
|
533
|
+
# Returns a quoted form of the column name.
|
534
|
+
def quote_column_name(name)
|
535
|
+
@logger.unknown("ODBCAdapter#quote_column_name>") if @@trace
|
536
|
+
@logger.unknown("args=[#{name}]") if @@trace
|
537
|
+
name = name.to_s if name.class == Symbol
|
538
|
+
idQuoteChar = @dsInfo.info[ODBC::SQL_IDENTIFIER_QUOTE_CHAR]
|
539
|
+
return name if !idQuoteChar || ((idQuoteChar = idQuoteChar.strip).length == 0)
|
540
|
+
idQuoteChar = idQuoteChar[0]
|
541
|
+
|
542
|
+
# Avoid quoting any already quoted name
|
543
|
+
return name if name[0] == idQuoteChar && name[-1] == idQuoteChar
|
544
|
+
|
545
|
+
# If DBMS's SQL_IDENTIFIER_CASE = SQL_IC_UPPER, only quote mixed
|
546
|
+
# case names.
|
547
|
+
# See #dbmsIdentCase for the identifier case conventions used by this
|
548
|
+
# adapter.
|
549
|
+
if @dsInfo.info[ODBC::SQL_IDENTIFIER_CASE] == ODBC::SQL_IC_UPPER
|
550
|
+
return name unless (name =~ /([A-Z]+[a-z])|([a-z]+[A-Z])/)
|
551
|
+
end
|
552
|
+
|
553
|
+
idQuoteChar.chr + name + idQuoteChar.chr
|
554
|
+
end
|
555
|
+
|
556
|
+
def quoted_true
|
557
|
+
@logger.unknown("ODBCAdapter#quoted_true>") if @@trace
|
558
|
+
'1'
|
559
|
+
end
|
560
|
+
|
561
|
+
def quoted_false
|
562
|
+
@logger.unknown("ODBCAdapter#quoted_false>") if @@trace
|
563
|
+
'0'
|
564
|
+
end
|
565
|
+
|
566
|
+
def quoted_date(value)
|
567
|
+
@logger.unknown("ODBCAdapter#quoted_date>") if @@trace
|
568
|
+
@logger.unknown("args=[#{value}]") if @@trace
|
569
|
+
# Ideally, we'd return an ODBC date or timestamp literal escape
|
570
|
+
# sequence, but not all ODBC drivers support them.
|
571
|
+
case value
|
572
|
+
when Time, DateTime
|
573
|
+
#%Q!{ts #{value.strftime("%Y-%m-%d %H:%M:%S")}}!
|
574
|
+
%Q!'#{value.strftime("%Y-%m-%d %H:%M:%S")}'!
|
575
|
+
when Date
|
576
|
+
#%Q!{d #{value.strftime("%Y-%m-%d")}}!
|
577
|
+
%Q!'#{value.strftime("%Y-%m-%d")}'!
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
#--
|
582
|
+
# DATABASE STATEMENTS OVERRIDES ====================================
|
583
|
+
#
|
584
|
+
# see: abstract/database_statements.rb
|
585
|
+
|
586
|
+
# Begins a transaction (and turns off auto-committing).
|
587
|
+
def begin_db_transaction
|
588
|
+
@logger.unknown("ODBCAdapter#begin_db_transaction>") if @@trace
|
589
|
+
@connection.autocommit = false
|
590
|
+
rescue Exception => e
|
591
|
+
@logger.unknown("exception=#{e}") if @@trace
|
592
|
+
raise ActiveRecordError, e.message
|
593
|
+
end
|
594
|
+
|
595
|
+
# Commits the transaction (and turns on auto-committing).
|
596
|
+
def commit_db_transaction
|
597
|
+
@logger.unknown("ODBCAdapter#commit_db_transaction>") if @@trace
|
598
|
+
@connection.commit
|
599
|
+
# ODBC chains transactions. Turn autocommit on after commit to
|
600
|
+
# allow explicit transaction initiation.
|
601
|
+
@connection.autocommit = true
|
602
|
+
rescue Exception => e
|
603
|
+
@logger.unknown("exception=#{e}") if @@trace
|
604
|
+
raise ActiveRecordError, e.message
|
605
|
+
end
|
606
|
+
|
607
|
+
# Rolls back the transaction (and turns on auto-committing).
|
608
|
+
def rollback_db_transaction
|
609
|
+
@logger.unknown("ODBCAdapter#rollback_db_transaction>") if @@trace
|
610
|
+
@connection.rollback
|
611
|
+
# ODBC chains transactions. Turn autocommit on after rollback to
|
612
|
+
# allow explicit transaction initiation.
|
613
|
+
@connection.autocommit = true
|
614
|
+
rescue Exception => e
|
615
|
+
@logger.unknown("exception=#{e}") if @@trace
|
616
|
+
raise ActiveRecordError, e.message
|
617
|
+
end
|
618
|
+
|
619
|
+
# Appends +LIMIT+ and/or +OFFSET+ options to a SQL statement.
|
620
|
+
# See DatabaseStatements#add_limit_offset!
|
621
|
+
#--
|
622
|
+
# Base class accepts only +LIMIT+ *AND* +OFFSET+
|
623
|
+
def add_limit_offset!(sql, options)
|
624
|
+
@logger.unknown("ODBCAdapter#add_limit_offset!>") if @@trace
|
625
|
+
@logger.unknown("args=[#{sql}]") if @@trace
|
626
|
+
if limit = options[:limit] then sql << " LIMIT #{limit}" end
|
627
|
+
if offset = options[:offset] then sql << " OFFSET #{offset}" end
|
628
|
+
end
|
629
|
+
|
630
|
+
# Returns an array of record hashes with the column names as keys and
|
631
|
+
# column values as values.
|
632
|
+
def select_all(sql, name = nil)
|
633
|
+
@logger.unknown("ODBCAdapter#select_all>") if @@trace
|
634
|
+
@logger.unknown("args=[#{sql}|#{name}]") if @@trace
|
635
|
+
retVal = []
|
636
|
+
scrollableCursor = false
|
637
|
+
limit = 0
|
638
|
+
offset = 0
|
639
|
+
qry = sql.dup
|
640
|
+
|
641
|
+
# Strip OFFSET and LIMIT from query if present, since ODBC doesn't
|
642
|
+
# support them in a generic form.
|
643
|
+
#
|
644
|
+
# TODO: Translate any OFFSET/LIMIT option to native SQL if DBMS supports it.
|
645
|
+
# This will perform much better than simulating them.
|
646
|
+
if qry =~ /(\bLIMIT\s+)(\d+)/i then
|
647
|
+
if (limit = $2.to_i) == 0 then return retVal end
|
648
|
+
end
|
649
|
+
|
650
|
+
if qry =~ /(\bOFFSET\s+)(\d+)/i then offset = $2.to_i end
|
651
|
+
qry.gsub!(/(\bLIMIT\s+\d+|\bOFFSET\s+\d+)/i, '')
|
652
|
+
|
653
|
+
# It's been assumed that it's quicker to support an offset and/or
|
654
|
+
# limit restriction using a forward-only cursor. A static cursor will
|
655
|
+
# presumably take a snapshot of the whole result set, whereas when
|
656
|
+
# using a forward-only cursor we only fetch the first offset+limit
|
657
|
+
# rows.
|
658
|
+
=begin
|
659
|
+
if offset > 0 then
|
660
|
+
scrollableCursor = true
|
661
|
+
begin
|
662
|
+
# ODBCStatement::fetch_first requires a scrollable cursor
|
663
|
+
@connection.cursortype = ODBC::SQL_CURSOR_STATIC
|
664
|
+
rescue
|
665
|
+
# Assume ODBC driver doesn't support scrollable cursors
|
666
|
+
@connection.cursortype = ODBC::SQL_CURSOR_FORWARD_ONLY
|
667
|
+
scrollableCursor = false
|
668
|
+
end
|
669
|
+
end
|
670
|
+
=end
|
671
|
+
|
672
|
+
# Execute the query
|
673
|
+
begin
|
674
|
+
stmt = @connection.run(qry)
|
675
|
+
rescue Exception => e
|
676
|
+
stmt.drop unless stmt.nil?
|
677
|
+
@logger.unknown("exception=#{e}") if @@trace && name != :force_error
|
678
|
+
raise StatementInvalid, e.message
|
679
|
+
end
|
680
|
+
|
681
|
+
rColDescs = stmt.columns(true)
|
682
|
+
|
683
|
+
# Get the rows, handling any offset and/or limit stipulated
|
684
|
+
if scrollableCursor then
|
685
|
+
rRows = nil
|
686
|
+
# scrollableCursor == true => offset > 0
|
687
|
+
if stmt.fetch_scroll(ODBC::SQL_FETCH_ABSOLUTE, offset)
|
688
|
+
rRows = limit > 0 ? stmt.fetch_many(limit) : stmt.fetch_all
|
689
|
+
end
|
690
|
+
else
|
691
|
+
rRows = limit > 0 ? stmt.fetch_many(offset + limit) : stmt.fetch_all
|
692
|
+
# Enforce OFFSET
|
693
|
+
if offset > 0 then
|
694
|
+
if rRows && rRows.length > offset then
|
695
|
+
rRows.slice!(0, offset)
|
696
|
+
else
|
697
|
+
rRows = nil
|
698
|
+
end
|
699
|
+
end
|
700
|
+
# Enforce LIMIT
|
701
|
+
if limit > 0 && rRows && rRows.length > limit then
|
702
|
+
rRows.slice!(limit..(rRows.length-1))
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
# Convert rows from arrays to hashes
|
707
|
+
if rRows
|
708
|
+
rRows.each do |row|
|
709
|
+
h = Hash.new
|
710
|
+
(0...row.length).each do |iCol|
|
711
|
+
h[activeRecIdentCase(rColDescs[iCol].name)] =
|
712
|
+
convertOdbcValToGenericVal(row[iCol])
|
713
|
+
end
|
714
|
+
retVal << h
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
718
|
+
stmt.drop
|
719
|
+
retVal
|
720
|
+
end
|
721
|
+
|
722
|
+
# Returns a record hash with the column names as keys and column values
|
723
|
+
# as values.
|
724
|
+
def select_one(sql, name = nil)
|
725
|
+
@logger.unknown("ODBCAdapter#select_one>") if @@trace
|
726
|
+
@logger.unknown("args=[#{sql}|#{name}]") if @@trace
|
727
|
+
retVal = nil
|
728
|
+
scrollableCursor = false
|
729
|
+
offset = 0
|
730
|
+
qry = sql.dup
|
731
|
+
|
732
|
+
# Strip OFFSET and LIMIT from query if present, since ODBC doesn't
|
733
|
+
# support them in a generic form.
|
734
|
+
#
|
735
|
+
# TODO: Translate any OFFSET/LIMIT option to native SQL if DBMS supports it.
|
736
|
+
# This will perform much better than simulating them.
|
737
|
+
if qry =~ /(\bLIMIT\s+)(\d+)/i then
|
738
|
+
# Check for 'LIMIT 0' otherwise ignore LIMIT
|
739
|
+
if $2.to_i == 0 then return retVal end
|
740
|
+
end
|
741
|
+
|
742
|
+
if qry =~ /(\bOFFSET\s+)(\d+)/i then offset = $2.to_i end
|
743
|
+
qry.gsub!(/(\bLIMIT\s+\d+|\bOFFSET\s+\d+)/i, '')
|
744
|
+
|
745
|
+
# It's been assumed that it's quicker to support an offset
|
746
|
+
# restriction using a forward-only cursor. A static cursor will
|
747
|
+
# presumably take a snapshot of the whole result set, whereas when
|
748
|
+
# using a forward-only cursor we only fetch the first offset+1
|
749
|
+
# rows.
|
750
|
+
=begin
|
751
|
+
if offset > 0 then
|
752
|
+
scrollableCursor = true
|
753
|
+
begin
|
754
|
+
# ODBCStatement::fetch_first requires a scrollable cursor
|
755
|
+
@connection.cursortype = ODBC::SQL_CURSOR_STATIC
|
756
|
+
rescue
|
757
|
+
# Assume ODBC driver doesn't support scrollable cursors
|
758
|
+
@connection.cursortype = ODBC::SQL_CURSOR_FORWARD_ONLY
|
759
|
+
scrollableCursor = false
|
760
|
+
end
|
761
|
+
end
|
762
|
+
=end
|
763
|
+
# Execute the query
|
764
|
+
begin
|
765
|
+
stmt = @connection.run(qry)
|
766
|
+
rescue Exception => e
|
767
|
+
@logger.unknown("exception=#{e}") if @@trace
|
768
|
+
stmt.drop unless stmt.nil?
|
769
|
+
raise StatementInvalid, e.message
|
770
|
+
end
|
771
|
+
|
772
|
+
# Get one row, handling any offset stipulated
|
773
|
+
rColDescs = stmt.columns(true)
|
774
|
+
if scrollableCursor then
|
775
|
+
# scrollableCursor == true => offset > 0
|
776
|
+
stmt.fetch_scroll(ODBC::SQL_FETCH_ABSOLUTE, offset)
|
777
|
+
row = stmt.fetch
|
778
|
+
else
|
779
|
+
row = nil
|
780
|
+
rRows = stmt.fetch_many(offset + 1)
|
781
|
+
if rRows && rRows.length > offset then
|
782
|
+
row = rRows[offset]
|
783
|
+
end
|
784
|
+
end
|
785
|
+
|
786
|
+
# Convert row from array to hash
|
787
|
+
if row then
|
788
|
+
retVal = h = Hash.new
|
789
|
+
(0...row.length).each do |iCol|
|
790
|
+
h[activeRecIdentCase(rColDescs[iCol].name)] =
|
791
|
+
convertOdbcValToGenericVal(row[iCol])
|
792
|
+
end
|
793
|
+
end
|
794
|
+
|
795
|
+
stmt.drop
|
796
|
+
retVal
|
797
|
+
end
|
798
|
+
|
799
|
+
# Executes the SQL statement in the context of this connection.
|
800
|
+
# Returns the number of rows affected.
|
801
|
+
def execute(sql, name = nil)
|
802
|
+
@logger.unknown("ODBCAdapter#execute>") if @@trace
|
803
|
+
@logger.unknown("args=[#{sql}|#{name}]") if @@trace
|
804
|
+
if sql =~ /^\s*INSERT/i &&
|
805
|
+
[:microsoftsqlserver, :virtuoso, :sybase].include?(@dbmsName)
|
806
|
+
# Guard against IDENTITY insert problems caused by explicit inserts
|
807
|
+
# into autoincrementing id column.
|
808
|
+
insert(sql, name)
|
809
|
+
else
|
810
|
+
begin
|
811
|
+
@connection.do(sql)
|
812
|
+
rescue Exception => e
|
813
|
+
@logger.unknown("exception=#{e}") if @@trace
|
814
|
+
raise StatementInvalid, e.message
|
815
|
+
end
|
816
|
+
end
|
817
|
+
end
|
818
|
+
|
819
|
+
alias_method :delete, :execute
|
820
|
+
alias_method :update, :execute
|
821
|
+
|
822
|
+
# Returns the ID of the last inserted row.
|
823
|
+
def insert(sql, name = nil, pk = nil, id_value = nil,
|
824
|
+
sequence_name = nil)
|
825
|
+
@logger.unknown("ODBCAdapter#insert>") if @@trace
|
826
|
+
@logger.unknown("args=[#{sql}|#{name}|#{pk}|#{id_value}|#{sequence_name}]") if @@trace
|
827
|
+
# id_value ::= pre-assigned id
|
828
|
+
retry_count = 0
|
829
|
+
begin
|
830
|
+
pre_insert(sql, name, pk, id_value, sequence_name) if respond_to?("pre_insert")
|
831
|
+
stmt = @connection.run(sql)
|
832
|
+
table = sql.split(" ", 4)[2]
|
833
|
+
res = id_value || last_insert_id(table, sequence_name ||
|
834
|
+
default_sequence_name(table, pk), stmt)
|
835
|
+
rescue Exception => e
|
836
|
+
@logger.unknown("exception=#{e}") if @@trace
|
837
|
+
if @dbmsName == :virtuoso && id_value.nil? && e.message =~ /sr197/i
|
838
|
+
# Error: Non unique primary key
|
839
|
+
# If id column is an autoincrementing IDENTITY column and there
|
840
|
+
# have been prior inserts using explicit id's, the sequence
|
841
|
+
# associated with the id column could lag behind the id values
|
842
|
+
# inserted explicitly. In the course of subsequent inserts, if
|
843
|
+
# an explicit id isn't given, the autogenerated id may collide
|
844
|
+
# with a previously explicitly inserted value.
|
845
|
+
unless stmt.nil?
|
846
|
+
stmt.drop; stmt = nil
|
847
|
+
end
|
848
|
+
table_name = e.message =~/Non unique primary key on (\w+\.\w+\.\w+)/i ? $1 : nil
|
849
|
+
if table_name && retry_count == 0
|
850
|
+
retry_count += 1
|
851
|
+
# Set next sequence value to be greater than current max. pk value
|
852
|
+
set_sequence(table_name, pk)
|
853
|
+
retry
|
854
|
+
end
|
855
|
+
end
|
856
|
+
raise StatementInvalid, e.message
|
857
|
+
ensure
|
858
|
+
post_insert(sql, name, pk, id_value, sequence_name) if respond_to?("post_insert")
|
859
|
+
stmt.drop unless stmt.nil?
|
860
|
+
end
|
861
|
+
res
|
862
|
+
end
|
863
|
+
|
864
|
+
# Returns the default sequence name for a table.
|
865
|
+
# Used for databases which don't support an autoincrementing column
|
866
|
+
# type, but do support sequences.
|
867
|
+
def default_sequence_name(table, primary_key=nil)
|
868
|
+
@logger.unknown("ODBCAdapter#default_sequence_name>") if @@trace
|
869
|
+
@logger.unknown("args=[#{table}|#{primary_key}]") if @@trace
|
870
|
+
"#{table}_seq"
|
871
|
+
end
|
872
|
+
|
873
|
+
# Set the sequence to the max value of the table�s column.
|
874
|
+
def reset_sequence!(table, column, sequence = nil)
|
875
|
+
@logger.unknown("ODBCAdapter#reset_sequence!>") if @@trace
|
876
|
+
@logger.unknown("args=[#{table}|#{column}|#{sequence}]") if @@trace
|
877
|
+
super(table, column, sequence)
|
878
|
+
rescue Exception => e
|
879
|
+
@logger.unknown("exception=#{e}") if @@trace
|
880
|
+
raise ActiveRecordError, e.message
|
881
|
+
end
|
882
|
+
|
883
|
+
#--
|
884
|
+
# SCHEMA STATEMENTS OVERRIDES ======================================
|
885
|
+
#
|
886
|
+
# see: abstract/schema_statements.rb
|
887
|
+
|
888
|
+
# Implemented by MySQL and SQL Server adapters
|
889
|
+
#def create_database(name)
|
890
|
+
#end
|
891
|
+
|
892
|
+
# Implemented by MySQL, SQL Server and Oracle adapters
|
893
|
+
#def current_database
|
894
|
+
#end
|
895
|
+
|
896
|
+
# Implemented by MySQL and SQL Server adapters
|
897
|
+
#drop_database(name)
|
898
|
+
#end
|
899
|
+
|
900
|
+
# The maximum length a table alias can be.
|
901
|
+
def table_alias_length
|
902
|
+
maxIdentLen = @dsInfo.info[ODBC::SQL_MAX_IDENTIFIER_LEN]
|
903
|
+
maxTblNameLen = @dsInfo.info[ODBC::SQL_MAX_TABLE_NAME_LEN]
|
904
|
+
maxTblNameLen < maxIdentLen ? maxTblNameLen : maxIdentLen
|
905
|
+
end
|
906
|
+
|
907
|
+
# Returns an array of table names, for database tables visible on the
|
908
|
+
# current connection.
|
909
|
+
def tables(name = nil)
|
910
|
+
@logger.unknown("ODBCAdapter#tables>") if @@trace
|
911
|
+
@logger.unknown("args=[#{name}]") if @@trace
|
912
|
+
tblNames = []
|
913
|
+
# TODO: ODBC::Connection#tables cannot filter on schema name
|
914
|
+
# Modify Werner's Ruby ODBC driver to allow this
|
915
|
+
currentUser = @dsInfo.info[ODBC::SQL_USER_NAME]
|
916
|
+
stmt = @connection.tables
|
917
|
+
resultSet = stmt.fetch_all || []
|
918
|
+
resultSet.each do |row|
|
919
|
+
if @@dbmsLookups.get_info(@dbmsName, @dbmsMajorVer, :supports_schema_names)
|
920
|
+
tblNames << activeRecIdentCase(row[2]) if row[1].casecmp(currentUser) == 0
|
921
|
+
else
|
922
|
+
tblNames << activeRecIdentCase(row[2])
|
923
|
+
end
|
924
|
+
end
|
925
|
+
stmt.drop
|
926
|
+
tblNames
|
927
|
+
rescue Exception => e
|
928
|
+
@logger.unknown("exception=#{e}") if @@trace
|
929
|
+
raise ActiveRecordError, e.message
|
930
|
+
end
|
931
|
+
|
932
|
+
# Returns an array of Column objects for the table specified by +table_name+.
|
933
|
+
def columns(table_name, name = nil)
|
934
|
+
@logger.unknown("ODBCAdapter#columns>") if @@trace
|
935
|
+
@logger.unknown("args=[#{table_name}|#{name}]") if @@trace
|
936
|
+
|
937
|
+
table_name = table_name.to_s if table_name.class == Symbol
|
938
|
+
|
939
|
+
getDbTypeInfo
|
940
|
+
cols = []
|
941
|
+
stmt = @connection.columns(dbmsIdentCase(table_name))
|
942
|
+
resultSet = stmt.fetch_all || []
|
943
|
+
resultSet.each do |col|
|
944
|
+
colName = col[3] # SQLColumns: COLUMN_NAME
|
945
|
+
colDefault = col[12] # SQLColumns: COLUMN_DEF
|
946
|
+
colSqlType = col[4] # SQLColumns: DATA_TYPE
|
947
|
+
colNativeType = col[5] # SQLColumns: TYPE_NAME
|
948
|
+
colLimit = col[6] # SQLColumns: COLUMN_SIZE
|
949
|
+
|
950
|
+
odbcIsNullable = col[17] # SQLColumns: IS_NULLABLE
|
951
|
+
odbcNullable = col[10] # SQLColumns: NULLABLE
|
952
|
+
# isNotNullable == true => *definitely not* nullable
|
953
|
+
# == false => *may* be nullable
|
954
|
+
isNotNullable = (odbcIsNullable.match('NO') != nil)
|
955
|
+
# Assume column is nullable if odbcNullable == SQL_NULLABLE_UNKNOWN
|
956
|
+
colNullable = !(isNotNullable || odbcNullable == SQL_NO_NULLS)
|
957
|
+
|
958
|
+
# HACK!
|
959
|
+
# MySQL native ODBC driver doesn't report nullability accurately.
|
960
|
+
# So force nullability of 'id' columns
|
961
|
+
colNullable = false if colName == 'id'
|
962
|
+
|
963
|
+
# SQL Server ODBC drivers may wrap default value in parentheses
|
964
|
+
if colDefault =~ /^\('(.*)'\)$/ # SQL Server character default
|
965
|
+
colDefault = $1
|
966
|
+
elsif colDefault =~ /^\((.*)\)$/ # SQL Server numeric default
|
967
|
+
colDefault = $1
|
968
|
+
# ODBC drivers should return string column defaults in quotes
|
969
|
+
# Oracle also includes a trailing space.
|
970
|
+
elsif colDefault =~ /^'(.*)' *$/
|
971
|
+
colDefault = $1
|
972
|
+
#TODO: HACKS for Progress
|
973
|
+
elsif @dbmsName == :progress || @dbmsName == :progress89
|
974
|
+
if colDefault =~ /^\?$/
|
975
|
+
colDefault = nil
|
976
|
+
elsif colSqlType == ODBC::SQL_BIT
|
977
|
+
if ["yes", "no"].include?(colDefault)
|
978
|
+
colDefault = colDefault == "yes" ? 1 : 0
|
979
|
+
end
|
980
|
+
end
|
981
|
+
end
|
982
|
+
cols << ODBCColumn.new(activeRecIdentCase(colName), table_name,
|
983
|
+
colDefault, colSqlType, colNativeType, colNullable, colLimit,
|
984
|
+
@odbcExtFile+"_col", @typeInfo)
|
985
|
+
end
|
986
|
+
stmt.drop
|
987
|
+
cols
|
988
|
+
rescue Exception => e
|
989
|
+
@logger.unknown("exception=#{e}") if @@trace
|
990
|
+
raise ActiveRecordError, e.message
|
991
|
+
end
|
992
|
+
|
993
|
+
# Returns an array of indexes for the given table.
|
994
|
+
def indexes(table_name, name = nil)
|
995
|
+
@logger.unknown("ODBCAdapter#indexes>") if @@trace
|
996
|
+
@logger.unknown("args=[#{table_name}|#{name}]") if @@trace
|
997
|
+
|
998
|
+
indexes = []
|
999
|
+
indexCols = indexName = isUnique = nil
|
1000
|
+
|
1001
|
+
stmt = @connection.indexes(dbmsIdentCase(table_name))
|
1002
|
+
rs = stmt.fetch_all || []
|
1003
|
+
rs.each_index do |iRow|
|
1004
|
+
row = rs[iRow]
|
1005
|
+
|
1006
|
+
# Skip table statistics
|
1007
|
+
next if row[6] == 0 # SQLStatistics: TYPE
|
1008
|
+
|
1009
|
+
if (row[7] == 1) # SQLStatistics: ORDINAL_POSITION
|
1010
|
+
# Start of column descriptor block for next index
|
1011
|
+
indexCols = Array.new
|
1012
|
+
isUnique = (row[3] == 0) # SQLStatistics: NON_UNIQUE
|
1013
|
+
indexName = String.new(row[5]) # SQLStatistics: INDEX_NAME
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
indexCols << activeRecIdentCase(row[8]) # SQLStatistics: COLUMN_NAME
|
1017
|
+
|
1018
|
+
lastRow = (iRow == rs.length - 1)
|
1019
|
+
if lastRow
|
1020
|
+
lastColOfIndex = true
|
1021
|
+
else
|
1022
|
+
nextRow = rs[iRow + 1]
|
1023
|
+
lastColOfIndex = (nextRow[6] == 0 || nextRow[7] == 1)
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
if lastColOfIndex
|
1027
|
+
indexes << IndexDefinition.new(table_name,
|
1028
|
+
activeRecIdentCase(indexName), isUnique, indexCols)
|
1029
|
+
end
|
1030
|
+
end
|
1031
|
+
indexes
|
1032
|
+
rescue Exception => e
|
1033
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1034
|
+
raise ActiveRecordError, e.message
|
1035
|
+
ensure
|
1036
|
+
stmt.drop unless stmt.nil?
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
# Returns a Hash of mappings from Rails' abstract data types to the
|
1040
|
+
# native database types.
|
1041
|
+
# See TableDefinition#column for details of the abstract data types.
|
1042
|
+
def native_database_types
|
1043
|
+
@logger.unknown("ODBCAdapter#native_database_types>") if @@trace
|
1044
|
+
|
1045
|
+
return {}.merge(@abstract2NativeTypeMap) unless @abstract2NativeTypeMap.nil?
|
1046
|
+
|
1047
|
+
@abstract2NativeTypeMap =
|
1048
|
+
{
|
1049
|
+
:primary_key => nil,
|
1050
|
+
:string => nil,
|
1051
|
+
:text => nil,
|
1052
|
+
:integer => nil,
|
1053
|
+
:float => nil,
|
1054
|
+
:datetime => nil,
|
1055
|
+
:timestamp => nil,
|
1056
|
+
:time => nil,
|
1057
|
+
:date => nil,
|
1058
|
+
:binary => nil,
|
1059
|
+
:boolean => nil
|
1060
|
+
}
|
1061
|
+
|
1062
|
+
getDbTypeInfo
|
1063
|
+
|
1064
|
+
# hAbs2Sql = Hash of ActiveRecord abstract types to ODBC SQL types
|
1065
|
+
hAbs2Sql = genericTypeToOdbcSqlTypesMap
|
1066
|
+
|
1067
|
+
# hSql2Native = Hash of ODBC native data type descriptors from
|
1068
|
+
# SQLGetTypeInfo keyed on ODBC SQL type.
|
1069
|
+
# The hash value is an array of all rows in the SQLGetTypeInfo result
|
1070
|
+
# set for which DATA_TYPE matches the key.
|
1071
|
+
hSql2Native = Hash.new
|
1072
|
+
@typeInfo.each do |row|
|
1073
|
+
sqlType = row[1] # SQLGetTypeInfo: DATA_TYPE
|
1074
|
+
if (rNativeTypeDescs = hSql2Native[sqlType]) == nil
|
1075
|
+
hSql2Native[sqlType] = rNativeTypeDescs = Array.new()
|
1076
|
+
end
|
1077
|
+
rNativeTypeDescs << row
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
# For a particular abstract type, check if the DBMS supports one of
|
1081
|
+
# the corresponding ODBC SQL types then, if so, find the native DBMS
|
1082
|
+
# types corresponding to this ODBC SQL type and select the most
|
1083
|
+
# suitable. (For each SQL type, SQLGetTypeInfo should return the
|
1084
|
+
# closest match first).
|
1085
|
+
@abstract2NativeTypeMap.each_key do |abstractType|
|
1086
|
+
rCandidateSqlTypes = hAbs2Sql[abstractType]
|
1087
|
+
isSupported = false
|
1088
|
+
rCandidateSqlTypes.each do |sqlType|
|
1089
|
+
if (rNativeTypeDescs = hSql2Native[sqlType])
|
1090
|
+
@abstract2NativeTypeMap[abstractType] =
|
1091
|
+
nativeTypeMapping(abstractType, rNativeTypeDescs)
|
1092
|
+
isSupported = true
|
1093
|
+
break
|
1094
|
+
end
|
1095
|
+
end
|
1096
|
+
@logger.unknown("WARNING: No suitable DBMS type for abstract type #{abstractType.to_s}") if !isSupported && @@trace
|
1097
|
+
end
|
1098
|
+
{}.merge(@abstract2NativeTypeMap)
|
1099
|
+
rescue Exception => e
|
1100
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1101
|
+
raise ActiveRecordError, e.message
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
# Creates a new table. See SchemaStatements#create_table.
|
1105
|
+
def create_table(name, options = {})
|
1106
|
+
@logger.unknown("ODBCAdapter#create_table>") if @@trace
|
1107
|
+
@logger.unknown("args=[#{name}]") if @@trace
|
1108
|
+
super(name, options)
|
1109
|
+
rescue Exception => e
|
1110
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1111
|
+
raise ActiveRecordError, e.message
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
# Renames a table.
|
1115
|
+
def rename_table(name, new_name)
|
1116
|
+
@logger.unknown("ODBCAdapter#rename_table>") if @@trace
|
1117
|
+
@logger.unknown("args=[#{name}|#{new_name}]") if @@trace
|
1118
|
+
# Base class raises NotImplementedError
|
1119
|
+
super(name, new_name)
|
1120
|
+
rescue Exception => e
|
1121
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1122
|
+
raise ActiveRecordError, e.message
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
# Drops a table from the database.
|
1126
|
+
def drop_table(name)
|
1127
|
+
@logger.unknown("ODBCAdapter#drop_table>") if @@trace
|
1128
|
+
@logger.unknown("args=[#{name}]") if @@trace
|
1129
|
+
super(name)
|
1130
|
+
rescue Exception => e
|
1131
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1132
|
+
raise ActiveRecordError, e.message
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
# Adds a new column to the named table.
|
1136
|
+
# See TableDefinition#column for details of the options you can use.
|
1137
|
+
def add_column(table_name, column_name, type, options = {})
|
1138
|
+
@logger.unknown("ODBCAdapter#add_column>") if @@trace
|
1139
|
+
@logger.unknown("args=[#{table_name}|#{column_name}|#{type}]") if @@trace
|
1140
|
+
super(table_name, column_name, type, options)
|
1141
|
+
rescue Exception => e
|
1142
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1143
|
+
raise ActiveRecordError, e.message
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
# Removes the column from the table definition.
|
1147
|
+
def remove_column(table_name, column_name)
|
1148
|
+
@logger.unknown("ODBCAdapter#remove_column>") if @@trace
|
1149
|
+
@logger.unknown("args=[#{table_name}|#{column_name}]") if @@trace
|
1150
|
+
super(table_name, column_name)
|
1151
|
+
rescue Exception => e
|
1152
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1153
|
+
raise ActiveRecordError, e.message
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
# Changes the column�s definition according to the new options.
|
1157
|
+
# See TableDefinition#column for details of the options you can use.
|
1158
|
+
def change_column(table_name, column_name, type, options = {})
|
1159
|
+
@logger.unknown("ODBCAdapter#change_column>") if @@trace
|
1160
|
+
@logger.unknown("args=[#{table_name}|#{column_name}|#{type}]") if @@trace
|
1161
|
+
# Base class raises NotImplementedError
|
1162
|
+
super(table_name, column_name, type, options)
|
1163
|
+
rescue Exception => e
|
1164
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1165
|
+
raise ActiveRecordError, e.message
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
# Sets a new default value for a column.
|
1169
|
+
def change_column_default(table_name, column_name, default)
|
1170
|
+
@logger.unknown("ODBCAdapter#change_column_default>") if @@trace
|
1171
|
+
@logger.unknown("args=[#{table_name}|#{column_name}]") if @@trace
|
1172
|
+
super(table_name, column_name, default)
|
1173
|
+
rescue Exception => e
|
1174
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1175
|
+
raise ActiveRecordError, e.message
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
def rename_column(table_name, column_name, new_column_name)
|
1179
|
+
@logger.unknown("ODBCAdapter#rename_column>") if @@trace
|
1180
|
+
@logger.unknown("args=[#{table_name}|#{column_name}|#{new_column_name}]") if @@trace
|
1181
|
+
# Base class raises NotImplementedError
|
1182
|
+
super(table_name, column_name, new_column_name)
|
1183
|
+
rescue Exception => e
|
1184
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1185
|
+
raise ActiveRecordError, e.message
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
def remove_index(table_name, options = {})
|
1189
|
+
@logger.unknown("ODBCAdapter#remove_index>") if @@trace
|
1190
|
+
@logger.unknown("args=[#{table_name}]") if @@trace
|
1191
|
+
super(table_name, options)
|
1192
|
+
rescue Exception => e
|
1193
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1194
|
+
raise ActiveRecordError, e.message
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
# Not exercised by ActiveRecord test suite
|
1198
|
+
def structure_dump # :nodoc:
|
1199
|
+
@logger.unknown("ODBCAdapter#structure_dump>") if @@trace
|
1200
|
+
raise NotImplementedError, "structure_dump is not implemented"
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
#--
|
1204
|
+
# WRAPPER METHODS FOR TRACING ======================================
|
1205
|
+
|
1206
|
+
#--
|
1207
|
+
# ------------------------------------------------------------------
|
1208
|
+
# see: abstract/database_statements.rb
|
1209
|
+
|
1210
|
+
# Returns a single value from a record
|
1211
|
+
#--
|
1212
|
+
# No need to implement beyond a tracing wrapper
|
1213
|
+
def select_value(sql, name = nil)
|
1214
|
+
@logger.unknown("ODBCAdapter#select_value>") if @@trace
|
1215
|
+
@logger.unknown("args=[#{sql}|#{name}]") if @@trace
|
1216
|
+
super(sql, name)
|
1217
|
+
rescue Exception => e
|
1218
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1219
|
+
raise StatementInvalid, e.message
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
# Returns an array of the values of the first column in a select.
|
1223
|
+
#--
|
1224
|
+
# No need to implement beyond a tracing wrapper
|
1225
|
+
def select_values(sql, name = nil)
|
1226
|
+
@logger.unknown("ODBCAdapter#select_values>") if @@trace
|
1227
|
+
@logger.unknown("args=[#{sql}|#{name}]") if @@trace
|
1228
|
+
super(sql, name)
|
1229
|
+
rescue Exception => e
|
1230
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1231
|
+
raise StatementInvalid, e.message
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
# Wrap a block in a transaction. Returns result of block.
|
1235
|
+
#--
|
1236
|
+
# No need to implement beyond a tracing wrapper
|
1237
|
+
def transaction(start_db_transaction = true)
|
1238
|
+
@logger.unknown("ODBCAdapter#transaction>") if @@trace
|
1239
|
+
super(start_db_transaction)
|
1240
|
+
rescue Exception => e
|
1241
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1242
|
+
raise ActiveRecordError, e.message
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
# Alias for #add_limit_offset!
|
1246
|
+
#--
|
1247
|
+
# No need to implement beyond a tracing wrapper
|
1248
|
+
def add_limit!(sql, options)
|
1249
|
+
@logger.unknown("ODBCAdapter#add_limit!>") if @@trace
|
1250
|
+
@logger.unknown("args=[#{sql}]") if @@trace
|
1251
|
+
super(sql, options)
|
1252
|
+
rescue Exception => e
|
1253
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1254
|
+
raise ActiveRecordError, e.message
|
1255
|
+
end
|
1256
|
+
|
1257
|
+
#--
|
1258
|
+
# ------------------------------------------------------------------
|
1259
|
+
# see: abstract/schema_statements.rb
|
1260
|
+
|
1261
|
+
# Adds a new index to the table.
|
1262
|
+
# See SchemaStatements#add_index.
|
1263
|
+
#--
|
1264
|
+
# No need to implement beyond a tracing wrapper
|
1265
|
+
def add_index(table_name, column_name, options = {})
|
1266
|
+
@logger.unknown("ODBCAdapter#add_index>") if @@trace
|
1267
|
+
@logger.unknown("args=[#{table_name}|#{column_name}]") if @@trace
|
1268
|
+
super(table_name, column_name, options)
|
1269
|
+
rescue Exception => e
|
1270
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1271
|
+
raise ActiveRecordError, e.message
|
1272
|
+
end
|
1273
|
+
|
1274
|
+
#--
|
1275
|
+
# Ensures index names constructed from the table name and a column
|
1276
|
+
# name do not exceed the maximum index name length supported by the
|
1277
|
+
# DBMS. It's assumed SQL_MAX_IDENTIFIER_LENGTH reflects the maximum
|
1278
|
+
# index name length.
|
1279
|
+
def index_name(table_name, options) # :nodoc:
|
1280
|
+
@logger.unknown("ODBCAdapter#index_name>") if @@trace
|
1281
|
+
@logger.unknown("args=[#{table_name}]") if @@trace
|
1282
|
+
maxIndxNameLen = @dsInfo.info[ODBC::SQL_MAX_IDENTIFIER_LEN]
|
1283
|
+
if Hash === options # legacy support
|
1284
|
+
if options[:name]
|
1285
|
+
return options[:name] # error if name length > max index length
|
1286
|
+
elsif options[:column]
|
1287
|
+
col_name = options[:column]
|
1288
|
+
else
|
1289
|
+
raise ArgumentError, "You must specify the index name"
|
1290
|
+
end
|
1291
|
+
else
|
1292
|
+
col_name = options
|
1293
|
+
end
|
1294
|
+
idx_name = "#{table_name}_#{col_name}_index"
|
1295
|
+
if idx_name.length > maxIndxNameLen
|
1296
|
+
cMax = ((maxIndxNameLen - 5)/2).to_i
|
1297
|
+
idx_name = "#{table_name.to_s[0,cMax]}_#{col_name.to_s[0,cMax]}_idx"
|
1298
|
+
end
|
1299
|
+
idx_name
|
1300
|
+
rescue Exception => e
|
1301
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1302
|
+
raise ActiveRecordError, e.message
|
1303
|
+
end
|
1304
|
+
|
1305
|
+
def type_to_sql(type, limit = nil) # :nodoc:
|
1306
|
+
@logger.unknown("ODBCAdapter#type_to_sql>") if @@trace
|
1307
|
+
@logger.unknown("args=[#{type}|#{limit}]") if @@trace
|
1308
|
+
native = native_database_types[type]
|
1309
|
+
column_type_sql = String.new(type == :primary_key ? native : native[:name])
|
1310
|
+
# if there's no limit in the type definition, assume that the type
|
1311
|
+
# doesn't support a length qualifier
|
1312
|
+
column_type_sql << "(#{limit || native[:limit]})" if native[:limit]
|
1313
|
+
column_type_sql
|
1314
|
+
rescue Exception => e
|
1315
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1316
|
+
raise ActiveRecordError, e.message
|
1317
|
+
end
|
1318
|
+
|
1319
|
+
# No need to implement beyond tracing wrapper
|
1320
|
+
def add_column_options!(sql, options) # :nodoc:
|
1321
|
+
@logger.unknown("ODBCAdapter#add_column_options!>") if @@trace
|
1322
|
+
@logger.unknown("args=[#{sql}]") if @@trace
|
1323
|
+
super(sql, options)
|
1324
|
+
rescue Exception => e
|
1325
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1326
|
+
raise StatementInvalid, e.message
|
1327
|
+
end
|
1328
|
+
|
1329
|
+
# No need to implement beyond tracing wrapper
|
1330
|
+
def dump_schema_information # :nodoc:
|
1331
|
+
@logger.unknown("ODBCAdapter#dump_schema_information>") if @@trace
|
1332
|
+
super
|
1333
|
+
rescue Exception => e
|
1334
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1335
|
+
raise ActiveRecordError, e.message
|
1336
|
+
end
|
1337
|
+
|
1338
|
+
# ==================================================================
|
1339
|
+
|
1340
|
+
private
|
1341
|
+
|
1342
|
+
# Maps a DBMS name to a symbol.
|
1343
|
+
#
|
1344
|
+
# Different ODBC drivers might return different names for the same
|
1345
|
+
# DBMS. So #dbmsNameToSym maps similar names to the same symbol.
|
1346
|
+
#
|
1347
|
+
# If adding an odbcext_xxx extension module for a particular DBMS,
|
1348
|
+
# you should define a symbol here for the target DBMS.
|
1349
|
+
#
|
1350
|
+
# dbmsName is the SQL_DBMS_NAME returned by ODBC, downcased with
|
1351
|
+
# whitespace removed.
|
1352
|
+
def dbmsNameToSym(dbmsName, dbmsVer)
|
1353
|
+
if dbmsName =~ /db2/i
|
1354
|
+
symbl = :db2
|
1355
|
+
elsif dbmsName =~ /informix/i
|
1356
|
+
symbl = :informix
|
1357
|
+
elsif dbmsName =~ /ingres/i
|
1358
|
+
symbl = :ingres
|
1359
|
+
elsif dbmsName =~ /my.*sql/i
|
1360
|
+
symbl = :mysql
|
1361
|
+
elsif dbmsName =~ /oracle/i
|
1362
|
+
symbl = :oracle
|
1363
|
+
elsif dbmsName =~ /postgres/i
|
1364
|
+
symbl = :postgresql
|
1365
|
+
elsif dbmsName =~ /progress/i
|
1366
|
+
# ODBC connections to Progress >= v9 are assumed to be to
|
1367
|
+
# the SQL-92 engine. Connections to Progress <= v8 are
|
1368
|
+
# assumed to be to the SQL-89 engine.
|
1369
|
+
symbl = dbmsVer <= 8 ? :progress89 : :progress
|
1370
|
+
elsif dbmsName =~ /sql.*server/i
|
1371
|
+
symbl = :microsoftsqlserver
|
1372
|
+
elsif dbmsName =~ /sybase/i
|
1373
|
+
symbl = :sybase
|
1374
|
+
elsif dbmsName =~ /virtuoso/i
|
1375
|
+
symbl = :virtuoso
|
1376
|
+
else
|
1377
|
+
raise ActiveRecord::ActiveRecordError, "ODBCAdapter: Unsupported database (#{dbmsName})"
|
1378
|
+
end
|
1379
|
+
symbl
|
1380
|
+
end
|
1381
|
+
|
1382
|
+
# Returns a Hash of mappings for each ActiveRecord abstract data type to
|
1383
|
+
# one or more ODBC SQL types
|
1384
|
+
#
|
1385
|
+
# Where more than one ODBC SQL type is associated with an abstract type,
|
1386
|
+
# the SQL types in the value array are in order of preference.
|
1387
|
+
def genericTypeToOdbcSqlTypesMap
|
1388
|
+
map =
|
1389
|
+
{
|
1390
|
+
:primary_key => [ODBC::SQL_INTEGER, ODBC::SQL_SMALLINT],
|
1391
|
+
:string => [ODBC::SQL_VARCHAR],
|
1392
|
+
:text => [ODBC::SQL_LONGVARCHAR, ODBC::SQL_VARCHAR],
|
1393
|
+
:integer => [ODBC::SQL_INTEGER, ODBC::SQL_SMALLINT],
|
1394
|
+
:float => [ODBC::SQL_DOUBLE, ODBC::SQL_REAL],
|
1395
|
+
:datetime => [ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP],
|
1396
|
+
:timestamp => [ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP],
|
1397
|
+
:time => [ODBC::SQL_TYPE_TIME, ODBC::SQL_TIME,
|
1398
|
+
ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP],
|
1399
|
+
:date => [ODBC::SQL_TYPE_DATE, ODBC::SQL_DATE,
|
1400
|
+
ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP],
|
1401
|
+
:binary => [ ODBC::SQL_LONGVARBINARY, ODBC::SQL_VARBINARY],
|
1402
|
+
:boolean => [ODBC::SQL_BIT, ODBC::SQL_TINYINT, ODBC::SQL_SMALLINT,
|
1403
|
+
ODBC::SQL_INTEGER]
|
1404
|
+
}
|
1405
|
+
|
1406
|
+
# MySQL:
|
1407
|
+
# Mapping of :boolean to ODBC::SQL_BIT is removed because it does not
|
1408
|
+
# work with the BIT datatype in MySQL 5.0.3 or later.
|
1409
|
+
# - Prior to MySQL 5.0.3: BIT was a synonym for TINYINT(1).
|
1410
|
+
# - MySQL 5.0.3: BIT datatype is supported only for MyISAM tables,
|
1411
|
+
# not InnoDB tables (which ActiveRecord requires for transaction
|
1412
|
+
# support).
|
1413
|
+
# - Ruby ODBC Bridge attempts to fetch SQL_BIT column to SQL_C_LONG.
|
1414
|
+
# With MySQL ODBC driver (3.51.12)
|
1415
|
+
# - 'select b from ...' returns 0 for a bit value of 0x1
|
1416
|
+
# - 'select hex(b) from ...' returns 1 for a bit value of 0x1
|
1417
|
+
if @dbmsName == :mysql
|
1418
|
+
map[:boolean].delete(ODBC::SQL_BIT) { raise ActiveRecordError, "SQL_BIT not found" }
|
1419
|
+
end
|
1420
|
+
|
1421
|
+
map
|
1422
|
+
end
|
1423
|
+
|
1424
|
+
# Creates a Hash describing a mapping from an abstract type to a
|
1425
|
+
# DBMS native type for use by #native_database_types
|
1426
|
+
#
|
1427
|
+
# rNativeTypeDescs = array of rows from SQLGetTypeInfo result set
|
1428
|
+
# all mapping to the same ODBC SQL type
|
1429
|
+
def nativeTypeMapping (abstractType, rNativeTypeDescs)
|
1430
|
+
res = {}
|
1431
|
+
if abstractType == :primary_key
|
1432
|
+
# The appropriate SQL for :primary_key is hard to derive as
|
1433
|
+
# ODBC doesn't provide any info on a DBMS's native syntax for
|
1434
|
+
# autoincrement columns. So we use a lookup instead.
|
1435
|
+
val = @@dbmsLookups.get_info(@dbmsName, @dbmsMajorVer, :primary_key)
|
1436
|
+
res = val
|
1437
|
+
else
|
1438
|
+
nativeTypeDesc = rNativeTypeDescs[0]
|
1439
|
+
# If more than one native type corresponds to the SQL type we're
|
1440
|
+
# handling, the type in the first descriptor should be the
|
1441
|
+
# best match, because the ODBC specification states that
|
1442
|
+
# SQLGetTypeInfo returns the results ordered by SQL type and then by
|
1443
|
+
# how closely the native type maps to that SQL type.
|
1444
|
+
# But, for :text and :binary, select the native type with the
|
1445
|
+
# largest capacity.
|
1446
|
+
if [:text, :binary].include?(abstractType)
|
1447
|
+
rNativeTypeDescs.each do |ntd|
|
1448
|
+
# Compare SQLGetTypeInfo:COLUMN_SIZE values
|
1449
|
+
nativeTypeDesc = ntd if nativeTypeDesc[2] < ntd[2]
|
1450
|
+
end
|
1451
|
+
end
|
1452
|
+
|
1453
|
+
res[:name] = nativeTypeDesc[0] # SQLGetTypeInfo: TYPE_NAME
|
1454
|
+
createParams = nativeTypeDesc[5]
|
1455
|
+
# CREATE_PARAMS is assumed to be a max_length specification.
|
1456
|
+
# The abstract types don't map to SQL_DECIMAL, SQL_NUMERIC or
|
1457
|
+
# SQL_FLOAT (#see genericTypeToOdbcSqlTypesMap), so we needn't worry
|
1458
|
+
# about these SQL types which take a precision and maybe a scale
|
1459
|
+
# creation parameter.
|
1460
|
+
if (createParams && createParams.strip.length > 0)
|
1461
|
+
unless @dbmsName == :db2 && ["BLOB", "CLOB"].include?(res[:name])
|
1462
|
+
# HACK:
|
1463
|
+
# Omit the :limit option for DB2's CLOB and BLOB types, as the
|
1464
|
+
# :limit value set from SQLGetTypeInfo(COL_SIZE) is 2GB.
|
1465
|
+
# The max. length for these types defaults to 1MB if the
|
1466
|
+
# length specifier is omitted.
|
1467
|
+
res[:limit] = nativeTypeDesc[2] # SQLGetTypeInfo: COL_SIZE
|
1468
|
+
end
|
1469
|
+
|
1470
|
+
# The max row length in Ingres is typically around 2008 bytes,
|
1471
|
+
# depending on the default page size.
|
1472
|
+
# Limit the reported max length of the native type which maps to
|
1473
|
+
# :string to 255, instead of the actual max length of 2000.
|
1474
|
+
# This is done to reduce the chances of add_column() exceeding
|
1475
|
+
# the maximum row length and Ingres returning an error.
|
1476
|
+
#
|
1477
|
+
# Similarly with DB2. The max row length is typically around 4005
|
1478
|
+
# bytes.
|
1479
|
+
#
|
1480
|
+
# Similarly with Sybase, reduce the max. :string length from 2000
|
1481
|
+
# to 255, to avoid add_index exceeding the max. allowed index size
|
1482
|
+
# of 1250 bytes when creating a composite index.
|
1483
|
+
res[:limit] = 255 if [:ingres, :sybase, :db2, :progress, :progress89].include?(@dbmsName) && abstractType == :string
|
1484
|
+
end
|
1485
|
+
end
|
1486
|
+
res
|
1487
|
+
end
|
1488
|
+
|
1489
|
+
def last_insert_id(table, sequence_name, stmt = nil)
|
1490
|
+
# This method must be overridden in module ODBCExt.
|
1491
|
+
# Each DBMS supported by this ODBCAdapter supplies its own version in
|
1492
|
+
# file vendor/odbcext_#{dbmsName}.rb
|
1493
|
+
raise NotImplementedError, "last_insert_id is an abstract method"
|
1494
|
+
end
|
1495
|
+
|
1496
|
+
# Converts a result set value from an ODBC type to an ActiveRecord
|
1497
|
+
# generic type.
|
1498
|
+
def convertOdbcValToGenericVal(value)
|
1499
|
+
# When fetching a result set, the Ruby ODBC driver converts all ODBC
|
1500
|
+
# SQL types to an equivalent Ruby type; with the exception of
|
1501
|
+
# SQL_TYPE_DATE, SQL_TYPE_TIME and SQL_TYPE_TIMESTAMP.
|
1502
|
+
#
|
1503
|
+
# The conversions below are consistent with the mappings in
|
1504
|
+
# ODBCColumn#mapOdbcSqlTypeToGenericType and Column#klass.
|
1505
|
+
res = value
|
1506
|
+
case value
|
1507
|
+
when ODBC::TimeStamp
|
1508
|
+
res = Time.gm(value.year, value.month, value.day, value.hour,
|
1509
|
+
value.minute, value.second)
|
1510
|
+
when ODBC::Time
|
1511
|
+
now = DateTime.now
|
1512
|
+
res = Time.gm(now.year, now.month, now.day, value.hour,
|
1513
|
+
value.minute, value.second)
|
1514
|
+
when ODBC::Date
|
1515
|
+
res = Date.new(value.year, value.month, value.day)
|
1516
|
+
end
|
1517
|
+
res
|
1518
|
+
end
|
1519
|
+
|
1520
|
+
# In general, ActiveRecord uses lowercase attribute names. This may
|
1521
|
+
# conflict with the database's data dictionary case.
|
1522
|
+
#
|
1523
|
+
# The ODBCAdapter uses the following conventions for databases
|
1524
|
+
# which report SQL_IDENTIFIER_CASE = SQL_IC_UPPER:
|
1525
|
+
# * if a name is returned from the DBMS in all uppercase, convert it
|
1526
|
+
# to lowercase before returning it to ActiveRecord.
|
1527
|
+
# * if a name is returned from the DBMS in lowercase or mixed case,
|
1528
|
+
# assume the underlying schema object's name was quoted when
|
1529
|
+
# the schema object was created. Leave the name untouched before
|
1530
|
+
# returning it to ActiveRecord.
|
1531
|
+
# * before making an ODBC catalog call, if a supplied identifier is all
|
1532
|
+
# lowercase, convert it to uppercase. Leave mixed case or all
|
1533
|
+
# uppercase identifiers unchanged.
|
1534
|
+
# * columns created with quoted lowercase names are not supported.
|
1535
|
+
|
1536
|
+
# Converts an identifier to the case conventions used by the DBMS.
|
1537
|
+
def dbmsIdentCase(identifier)
|
1538
|
+
# Assume received identifier is in ActiveRecord case.
|
1539
|
+
case @dsInfo.info[ODBC::SQL_IDENTIFIER_CASE]
|
1540
|
+
when ODBC::SQL_IC_UPPER
|
1541
|
+
identifier =~ /[A-Z]/ ? identifier : identifier.upcase
|
1542
|
+
else
|
1543
|
+
identifier
|
1544
|
+
end
|
1545
|
+
end
|
1546
|
+
|
1547
|
+
# Converts an identifier to the case conventions used by ActiveRecord.
|
1548
|
+
def activeRecIdentCase(identifier)
|
1549
|
+
# Assume received identifier is in DBMS's data dictionary case.
|
1550
|
+
case @dsInfo.info[ODBC::SQL_IDENTIFIER_CASE]
|
1551
|
+
when ODBC::SQL_IC_UPPER
|
1552
|
+
identifier =~ /[a-z]/ ? identifier : identifier.downcase
|
1553
|
+
else
|
1554
|
+
identifier
|
1555
|
+
end
|
1556
|
+
end
|
1557
|
+
|
1558
|
+
# Gets ODBCColumn descriptor for specified column
|
1559
|
+
def getODBCColumnDesc(table_name, column_name)
|
1560
|
+
col = nil
|
1561
|
+
columns(table_name, column_name).each do |colDesc|
|
1562
|
+
if colDesc.name == column_name
|
1563
|
+
col = colDesc
|
1564
|
+
break
|
1565
|
+
end
|
1566
|
+
end
|
1567
|
+
col
|
1568
|
+
end
|
1569
|
+
|
1570
|
+
# Gets and caches SQLGetTypeInfo result set
|
1571
|
+
def getDbTypeInfo
|
1572
|
+
return @typeInfo if @typeInfo
|
1573
|
+
|
1574
|
+
begin
|
1575
|
+
stmt = @connection.types
|
1576
|
+
@typeInfo = stmt.fetch_all
|
1577
|
+
rescue Exception => e
|
1578
|
+
@logger.unknown("exception=#{e}") if @@trace
|
1579
|
+
raise ActiveRecordError, e.message
|
1580
|
+
ensure
|
1581
|
+
stmt.drop unless stmt.nil?
|
1582
|
+
end
|
1583
|
+
@typeInfo
|
1584
|
+
end
|
1585
|
+
|
1586
|
+
# Simulating sequences
|
1587
|
+
def create_sequence(name, start_val = 1) end
|
1588
|
+
def drop_sequence(name) end
|
1589
|
+
def next_sequence_value(name) end
|
1590
|
+
def ensure_sequences_table() end
|
1591
|
+
|
1592
|
+
end # class ODBCAdapter
|
1593
|
+
|
1594
|
+
#---------------------------------------------------------------------
|
1595
|
+
|
1596
|
+
class ODBCColumn < Column #:nodoc:
|
1597
|
+
|
1598
|
+
def initialize (name, tableName, default, odbcSqlType, nativeType,
|
1599
|
+
null = true, limit = nil, dbExt = nil, typeInfo = nil)
|
1600
|
+
|
1601
|
+
begin
|
1602
|
+
require "#{dbExt}"
|
1603
|
+
self.extend ODBCColumnExt
|
1604
|
+
rescue MissingSourceFile
|
1605
|
+
# Assume the current DBMS doesn't require extensions to ODBCColumn
|
1606
|
+
end
|
1607
|
+
|
1608
|
+
@name, @null = name, null
|
1609
|
+
|
1610
|
+
# Only set @limit if native type takes a creation parameter
|
1611
|
+
nativeTypeTakesCreateParams = false
|
1612
|
+
if typeInfo
|
1613
|
+
typeInfo.each do |row|
|
1614
|
+
dbmsType = row[0] #SQLGetTypeInfo: TYPE_NAME
|
1615
|
+
if dbmsType.casecmp(nativeType) == 0
|
1616
|
+
createParams = row[5]
|
1617
|
+
nativeTypeTakesCreateParams = (createParams && createParams.strip.length > 0)
|
1618
|
+
break;
|
1619
|
+
end
|
1620
|
+
end
|
1621
|
+
end
|
1622
|
+
@limit = nativeTypeTakesCreateParams ? limit : nil
|
1623
|
+
|
1624
|
+
# nativeType is DBMS type used for column definition
|
1625
|
+
# sql_type assigned here excludes any length specification
|
1626
|
+
@sql_type = @nativeType = String.new(nativeType)
|
1627
|
+
@type = mapOdbcSqlTypeToGenericType(odbcSqlType)
|
1628
|
+
# type_cast uses #type so @type must be set first
|
1629
|
+
|
1630
|
+
# The MS SQL Native Client ODBC driver wraps defaults in parentheses
|
1631
|
+
# (contrary to the ODBC spec).
|
1632
|
+
# e.g. '(1)' instead of '1', '(null)' instead of 'null'
|
1633
|
+
if default =~ /^\((.+)\)$/ then default = $1 end
|
1634
|
+
|
1635
|
+
if self.respond_to?(:default_preprocess, true)
|
1636
|
+
default_preprocess(nativeType, default)
|
1637
|
+
end
|
1638
|
+
|
1639
|
+
@default = type_cast(default)
|
1640
|
+
@table = tableName
|
1641
|
+
@text = [:string, :text].include? @type
|
1642
|
+
@number = [:float, :integer].include? @type
|
1643
|
+
@primary = nil
|
1644
|
+
@autounique = self.respond_to?(:autoUnique?, true) ? autoUnique? : false
|
1645
|
+
end
|
1646
|
+
|
1647
|
+
# Casts a value to the Ruby class corresponding to the ActiveRecord
|
1648
|
+
# abstract type associated with the column.
|
1649
|
+
#
|
1650
|
+
# See Column#klass for the Ruby class corresponding to each
|
1651
|
+
# ActiveRecord abstract type.
|
1652
|
+
#
|
1653
|
+
# This method is not just called by the ODBCColumn constructor, so
|
1654
|
+
# value may be something other than a String.
|
1655
|
+
#
|
1656
|
+
# When casting a column's default value:
|
1657
|
+
# nil => no default value specified
|
1658
|
+
# "'<value>'" => string default value
|
1659
|
+
# "NULL" => default value of NULL
|
1660
|
+
# "TRUNCATED" => default value can't be represented without truncation
|
1661
|
+
#
|
1662
|
+
# Microsoft's SQL Native Client ODBC driver may return '(null)'
|
1663
|
+
# as a column default, instead of NULL, contrary to the ODBC spec'
|
1664
|
+
# It also wraps other default values in parentheses.
|
1665
|
+
def type_cast(value)
|
1666
|
+
return nil if value.nil? || value =~
|
1667
|
+
/(^\s*[(]*\s*null\s*[)]*\s*$)|(^\s*truncated\s*$)/i
|
1668
|
+
case type
|
1669
|
+
when :string then value.to_s
|
1670
|
+
when :text then value.to_s
|
1671
|
+
when :integer then value.to_i
|
1672
|
+
when :float then value.to_f
|
1673
|
+
when :boolean then self.class.value_to_boolean(value)
|
1674
|
+
when :binary then value.to_s
|
1675
|
+
when :datetime then self.class.value_to_time(value)
|
1676
|
+
when :timestamp then self.class.value_to_time(value)
|
1677
|
+
when :time then self.class.value_to_time(value)
|
1678
|
+
when :date then self.class.value_to_date(value)
|
1679
|
+
else
|
1680
|
+
raise ActiveRecordError, "Unknown ActiveRecord abstract type"
|
1681
|
+
end
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
def self.value_to_time(value)
|
1685
|
+
# If we received a time literal without a date component, pad the
|
1686
|
+
# resulting array with dummy date information.
|
1687
|
+
#
|
1688
|
+
# See Column#string_to_dummy_time and
|
1689
|
+
# BasicsTest#test_attributes_on_dummy_time. Both assume the dummy
|
1690
|
+
# date component will be 2000-01-01.
|
1691
|
+
if value.is_a?(Time)
|
1692
|
+
if value.year != 0 and value.month != 0 and value.day != 0
|
1693
|
+
return value
|
1694
|
+
else
|
1695
|
+
return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
|
1696
|
+
end
|
1697
|
+
elsif value.is_a?(String)
|
1698
|
+
time = ParseDate.parsedate(value)
|
1699
|
+
if time[0].nil? && time[1].nil? && time[2].nil?
|
1700
|
+
time[0] = 2000; time[1] = 1; time[2] = 1;
|
1701
|
+
end
|
1702
|
+
Time.send(Base.default_timezone, *time) rescue nil
|
1703
|
+
else
|
1704
|
+
raise ActiveRecordError, "Unexpected type (#{value.class})"
|
1705
|
+
end
|
1706
|
+
end
|
1707
|
+
|
1708
|
+
def self.value_to_date(value)
|
1709
|
+
if value.is_a?(Date)
|
1710
|
+
return value
|
1711
|
+
elsif value.is_a?(String)
|
1712
|
+
begin
|
1713
|
+
date = ParseDate.parsedate(value)
|
1714
|
+
Date.new(date[0], date[1], date[2])
|
1715
|
+
rescue
|
1716
|
+
raise ActiveRecordError, "Cannot convert supplied String value to Date (#{value})"
|
1717
|
+
end
|
1718
|
+
elsif value.is_a?(Time)
|
1719
|
+
begin
|
1720
|
+
Date.new(value.year, value.month, value.day)
|
1721
|
+
rescue
|
1722
|
+
raise ActiveRecordError, "Cannot convert supplied Time value to Date (#{value})"
|
1723
|
+
end
|
1724
|
+
else
|
1725
|
+
raise ActiveRecordError, "Unexpected type (#{value.class})"
|
1726
|
+
end
|
1727
|
+
end
|
1728
|
+
|
1729
|
+
private
|
1730
|
+
|
1731
|
+
# Maps an ODBC SQL type to an ActiveRecord abstract data type
|
1732
|
+
#
|
1733
|
+
# c.f. Mappings in ConnectionAdapters::Column#simplified_type based on
|
1734
|
+
# native column type declaration
|
1735
|
+
#
|
1736
|
+
# See also:
|
1737
|
+
# Column#klass (schema_definitions.rb) for the Ruby class corresponding
|
1738
|
+
# to each abstract data type.
|
1739
|
+
def mapOdbcSqlTypeToGenericType (odbcSqlType)
|
1740
|
+
case odbcSqlType
|
1741
|
+
when ODBC::SQL_BIT then :boolean
|
1742
|
+
|
1743
|
+
when ODBC::SQL_CHAR, ODBC::SQL_VARCHAR then :string
|
1744
|
+
when ODBC::SQL_LONGVARCHAR then :text
|
1745
|
+
|
1746
|
+
when ODBC::SQL_WCHAR, ODBC::SQL_WVARCHAR then :string
|
1747
|
+
when ODBC::SQL_WLONGVARCHAR then :text
|
1748
|
+
|
1749
|
+
when ODBC::SQL_TINYINT, ODBC::SQL_SMALLINT, ODBC::SQL_INTEGER,
|
1750
|
+
ODBC::SQL_BIGINT then :integer
|
1751
|
+
|
1752
|
+
when ODBC::SQL_REAL, ODBC::SQL_FLOAT, ODBC::SQL_DOUBLE then :float
|
1753
|
+
|
1754
|
+
when ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC then :float
|
1755
|
+
# SQL_DECIMAL & SQL_NUMERIC are both exact types.
|
1756
|
+
# Possible loss of precision if retrieved to :float
|
1757
|
+
|
1758
|
+
when ODBC::SQL_BINARY, ODBC::SQL_VARBINARY,
|
1759
|
+
ODBC::SQL_LONGVARBINARY then :binary
|
1760
|
+
|
1761
|
+
# SQL_DATETIME is an alias for SQL_DATE in ODBC's sql.h & sqlext.h
|
1762
|
+
when ODBC::SQL_DATE, ODBC::SQL_TYPE_DATE,
|
1763
|
+
ODBC::SQL_DATETIME then :date
|
1764
|
+
when ODBC::SQL_TIME, ODBC::SQL_TYPE_TIME then :time
|
1765
|
+
when ODBC::SQL_TIMESTAMP, ODBC::SQL_TYPE_TIMESTAMP then :timestamp
|
1766
|
+
|
1767
|
+
when ODBC::SQL_GUID then :string
|
1768
|
+
|
1769
|
+
else
|
1770
|
+
# when SQL_UNKNOWN_TYPE
|
1771
|
+
# (ruby-odbc driver doesn't support following ODBC SQL types:
|
1772
|
+
# SQL_WCHAR, SQL_WVARCHAR, SQL_WLONGVARCHAR, SQL_INTERVAL_xxx)
|
1773
|
+
msg = "Unsupported ODBC SQL type [" << odbcSqlType.to_s << "]"
|
1774
|
+
raise ActiveRecordError, msg
|
1775
|
+
end
|
1776
|
+
end
|
1777
|
+
|
1778
|
+
end # class ODBCColumn
|
1779
|
+
|
1780
|
+
end # module ConnectionAdapters
|
1781
|
+
end # module ActiveRecord
|
1782
|
+
|
1783
|
+
#-------------------------------------------------------------------------
|
1784
|
+
rescue LoadError
|
1785
|
+
module ActiveRecord # :nodoc:
|
1786
|
+
class Base
|
1787
|
+
def self.odbc_connection(config) # :nodoc:
|
1788
|
+
raise LoadError, "The Ruby ODBC module could not be loaded."
|
1789
|
+
end
|
1790
|
+
end
|
1791
|
+
end
|
1792
|
+
end
|