odbc-rails 1.3 → 1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/ChangeLog +18 -0
  2. data/NEWS +5 -0
  3. data/README +20 -7
  4. data/lib/active_record/connection_adapters/odbc_adapter.rb +254 -211
  5. data/lib/active_record/vendor/odbcext_informix.rb +17 -4
  6. data/lib/active_record/vendor/odbcext_ingres.rb +5 -5
  7. data/lib/active_record/vendor/odbcext_microsoftsqlserver.rb +35 -4
  8. data/lib/active_record/vendor/odbcext_mysql.rb +36 -8
  9. data/lib/active_record/vendor/odbcext_oracle.rb +4 -4
  10. data/lib/active_record/vendor/odbcext_postgresql.rb +8 -12
  11. data/lib/active_record/vendor/odbcext_progress.rb +3 -3
  12. data/lib/active_record/vendor/odbcext_progress89.rb +5 -4
  13. data/lib/active_record/vendor/odbcext_sybase.rb +6 -5
  14. data/lib/active_record/vendor/odbcext_virtuoso.rb +16 -3
  15. data/support/odbc_rails.diff +335 -266
  16. data/support/rake_fixes/README +6 -0
  17. data/support/rake_fixes/databases.dif +13 -0
  18. data/support/test/base_test.rb +333 -75
  19. data/support/test/migration_test.rb +430 -149
  20. data/test/connections/native_odbc/connection.rb +63 -44
  21. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  22. data/test/fixtures/db_definitions/db2.sql +9 -0
  23. data/test/fixtures/db_definitions/informix.drop.sql +1 -0
  24. data/test/fixtures/db_definitions/informix.sql +10 -0
  25. data/test/fixtures/db_definitions/ingres.drop.sql +2 -0
  26. data/test/fixtures/db_definitions/ingres.sql +9 -0
  27. data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
  28. data/test/fixtures/db_definitions/mysql.sql +21 -12
  29. data/test/fixtures/db_definitions/oracle_odbc.drop.sql +4 -0
  30. data/test/fixtures/db_definitions/oracle_odbc.sql +29 -1
  31. data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
  32. data/test/fixtures/db_definitions/postgresql.sql +13 -3
  33. data/test/fixtures/db_definitions/progress.drop.sql +2 -0
  34. data/test/fixtures/db_definitions/progress.sql +11 -0
  35. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  36. data/test/fixtures/db_definitions/sqlserver.sql +35 -0
  37. data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
  38. data/test/fixtures/db_definitions/sybase.sql +16 -7
  39. data/test/fixtures/db_definitions/virtuoso.drop.sql +1 -0
  40. data/test/fixtures/db_definitions/virtuoso.sql +10 -0
  41. metadata +6 -3
data/ChangeLog CHANGED
@@ -1,3 +1,21 @@
1
+ 2007-02-27 15:28 source
2
+
3
+ * Set version 1.4 for final release
4
+
5
+ 2007-02-27 12:53 source
6
+
7
+ * Small rdoc fix
8
+
9
+ 2007-02-27 11:05 source
10
+
11
+ * Added small diff for rake unit testing
12
+
13
+ 2007-02-27 11:00 source
14
+
15
+ * Changes for Rails 1.2.x / ActiveRecord 1.15.x
16
+ * Added support for :decimal type
17
+ * Added :emulate_booleans connection option
18
+
1
19
  2007-01-09 10:18 source
2
20
 
3
21
  * Updated version number to 1.3
data/NEWS CHANGED
@@ -1,3 +1,8 @@
1
+ February 27, 2007, V1.4:
2
+ * Changes for Rails 1.2.x / ActiveRecord 1.15.x
3
+ * Added support for :decimal type
4
+ * Added :emulate_booleans connection option
5
+
1
6
  January 9, 2007, V1.3:
2
7
  * Added support for PostgreSQL
3
8
 
data/README CHANGED
@@ -2,11 +2,21 @@
2
2
 
3
3
  (C) 2006 OpenLink Software
4
4
 
5
- 09-Jan-2007
6
-
7
5
 
8
6
  == Status
9
7
 
8
+ 23-Feb-2007
9
+
10
+ The adapter has been updated to support Rails 1.2.x / ActiveRecord 1.15.x.
11
+ The new adapter (v1.4) is not recommended for use with Rails 1.1 / ActiveRecord
12
+ 1.14.x. Please use v1.3 of the adapter with Rails 1.1.
13
+
14
+ The driver now supports the new ActiveRecord :decimal type and an
15
+ :emulate_booleans connection option. See http://odbc-rails.rubyforge.org for
16
+ more information about this option.
17
+
18
+ 09-Jan-2007
19
+
10
20
  The adapter accompanying this note is a Generic ODBC Adapter for Ruby on Rails
