activerecord-odbc-adapter-openedge 2.3

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