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.
Files changed (66) hide show
  1. data/AUTHORS +16 -0
  2. data/COPYING +21 -0
  3. data/ChangeLog +89 -0
  4. data/LICENSE +5 -0
  5. data/NEWS +12 -0
  6. data/README +282 -0
  7. data/lib/active_record/connection_adapters/odbc_adapter.rb +1792 -0
  8. data/lib/active_record/vendor/odbcext_db2.rb +87 -0
  9. data/lib/active_record/vendor/odbcext_informix.rb +132 -0
  10. data/lib/active_record/vendor/odbcext_informix_col.rb +45 -0
  11. data/lib/active_record/vendor/odbcext_ingres.rb +156 -0
  12. data/lib/active_record/vendor/odbcext_microsoftsqlserver.rb +185 -0
  13. data/lib/active_record/vendor/odbcext_microsoftsqlserver_col.rb +40 -0
  14. data/lib/active_record/vendor/odbcext_mysql.rb +136 -0
  15. data/lib/active_record/vendor/odbcext_oracle.rb +220 -0
  16. data/lib/active_record/vendor/odbcext_postgresql.rb +179 -0
  17. data/lib/active_record/vendor/odbcext_progress.rb +139 -0
  18. data/lib/active_record/vendor/odbcext_progress89.rb +259 -0
  19. data/lib/active_record/vendor/odbcext_sybase.rb +212 -0
  20. data/lib/active_record/vendor/odbcext_sybase_col.rb +49 -0
  21. data/lib/active_record/vendor/odbcext_virtuoso.rb +146 -0
  22. data/lib/odbc_adapter.rb +28 -0
  23. data/support/lib/active_record/connection_adapters/abstract/schema_definitions.rb +259 -0
  24. data/support/odbc_rails.diff +707 -0
  25. data/support/pack_odbc.rb +119 -0
  26. data/support/rake/rails_plugin_package_task.rb +212 -0
  27. data/support/test/base_test.rb +1349 -0
  28. data/support/test/migration_test.rb +566 -0
  29. data/test/connections/native_odbc/connection.rb +95 -0
  30. data/test/fixtures/db_definitions/db2.drop.sql +30 -0
  31. data/test/fixtures/db_definitions/db2.sql +217 -0
  32. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  33. data/test/fixtures/db_definitions/db22.sql +5 -0
  34. data/test/fixtures/db_definitions/informix.drop.sql +30 -0
  35. data/test/fixtures/db_definitions/informix.sql +205 -0
  36. data/test/fixtures/db_definitions/informix2.drop.sql +2 -0
  37. data/test/fixtures/db_definitions/informix2.sql +5 -0
  38. data/test/fixtures/db_definitions/ingres.drop.sql +62 -0
  39. data/test/fixtures/db_definitions/ingres.sql +232 -0
  40. data/test/fixtures/db_definitions/ingres2.drop.sql +2 -0
  41. data/test/fixtures/db_definitions/ingres2.sql +5 -0
  42. data/test/fixtures/db_definitions/mysql.drop.sql +30 -0
  43. data/test/fixtures/db_definitions/mysql.sql +219 -0
  44. data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
  45. data/test/fixtures/db_definitions/mysql2.sql +5 -0
  46. data/test/fixtures/db_definitions/oracle_odbc.drop.sql +64 -0
  47. data/test/fixtures/db_definitions/oracle_odbc.sql +257 -0
  48. data/test/fixtures/db_definitions/oracle_odbc2.drop.sql +2 -0
  49. data/test/fixtures/db_definitions/oracle_odbc2.sql +6 -0
  50. data/test/fixtures/db_definitions/progress.drop.sql +61 -0
  51. data/test/fixtures/db_definitions/progress.sql +234 -0
  52. data/test/fixtures/db_definitions/progress2.drop.sql +2 -0
  53. data/test/fixtures/db_definitions/progress2.sql +6 -0
  54. data/test/fixtures/db_definitions/sqlserver.drop.sql +30 -0
  55. data/test/fixtures/db_definitions/sqlserver.sql +203 -0
  56. data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
  57. data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
  58. data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
  59. data/test/fixtures/db_definitions/sybase.sql +204 -0
  60. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  61. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  62. data/test/fixtures/db_definitions/virtuoso.drop.sql +30 -0
  63. data/test/fixtures/db_definitions/virtuoso.sql +200 -0
  64. data/test/fixtures/db_definitions/virtuoso2.drop.sql +2 -0
  65. data/test/fixtures/db_definitions/virtuoso2.sql +5 -0
  66. 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
@@ -0,0 +1,5 @@
1
+ ODBC Adapter for Ruby on Rails
2
+ Copyright (C) 2006 OpenLink Software
3
+
4
+ The OpenLink ODBC Adapter for Ruby on Rails is released under the
5
+ MIT license (see COPYING).
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&nbsp;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&nbsp;Source&nbsp;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&nbsp;ODBC&nbsp;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