11
21
  being developed by <B>OpenLink&nbsp;Software</B>[http://www.openlinksw.com].
12
22
 
@@ -18,7 +28,7 @@ its own adapter.
18
28
  It currently supports Ingres r3, Informix 9.3 or later, Oracle 10g,
19
29
  MySQL 5 and OpenLink's Virtuoso
20
30
  (Open&nbsp;Source&nbsp;Edition[http://virtuoso.openlinksw.com]),
21
- SQL Server 2000, Sybase ASE 15, DB2 v9, Progress v8/9/10 and PostgreSQL 8.2.
31
+ SQL Server 2000/2005, Sybase ASE 15, DB2 v9, Progress v8/9/10 and PostgreSQL 8.2.
22
32
 
23
33
  Testing to date has been limited to the ROR 'Expenses' sample
24
34
  application described at http://developer.apple.com/tools/rubyonrails.html
@@ -71,18 +81,21 @@ due course, if we request that the OpenLink ODBC Adapter for Ruby
71
81
  on Rails be added to the ActiveRecord source distribution to accompany
72
82
  the existing Rails adapters.
73
83
 
74
- Only one patch in this diff file is really necessary - that to
84
+ Only one of the patches is really necessary. In adapter release 1.3 or
85
+ earlier, the diff file includes a patch to:
75
86
 
76
87
  lib/active_record/connection_adapters/abstract/schema_definitions.rb
77
88
 
78
- This fixes what appears to be a bug in Active Record.
89
+ This fixes what appears to be a bug in Active Record 1.14. The patch
90
+ is unnecessary for AR 1.15 and consequently not included in adapter
91
+ release 1.4
79
92
 
80
93
  The patches to base_test.rb and migration_test.rb are not essential.
81
94
  However, they modify or bypass certain tests to cope with limitations
82
95
  of particular databases. Other developers have previously modified
83
96
  the tests similarly to cope with limitations of other Rails-supported
84
- databases. The patched versions of files touched by odbc_rails.diff can be
85
- found in support/lib and support/test.
97
+ databases. The patched versions of files touched by odbc_rails.diff can
98
+ be found in support/lib and support/test.
86
99
 
87
100
  == Installation
88
101
 
@@ -1,5 +1,5 @@
1
1
  #
2
- # $Id: odbc_adapter.rb,v 1.3 2007/01/09 10:11:40 source Exp $
2
+ # $Id: odbc_adapter.rb,v 1.5 2007/02/27 12:53:51 source Exp $
3
3
  #
4
4
  # OpenLink ODBC Adapter for Ruby on Rails
5
5
  # Copyright (C) 2006 OpenLink Software
@@ -41,12 +41,16 @@ begin
41
41
  end
42
42
  username = config[:username] ? config[:username].to_s : nil
43
43
  password = config[:password] ? config[:password].to_s : nil
44
- trace = config[:trace] ? config[:trace] : false
45
- conv_num_lits = config[:convert_numeric_literals] ? config[:convert_numeric_literals] : false
44
+ trace = config[:trace].nil? ? false : config[:trace]
45
+ conv_num_lits = config[:convert_numeric_literals].nil? ? false : config[:convert_numeric_literals]
46
+ emulate_bools = config[:emulate_booleans].nil? ? false : config[:emulate_booleans]
47
+
46
48
  conn = ODBC::connect(dsn, username, password)
47
49
  conn.autocommit = true
48
- ConnectionAdapters::ODBCAdapter.new(conn, [dsn, username, password],
49
- trace, conv_num_lits, logger)
50
+
51
+ ConnectionAdapters::ODBCAdapter.new(conn, {:dsn => dsn, :username => username,
52
+ :password => password, :trace => trace, :conv_num_lits => conv_num_lits,
53
+ :emulate_booleans => emulate_bools}, logger)
50
54
  end
51
55
  end # class Base
52
56
 
@@ -57,7 +61,15 @@ begin
57
61
  # The ODBC adapter requires the Ruby ODBC module (version 0.9991 or
58
62
  # later), available from http://raa.ruby-lang.org/project/ruby-odbc
59
63
  #
60
- # == Status at 09-Jan-2007
64
+ # == Status
65
+ #
66
+ # === 27-Feb-2007
67
+ #
68
+ # Adapter updated to support Rails 1.2.x / ActiveRecord 1.15.x.
69
+ # Support added for AR :decimal type and :emulate_booleans connection
70
+ # option introduced.
71
+ #
72
+ # === 09-Jan-2007
61
73
  #
62
74
  # The current adapter supports Ingres r3, Informix 9.3 or later,
63
75
  # Virtuoso (Open-Source Edition) 4.5, Oracle 10g, MySQL 5,
@@ -109,6 +121,11 @@ begin
109
121
  # If set to <tt>true</tt>, suppresses quoting of numeric literals.
110
122
  # If omitted, <tt>:convert_numeric_literals</tt> defaults to
111
123
  # <tt>false</tt>.
124
+ # <tt>:emulate_booleans</tt>::
125
+ # Instructs the adapter to interpret certain numeric column types as
126
+ # holding boolean, rather than numeric, data. It is intended for use
127
+ # with databases which do not have a native boolean data type.
128
+ # If omitted, <tt>:emulate_booleans</tt> defaults to <tt>false</tt>.
112
129
  #
113
130
  # == Usage Notes
114
131
  # === Informix
@@ -187,115 +204,144 @@ begin
187
204
  # Uses dbmsName as key and dbmsMajorVer as a subkey.
188
205
  :db2 => {
189
206
  :any_version => {
190
- :primary_key => "integer generated by default as identity (start with 10000) primary key",
207
+ :primary_key => "INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 10000) PRIMARY KEY",
191
208
  :has_autoincrement_col => true,
192
209
  :supports_migrations => true,
193
210
  :supports_schema_names => true,
194
- :supports_count_distinct => true
211
+ :supports_count_distinct => true,
212
+ :boolean_col_surrogate => "DECIMAL(1)"
195
213
  }
196
214
  },
197
215
  :informix => {
198
216
  :any_version => {
199
- :primary_key => "serial primary key",
217
+ :primary_key => "SERIAL PRIMARY KEY",
200
218
  :has_autoincrement_col => true,
201
219
  :supports_migrations => true,
202
220
  :supports_schema_names => true,
203
- :supports_count_distinct => true
221
+ :supports_count_distinct => true,
222
+ # This data adapter automatically maps ODBC::SQL_BIT to
223
+ # :boolean. So, the following is unnecessary if the ODBC
224
+ # driver maps the native BOOLEAN type available in
225
+ # Informix 9.x to ODBC::SQL_BIT in SQLGetTypeInfo.
226
+ :boolean_col_surrogate => "SMALLINT"
204
227
  }
205
228
  },
206
229
  :ingres => {
207
230
  :any_version => {
208
- :primary_key => "integer primary key not null",
231
+ :primary_key => "INTEGER PRIMARY KEY NOT NULL",
209
232
  :has_autoincrement_col => false,
210
233
  :supports_migrations => true,
211
234
  :supports_schema_names => true,
212
- :supports_count_distinct => true
235
+ :supports_count_distinct => true,
236
+ :boolean_col_surrogate => "INTEGER1"
213
237
  }
214
238
  },
215
- :mysql => {
239
+ :microsoftsqlserver => {
216
240
  :any_version => {
217
- :primary_key => "int(11) not null auto_increment primary key",
241
+ :primary_key => "INT NOT NULL IDENTITY(1, 1) PRIMARY KEY",
218
242
  :has_autoincrement_col => true,
219
243
  :supports_migrations => true,
220
- :supports_schema_names => false,
221
- :supports_count_distinct => true
244
+ :supports_schema_names => true,
245
+ :supports_count_distinct => true,
246
+ # boolean_col_surrogate not necessary.
247
+ # SQL Server's BIT data type is mapped to ODBC::SQL_BIT/:boolean.
248
+ :boolean_col_surrogate => nil
222
249
  },
223
- 5 => {
224
- :primary_key => "int(11) not null auto_increment primary key",
250
+ 8 => {
251
+ :primary_key => "INT NOT NULL IDENTITY(1, 1) PRIMARY KEY",
225
252
  :has_autoincrement_col => true,
226
253
  :supports_migrations => true,
227
- :supports_schema_names => false,
228
- :supports_count_distinct => true
254
+ :supports_schema_names => true,
255
+ :supports_count_distinct => true,
256
+ # boolean_col_surrogate not necessary.
257
+ # SQL Server's BIT data type is mapped to ODBC::SQL_BIT/:boolean.
258
+ :boolean_col_surrogate => nil
229
259
  }
230
260
  },
231
- :microsoftsqlserver => {
261
+ :mysql => {
232
262
  :any_version => {
233
- :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
263
+ :primary_key => "INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY",
234
264
  :has_autoincrement_col => true,
235
265
  :supports_migrations => true,
236
- :supports_schema_names => true,
237
- :supports_count_distinct => true
266
+ :supports_schema_names => false,
267
+ :supports_count_distinct => true,
268
+ :boolean_col_surrogate => "TINYINT"
238
269
  },
239
- 8 => {
240
- :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
270
+ 5 => {
271
+ :primary_key => "INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY",
241
272
  :has_autoincrement_col => true,
242
273
  :supports_migrations => true,
243
- :supports_schema_names => true,
244
- :supports_count_distinct => true
274
+ :supports_schema_names => false,
275
+ :supports_count_distinct => true,
276
+ :boolean_col_surrogate => "TINYINT"
245
277
  }
246
278
  },
247
279
  :oracle => {
248
280
  :any_version => {
249
- :primary_key => "number(10) PRIMARY KEY NOT NULL",
281
+ :primary_key => "NUMBER(10) PRIMARY KEY NOT NULL",
250
282
  :has_autoincrement_col => false,
251
283
  :supports_migrations => true,
252
284
  :supports_schema_names => true,
253
- :supports_count_distinct => true
285
+ :supports_count_distinct => true,
286
+ :boolean_col_surrogate => "NUMBER(1)"
254
287
  }
255
288
  },
256
289
  :postgresql => {
257
290
  :any_version => {
258
- :primary_key => "serial primary key",
291
+ :primary_key => "SERIAL PRIMARY KEY",
259
292
  :has_autoincrement_col => true,
260
293
  :supports_migrations => true,
261
294
  :supports_schema_names => false,
262
- :supports_count_distinct => true
295
+ :supports_count_distinct => true,
296
+ # boolean_col_surrogate not necessary.
297
+ # PostgreSQL's BOOL data type is mapped to ODBC::SQL_BIT/:boolean.
298
+ :boolean_col_surrogate => nil
263
299
  }
264
300
  },
265
301
  :progress => {
266
302
  :any_version => {
267
- :primary_key => "integer not null primary key",
303
+ :primary_key => "INTEGER NOT NULL PRIMARY KEY",
268
304
  :has_autoincrement_col => false,
269
305
  :supports_migrations => true,
270
306
  :supports_schema_names => true,
271
- :supports_count_distinct => true
307
+ :supports_count_distinct => true,
308
+ # boolean_col_surrogate not necessary.
309
+ # Progress SQL-92's BIT data type is mapped to ODBC::SQL_BIT/:boolean.
310
+ :boolean_col_surrogate => nil
272
311
  }
273
312
  },
274
313
  :progress89 => {
275
314
  :any_version => {
276
- :primary_key => "integer not null unique",
315
+ :primary_key => "INTEGER NOT NULL UNIQUE",
277
316
  :has_autoincrement_col => false,
278
317
  :supports_migrations => true,
279
318
  :supports_schema_names => false,
280
- :supports_count_distinct => true
319
+ :supports_count_distinct => true,
320
+ # boolean_col_surrogate not necessary.
321
+ # Progress SQL-89's LOGICAL data type is mapped to ODBC::SQL_BIT/:boolean.
322
+ :boolean_col_surrogate => nil
281
323
  }
282
324
  },
283
325
  :sybase => {
284
326
  :any_version => {
285
- :primary_key => "int IDENTITY PRIMARY KEY",
327
+ :primary_key => "INT IDENTITY PRIMARY KEY",
286
328
  :has_autoincrement_col => true,
287
329
  :supports_migrations => true,
288
330
  :supports_schema_names => true,
289
- :supports_count_distinct => true
331
+ :supports_count_distinct => true,
332
+ # boolean_col_surrogate not necessary.
333
+ # Sybase's BIT data type is mapped to ODBC::SQL_BIT/:boolean.
334
+ :boolean_col_surrogate => nil
290
335
  }
291
336
  },
292
337
  :virtuoso => {
293
338
  :any_version => {
294
- :primary_key => "int NOT NULL IDENTITY PRIMARY KEY",
339
+ :primary_key => "INT NOT NULL IDENTITY PRIMARY KEY",
295
340
  :has_autoincrement_col => true,
296
341
  :supports_migrations => true,
297
342
  :supports_schema_names => true,
298
- :supports_count_distinct => true
343
+ :supports_count_distinct => true,
344
+ :boolean_col_surrogate => "SMALLINT"
299
345
  }
300
346
  }
301
347
  }
@@ -333,7 +379,8 @@ begin
333
379
  ODBC::SQL_IDENTIFIER_QUOTE_CHAR,
334
380
  ODBC::SQL_MAX_IDENTIFIER_LEN,
335
381
  ODBC::SQL_MAX_TABLE_NAME_LEN,
336
- ODBC::SQL_USER_NAME
382
+ ODBC::SQL_USER_NAME,
383
+ ODBC::SQL_DATABASE_NAME
337
384
  ]
338
385
 
339
386
  def initialize(connection)
@@ -376,9 +423,12 @@ begin
376
423
  # DBMS; so similar names are mapped to the same symbol.
377
424
  # _dbmsName_ is the SQL_DBMS_NAME returned by ODBC, downcased with
378
425
  # whitespace removed. e.g. <tt>informix</tt>, <tt>ingres</tt>,
379
- # <tt>microsoftsqlserver</tt> etc.
426
+ # <tt>microsoftsqlserver</tt> etc.
380
427
  attr_reader :dbmsName
381
428
 
429
+ # Emulate boolean columns if the database doesn't have a native BOOLEAN type.
430
+ attr_reader :emulate_booleans
431
+
382
432
  # Supports lookups of DBMS-dependent information/settings which
383
433
  # cannot be derived satisfactorily through ODBC
384
434
  @@dbmsLookups = DbmsInfo.create
@@ -386,8 +436,8 @@ begin
386
436
  @@trace = nil
387
437
  #--
388
438
 
389
- def initialize(connection, connection_options, trace, convert_numeric_literals, logger = nil)
390
- @@trace = trace && logger if !@@trace
439
+ def initialize(connection, connection_options, logger = nil)
440
+ @@trace = connection_options[:trace] && logger if !@@trace
391
441
  # Mixins in odbcext_xxx.rb included using Object#extend can't access
392
442
  # @@trace. Why?
393
443
  # (Error message "NameError: uninitialized class variable @@trace".)
@@ -399,7 +449,9 @@ begin
399
449
  @logger.unknown("ODBCAdapter#initialize>") if @@trace
400
450
 
401
451
  @connection, @connection_options = connection, connection_options
402
- @convert_numeric_literals = convert_numeric_literals
452
+ @convert_numeric_literals = connection_options[:conv_num_lits]
453
+ @emulate_booleans = connection_options[:emulate_booleans]
454
+
403
455
  # Caches SQLGetInfo output
404
456
  @dsInfo = DSInfo.new(connection)
405
457
  # Caches SQLGetTypeInfo output
@@ -437,7 +489,7 @@ begin
437
489
  # Returns the human-readable name of the adapter.
438
490
  def adapter_name
439
491
  @logger.unknown("ODBCAdapter#adapter_name>") if @@trace
440
- 'ODBC'
492
+ 'ODBC'
441
493
  end
442
494
 
443
495
  # Does this adapter support migrations?
@@ -505,11 +557,14 @@ begin
505
557
  @logger.unknown("ODBCAdapter#quote>") if @@trace
506
558
  @logger.unknown("args=[#{value}]") if @@trace
507
559
  case value
508
- when String
560
+ when String, ActiveSupport::Multibyte::Chars
561
+ value = value.to_s
509
562
  if column && column.type == :binary && self.respond_to?(:string_to_binary)
510
563
  "'#{string_to_binary(value)}'"
511
- elsif (column && [:integer, :float].include?(column.type)) ||
512
- (column.nil? && @convert_numeric_literals &&
564
+ elsif (column && [:integer, :float].include?(column.type))
565
+ value = column.type == :integer ? value.to_i : value.to_f
566
+ value.to_s
567
+ elsif (column.nil? && @convert_numeric_literals &&
513
568
  (value =~ /^[-+]?[0-9]+[.]?[0-9]*([eE][-+]?[0-9]+)?$/))
514
569
  value
515
570
  else
@@ -564,12 +619,12 @@ begin
564
619
 
565
620
  def quoted_true
566
621
  @logger.unknown("ODBCAdapter#quoted_true>") if @@trace
567
- '1'
622
+ '1'
568
623
  end
569
624
 
570
625
  def quoted_false
571
626
  @logger.unknown("ODBCAdapter#quoted_false>") if @@trace
572
- '0'
627
+ '0'
573
628
  end
574
629
 
575
630
  def quoted_date(value)
@@ -580,10 +635,10 @@ begin
580
635
  case value
581
636
  when Time, DateTime
582
637
  #%Q!{ts #{value.strftime("%Y-%m-%d %H:%M:%S")}}!
583
- %Q!'#{value.strftime("%Y-%m-%d %H:%M:%S")}'!
638
+ %Q!'#{value.strftime("%Y-%m-%d %H:%M:%S")}'!
584
639
  when Date
585
640
  #%Q!{d #{value.strftime("%Y-%m-%d")}}!
586
- %Q!'#{value.strftime("%Y-%m-%d")}'!
641
+ %Q!'#{value.strftime("%Y-%m-%d")}'!
587
642
  end
588
643
  end
589
644
 
@@ -876,7 +931,7 @@ begin
876
931
  def default_sequence_name(table, column)
877
932
  @logger.unknown("ODBCAdapter#default_sequence_name>") if @@trace
878
933
  @logger.unknown("args=[#{table}|#{column}]") if @@trace
879
- "#{table}_seq"
934
+ "#{table}_seq"
880
935
  end
881
936
 
882
937
  # Set the sequence to the max value of the table�s column.
@@ -894,18 +949,41 @@ begin
894
949
  #
895
950
  # see: abstract/schema_statements.rb
896
951
 
897
- # Implemented by MySQL and SQL Server adapters
898
- #def create_database(name)
899
- #end
952
+ def create_database(name)
953
+ @logger.unknown("ODBCAdapter#create_database>") if @trace
954
+ @logger.unknown("args=[#{name}]") if @trace
955
+ raise NotImplementedError, "create_database is not implemented"
956
+ rescue Exception => e
957
+ @logger.unknown("exception=#{e}") if @trace
958
+ raise
959
+ end
900
960
 
901
- # Implemented by MySQL, SQL Server and Oracle adapters
902
- #def current_database
903
- #end
961
+ def drop_database(name)
962
+ @logger.unknown("ODBCAdapter#drop_database>") if @trace
963
+ @logger.unknown("args=[#{name}]") if @trace
964
+ raise NotImplementedError, "drop_database is not implemented"
965
+ rescue Exception => e
966
+ @logger.unknown("exception=#{e}") if @trace
967
+ raise
968
+ end
904
969
 
905
- # Implemented by MySQL and SQL Server adapters
906
- #drop_database(name)
907
- #end
970
+ #--
971
+ # Required by db:test:purge Rake task (see databases.rake)
972
+ def recreate_database(name, fail_quietly = false)
973
+ @logger.unknown("ODBCAdapter#recreate_database>") if @@trace
974
+ @logger.unknown("args=[#{name}|#{fail_quietly}]") if @@trace
975
+ begin
976
+ drop_database(name)
977
+ create_database(name)
978
+ rescue Exception => e
979
+ raise unless fail_quietly
980
+ end
981
+ end
908
982
 
983
+ def current_database
984
+ @dsInfo.info[ODBC::SQL_DATABASE_NAME].strip
985
+ end
986
+
909
987
  # The maximum length a table alias can be.
910
988
  def table_alias_length
911
989
  maxIdentLen = @dsInfo.info[ODBC::SQL_MAX_IDENTIFIER_LEN]
@@ -950,6 +1028,13 @@ begin
950
1028
  table_name = table_name.to_s if table_name.class == Symbol
951
1029
 
952
1030
  getDbTypeInfo
1031
+ begin
1032
+ booleanColSurrogate = @emulate_booleans ? @@dbmsLookups.get_info(@dbmsName, @dbmsMajorVer, :boolean_col_surrogate) : nil
1033
+ rescue Exception
1034
+ # No boolean column surrogate defined for target database in lookup table
1035
+ booleanColSurrogate = nil
1036
+ @emulate_booleans = false
1037
+ end
953
1038
  cols = []
954
1039
  stmt = @connection.columns(dbmsIdentCase(table_name))
955
1040
  resultSet = stmt.fetch_all || []
@@ -959,6 +1044,7 @@ begin
959
1044
  colSqlType = col[4] # SQLColumns: DATA_TYPE
960
1045
  colNativeType = col[5] # SQLColumns: TYPE_NAME
961
1046
  colLimit = col[6] # SQLColumns: COLUMN_SIZE
1047
+ colScale = col[8] # SQLColumns: DECIMAL_DIGITS
962
1048
 
963
1049
  odbcIsNullable = col[17] # SQLColumns: IS_NULLABLE
964
1050
  odbcNullable = col[10] # SQLColumns: NULLABLE
@@ -979,9 +1065,10 @@ begin
979
1065
  elsif colDefault =~ /^\((.*)\)$/ # SQL Server numeric default
980
1066
  colDefault = $1
981
1067
  # ODBC drivers should return string column defaults in quotes
982
- # - Oracle includes a trailing space.
1068
+ # - strip off the quotes
1069
+ # - Oracle may include a trailing space.
983
1070
  # - PostgreSQL may return '<default>::character varying'
984
- elsif colDefault =~ /^'(.*)'[ :].*$/
1071
+ elsif colDefault =~ /^'(.*)'([ :].*)*$/
985
1072
  colDefault = $1
986
1073
  #TODO: HACKS for Progress
987
1074
  elsif @dbmsName == :progress || @dbmsName == :progress89
@@ -995,7 +1082,7 @@ begin
995
1082
  end
996
1083
  cols << ODBCColumn.new(activeRecIdentCase(colName), table_name,
997
1084
  colDefault, colSqlType, colNativeType, colNullable, colLimit,
998
- @odbcExtFile+"_col", @typeInfo)
1085
+ colScale, @odbcExtFile+"_col", booleanColSurrogate, native_database_types())
999
1086
  end
1000
1087
  stmt.drop
1001
1088
  cols
@@ -1012,7 +1099,7 @@ begin
1012
1099
  indexes = []
1013
1100
  indexCols = indexName = isUnique = nil
1014
1101
 
1015
- stmt = @connection.indexes(dbmsIdentCase(table_name))
1102
+ stmt = @connection.indexes(dbmsIdentCase(table_name.to_s))
1016
1103
  rs = stmt.fetch_all || []
1017
1104
  rs.each_index do |iRow|
1018
1105
  row = rs[iRow]
@@ -1064,6 +1151,7 @@ begin
1064
1151
  :string => nil,
1065
1152
  :text => nil,
1066
1153
  :integer => nil,
1154
+ :decimal => nil,
1067
1155
  :float => nil,
1068
1156
  :datetime => nil,
1069
1157
  :timestamp => nil,
@@ -1109,6 +1197,16 @@ begin
1109
1197
  end
1110
1198
  @logger.unknown("WARNING: No suitable DBMS type for abstract type #{abstractType.to_s}") if !isSupported && @@trace
1111
1199
  end
1200
+
1201
+ begin
1202
+ booleanColSurrogate = @emulate_booleans ? @@dbmsLookups.get_info(@dbmsName, @dbmsMajorVer, :boolean_col_surrogate) : nil
1203
+ rescue Exception
1204
+ # No boolean column surrogate defined for target database in lookup table
1205
+ booleanColSurrogate = nil
1206
+ @emulate_booleans = false
1207
+ end
1208
+ @abstract2NativeTypeMap[:boolean] = {:name => booleanColSurrogate} if booleanColSurrogate
1209
+
1112
1210
  {}.merge(@abstract2NativeTypeMap)
1113
1211
  rescue Exception => e
1114
1212
  @logger.unknown("exception=#{e}") if @@trace
@@ -1137,10 +1235,10 @@ begin
1137
1235
  end
1138
1236
 
1139
1237
  # Drops a table from the database.
1140
- def drop_table(name)
1238
+ def drop_table(name, options = {})
1141
1239
  @logger.unknown("ODBCAdapter#drop_table>") if @@trace
1142
1240
  @logger.unknown("args=[#{name}]") if @@trace
1143
- super(name)
1241
+ super(name, options)
1144
1242
  rescue Exception => e
1145
1243
  @logger.unknown("exception=#{e}") if @@trace
1146
1244
  raise ActiveRecordError, e.message
@@ -1252,8 +1350,8 @@ begin
1252
1350
  @logger.unknown("ODBCAdapter#transaction>") if @@trace
1253
1351
  super(start_db_transaction)
1254
1352
  rescue Exception => e
1255
- @logger.unknown("exception=#{e}") if @@trace
1256
- raise ActiveRecordError, e.message
1353
+ @logger.unknown("#{e.class}: #{e}") if @@trace
1354
+ raise
1257
1355
  end
1258
1356
 
1259
1357
  # Alias for #add_limit_offset!
@@ -1286,45 +1384,43 @@ begin
1286
1384
  end
1287
1385
 
1288
1386
  #--
1289
- # Ensures index names constructed from the table name and a column
1290
- # name do not exceed the maximum index name length supported by the
1291
- # DBMS. It's assumed SQL_MAX_IDENTIFIER_LENGTH reflects the maximum
1292
- # index name length.
1387
+ # If the index is not explicitly named using the :name option,
1388
+ # there's a risk the generated index name could exceed the maximum
1389
+ # length supported by the database.
1390
+ # i.e. dsInfo.info[ODBC::SQL_MAX_IDENTIFIER_LEN]
1293
1391
  def index_name(table_name, options) # :nodoc:
1294
1392
  @logger.unknown("ODBCAdapter#index_name>") if @@trace
1295
1393
  @logger.unknown("args=[#{table_name}]") if @@trace
1296
- maxIndxNameLen = @dsInfo.info[ODBC::SQL_MAX_IDENTIFIER_LEN]
1297
- if Hash === options # legacy support
1298
- if options[:name]
1299
- return options[:name] # error if name length > max index length
1300
- elsif options[:column]
1301
- col_name = options[:column]
1302
- else
1303
- raise ArgumentError, "You must specify the index name"
1304
- end
1305
- else
1306
- col_name = options
1307
- end
1308
- idx_name = "#{table_name}_#{col_name}_index"
1309
- if idx_name.length > maxIndxNameLen
1310
- cMax = ((maxIndxNameLen - 5)/2).to_i
1311
- idx_name = "#{table_name.to_s[0,cMax]}_#{col_name.to_s[0,cMax]}_idx"
1312
- end
1313
- idx_name
1394
+ super
1314
1395
  rescue Exception => e
1315
1396
  @logger.unknown("exception=#{e}") if @@trace
1316
1397
  raise ActiveRecordError, e.message
1317
1398
  end
1318
1399
 
1319
- def type_to_sql(type, limit = nil) # :nodoc:
1400
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) # :nodoc:
1320
1401
  @logger.unknown("ODBCAdapter#type_to_sql>") if @@trace
1321
- @logger.unknown("args=[#{type}|#{limit}]") if @@trace
1402
+ @logger.unknown("args=[#{type}|#{limit}|#{precision}|#{scale}]") if @@trace
1322
1403
  native = native_database_types[type]
1323
- column_type_sql = String.new(type == :primary_key ? native : native[:name])
1324
- # if there's no limit in the type definition, assume that the type
1325
- # doesn't support a length qualifier
1326
- column_type_sql << "(#{limit || native[:limit]})" if native[:limit]
1327
- column_type_sql
1404
+ column_type_sql = String.new(native.is_a?(Hash) ? native[:name] : native)
1405
+ if type == :decimal # ignore limit, use precision and scale
1406
+ precision ||= native[:precision]
1407
+ scale ||= native[:scale]
1408
+ if precision
1409
+ if scale
1410
+ column_type_sql << "(#{precision},#{scale})"
1411
+ else
1412
+ column_type_sql << "(#{precision})"
1413
+ end
1414
+ else
1415
+ raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified" if scale
1416
+ end
1417
+ column_type_sql
1418
+ else
1419
+ # if there's no limit in the type definition, assume that the type
1420
+ # doesn't support a length qualifier
1421
+ column_type_sql << "(#{limit || native[:limit]})" if native[:limit]
1422
+ column_type_sql
1423
+ end
1328
1424
  rescue Exception => e
1329
1425
  @logger.unknown("exception=#{e}") if @@trace
1330
1426
  raise ActiveRecordError, e.message
@@ -1350,7 +1446,7 @@ begin
1350
1446
  end
1351
1447
 
1352
1448
  # ==================================================================
1353
-
1449
+
1354
1450
  private
1355
1451
 
1356
1452
  # Maps a DBMS name to a symbol.
@@ -1405,6 +1501,7 @@ begin
1405
1501
  :string => [ODBC::SQL_VARCHAR],
1406
1502
  :text => [ODBC::SQL_LONGVARCHAR, ODBC::SQL_VARCHAR],
1407
1503
  :integer => [ODBC::SQL_INTEGER, ODBC::SQL_SMALLINT],
1504
+ :decimal => [ ODBC::SQL_NUMERIC, ODBC::SQL_DECIMAL],
1408
1505
  :float => [ODBC::SQL_DOUBLE, ODBC::SQL_REAL],
1409
1506
  :datetime => [ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP],
1410
1507
  :timestamp => [ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP],
@@ -1466,12 +1563,10 @@ begin
1466
1563
 
1467
1564
  res[:name] = nativeTypeDesc[0] # SQLGetTypeInfo: TYPE_NAME
1468
1565
  createParams = nativeTypeDesc[5]
1469
- # CREATE_PARAMS is assumed to be a max_length specification.
1470
- # The abstract types don't map to SQL_DECIMAL, SQL_NUMERIC or
1471
- # SQL_FLOAT (#see genericTypeToOdbcSqlTypesMap), so we needn't worry
1472
- # about these SQL types which take a precision and maybe a scale
1473
- # creation parameter.
1474
- if (createParams && createParams.strip.length > 0)
1566
+ # Depending on the column type, the CREATE_PARAMS keywords can
1567
+ # include length, precision or scale.
1568
+ if (createParams && createParams.strip.length > 0 &&
1569
+ ![:decimal].include?(abstractType))
1475
1570
  unless @dbmsName == :db2 && ["BLOB", "CLOB"].include?(res[:name])
1476
1571
  # HACK:
1477
1572
  # Omit the :limit option for DB2's CLOB and BLOB types, as the
@@ -1515,7 +1610,7 @@ begin
1515
1610
  # SQL_TYPE_DATE, SQL_TYPE_TIME and SQL_TYPE_TIMESTAMP.
1516
1611
  #
1517
1612
  # The conversions below are consistent with the mappings in
1518
- # ODBCColumn#mapOdbcSqlTypeToGenericType and Column#klass.
1613
+ # ODBCColumn#mapSqlTypeToGenericType and Column#klass.
1519
1614
  res = value
1520
1615
  case value
1521
1616
  when ODBC::TimeStamp
@@ -1610,8 +1705,8 @@ begin
1610
1705
  class ODBCColumn < Column #:nodoc:
1611
1706
 
1612
1707
  def initialize (name, tableName, default, odbcSqlType, nativeType,
1613
- null = true, limit = nil, dbExt = nil, typeInfo = nil)
1614
-
1708
+ null = true, limit = nil, scale = nil, dbExt = nil,
1709
+ booleanColSurrogate = nil, nativeTypes = nil)
1615
1710
  begin
1616
1711
  require "#{dbExt}"
1617
1712
  self.extend ODBCColumnExt
@@ -1621,24 +1716,15 @@ begin
1621
1716
 
1622
1717
  @name, @null = name, null
1623
1718
 
1624
- # Only set @limit if native type takes a creation parameter
1625
- nativeTypeTakesCreateParams = false
1626
- if typeInfo
1627
- typeInfo.each do |row|
1628
- dbmsType = row[0] #SQLGetTypeInfo: TYPE_NAME
1629
- if dbmsType.casecmp(nativeType) == 0
1630
- createParams = row[5]
1631
- nativeTypeTakesCreateParams = (createParams && createParams.strip.length > 0)
1632
- break;
1633
- end
1634
- end
1635
- end
1636
- @limit = nativeTypeTakesCreateParams ? limit : nil
1719
+ @precision = extract_precision(odbcSqlType, limit)
1720
+ @scale = extract_scale(odbcSqlType, scale)
1721
+ @limit = limit
1637
1722
 
1638
1723
  # nativeType is DBMS type used for column definition
1639
1724
  # sql_type assigned here excludes any length specification
1640
1725
  @sql_type = @nativeType = String.new(nativeType)
1641
- @type = mapOdbcSqlTypeToGenericType(odbcSqlType)
1726
+ @type = mapSqlTypeToGenericType(odbcSqlType, @nativeType, @scale, booleanColSurrogate, limit,
1727
+ nativeTypes)
1642
1728
  # type_cast uses #type so @type must be set first
1643
1729
 
1644
1730
  # The MS SQL Native Client ODBC driver wraps defaults in parentheses
@@ -1652,21 +1738,17 @@ begin
1652
1738
 
1653
1739
  @default = type_cast(default)
1654
1740
  @table = tableName
1655
- @text = [:string, :text].include? @type
1656
- @number = [:float, :integer].include? @type
1657
1741
  @primary = nil
1658
1742
  @autounique = self.respond_to?(:autoUnique?, true) ? autoUnique? : false
1659
1743
  end
1660
1744
 
1661
- # Casts a value to the Ruby class corresponding to the ActiveRecord
1662
- # abstract type associated with the column.
1745
+ # Casts a value (which is a String) to the Ruby class
1746
+ # corresponding to the ActiveRecord abstract type associated
1747
+ # with the column.
1663
1748
  #
1664
1749
  # See Column#klass for the Ruby class corresponding to each
1665
1750
  # ActiveRecord abstract type.
1666
1751
  #
1667
- # This method is not just called by the ODBCColumn constructor, so
1668
- # value may be something other than a String.
1669
- #
1670
1752
  # When casting a column's default value:
1671
1753
  # nil => no default value specified
1672
1754
  # "'<value>'" => string default value
@@ -1679,67 +1761,9 @@ begin
1679
1761
  def type_cast(value)
1680
1762
  return nil if value.nil? || value =~
1681
1763
  /(^\s*[(]*\s*null\s*[)]*\s*$)|(^\s*truncated\s*$)/i
1682
- case type
1683
- when :string then value.to_s
1684
- when :text then value.to_s
1685
- when :integer then value.to_i
1686
- when :float then value.to_f
1687
- when :boolean then self.class.value_to_boolean(value)
1688
- when :binary then value.to_s
1689
- when :datetime then self.class.value_to_time(value)
1690
- when :timestamp then self.class.value_to_time(value)
1691
- when :time then self.class.value_to_time(value)
1692
- when :date then self.class.value_to_date(value)
1693
- else
1694
- raise ActiveRecordError, "Unknown ActiveRecord abstract type"
1695
- end
1696
- end
1697
-
1698
- def self.value_to_time(value)
1699
- # If we received a time literal without a date component, pad the
1700
- # resulting array with dummy date information.
1701
- #
1702
- # See Column#string_to_dummy_time and
1703
- # BasicsTest#test_attributes_on_dummy_time. Both assume the dummy
1704
- # date component will be 2000-01-01.
1705
- if value.is_a?(Time)
1706
- if value.year != 0 and value.month != 0 and value.day != 0
1707
- return value
1708
- else
1709
- return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
1710
- end
1711
- elsif value.is_a?(String)
1712
- time = ParseDate.parsedate(value)
1713
- if time[0].nil? && time[1].nil? && time[2].nil?
1714
- time[0] = 2000; time[1] = 1; time[2] = 1;
1715
- end
1716
- Time.send(Base.default_timezone, *time) rescue nil
1717
- else
1718
- raise ActiveRecordError, "Unexpected type (#{value.class})"
1719
- end
1720
- end
1721
-
1722
- def self.value_to_date(value)
1723
- if value.is_a?(Date)
1724
- return value
1725
- elsif value.is_a?(String)
1726
- begin
1727
- date = ParseDate.parsedate(value)
1728
- Date.new(date[0], date[1], date[2])
1729
- rescue
1730
- raise ActiveRecordError, "Cannot convert supplied String value to Date (#{value})"
1731
- end
1732
- elsif value.is_a?(Time)
1733
- begin
1734
- Date.new(value.year, value.month, value.day)
1735
- rescue
1736
- raise ActiveRecordError, "Cannot convert supplied Time value to Date (#{value})"
1737
- end
1738
- else
1739
- raise ActiveRecordError, "Unexpected type (#{value.class})"
1740
- end
1764
+ super
1741
1765
  end
1742
-
1766
+
1743
1767
  private
1744
1768
 
1745
1769
  # Maps an ODBC SQL type to an ActiveRecord abstract data type
@@ -1750,36 +1774,39 @@ begin
1750
1774
  # See also:
1751
1775
  # Column#klass (schema_definitions.rb) for the Ruby class corresponding
1752
1776
  # to each abstract data type.
1753
- def mapOdbcSqlTypeToGenericType (odbcSqlType)
1777
+ def mapSqlTypeToGenericType (odbcSqlType, nativeType, scale,
1778
+ booleanColSurrogate, rawPrecision, nativeTypes)
1779
+ if booleanColSurrogate && booleanColSurrogate.upcase.index(nativeType.upcase)
1780
+ fullType = nativeType.dup
1781
+ if booleanColSurrogate =~ /\(\d+(,\d+)?\)/ && rawPrecision
1782
+ fullType << "(#{rawPrecision}"
1783
+ fullType << ",#{scale}" if $1 && scale
1784
+ fullType << ")"
1785
+ end
1786
+ return :boolean if fullType.casecmp(booleanColSurrogate) == 0
1787
+ end
1788
+
1754
1789
  case odbcSqlType
1755
- when ODBC::SQL_BIT then :boolean
1756
-
1790
+ when ODBC::SQL_BIT then :boolean
1757
1791
  when ODBC::SQL_CHAR, ODBC::SQL_VARCHAR then :string
1758
1792
  when ODBC::SQL_LONGVARCHAR then :text
1759
-
1760
1793
  when ODBC::SQL_WCHAR, ODBC::SQL_WVARCHAR then :string
1761
- when ODBC::SQL_WLONGVARCHAR then :text
1762
-
1794
+ when ODBC::SQL_WLONGVARCHAR then :text
1763
1795
  when ODBC::SQL_TINYINT, ODBC::SQL_SMALLINT, ODBC::SQL_INTEGER,
1764
- ODBC::SQL_BIGINT then :integer
1765
-
1796
+ ODBC::SQL_BIGINT then :integer
1766
1797
  when ODBC::SQL_REAL, ODBC::SQL_FLOAT, ODBC::SQL_DOUBLE then :float
1767
-
1768
- when ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC then :float
1769
- # SQL_DECIMAL & SQL_NUMERIC are both exact types.
1770
- # Possible loss of precision if retrieved to :float
1771
-
1798
+ # If SQLGetTypeInfo output of ODBC driver doesn't include a mapping
1799
+ # to a native type from SQL_DECIMAL/SQL_NUMERIC, map to :float
1800
+ when ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC then scale.nil? || scale == 0 ? :integer :
1801
+ nativeTypes[:decimal].nil? ? :float : :decimal
1772
1802
  when ODBC::SQL_BINARY, ODBC::SQL_VARBINARY,
1773
- ODBC::SQL_LONGVARBINARY then :binary
1774
-
1775
- # SQL_DATETIME is an alias for SQL_DATE in ODBC's sql.h & sqlext.h
1803
+ ODBC::SQL_LONGVARBINARY then :binary
1804
+ # SQL_DATETIME is an alias for SQL_DATE in ODBC's sql.h & sqlext.h
1776
1805
  when ODBC::SQL_DATE, ODBC::SQL_TYPE_DATE,
1777
1806
  ODBC::SQL_DATETIME then :date
1778
1807
  when ODBC::SQL_TIME, ODBC::SQL_TYPE_TIME then :time
1779
- when ODBC::SQL_TIMESTAMP, ODBC::SQL_TYPE_TIMESTAMP then :timestamp
1780
-
1781
- when ODBC::SQL_GUID then :string
1782
-
1808
+ when ODBC::SQL_TIMESTAMP, ODBC::SQL_TYPE_TIMESTAMP then :timestamp
1809
+ when ODBC::SQL_GUID then :string
1783
1810
  else
1784
1811
  # when SQL_UNKNOWN_TYPE
1785
1812
  # (ruby-odbc driver doesn't support following ODBC SQL types:
@@ -1788,6 +1815,22 @@ begin
1788
1815
  raise ActiveRecordError, msg
1789
1816
  end
1790
1817
  end
1818
+
1819
+ def extract_precision(odbcSqlType, odbcPrecision)
1820
+ # Ignore the ODBC precision of SQL types which don't take
1821
+ # an explicit precision when defining a column
1822
+ case odbcSqlType
1823
+ when ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC then odbcPrecision
1824
+ end
1825
+ end
1826
+
1827
+ def extract_scale(odbcSqlType, odbcScale)
1828
+ # Ignore the ODBC scale of SQL types which don't take
1829
+ # an explicit scale when defining a column
1830
+ case odbcSqlType
1831
+ when ODBC::SQL_DECIMAL, ODBC::SQL_NUMERIC then odbcScale ? odbcScale : 0
1832
+ end
1833
+ end
1791
1834
 
1792
1835
  end # class ODBCColumn
1793
1836