activerecord-odbc-adapter 2.0

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