activerecord 1.13.2 → 1.14.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (144) hide show
  1. data/CHANGELOG +452 -10
  2. data/RUNNING_UNIT_TESTS +1 -1
  3. data/lib/active_record.rb +5 -2
  4. data/lib/active_record/acts/list.rb +1 -1
  5. data/lib/active_record/acts/tree.rb +29 -25
  6. data/lib/active_record/aggregations.rb +3 -2
  7. data/lib/active_record/associations.rb +783 -337
  8. data/lib/active_record/associations/association_collection.rb +7 -12
  9. data/lib/active_record/associations/association_proxy.rb +62 -24
  10. data/lib/active_record/associations/belongs_to_association.rb +27 -46
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  12. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +38 -38
  13. data/lib/active_record/associations/has_many_association.rb +61 -56
  14. data/lib/active_record/associations/has_many_through_association.rb +144 -0
  15. data/lib/active_record/associations/has_one_association.rb +22 -16
  16. data/lib/active_record/base.rb +482 -182
  17. data/lib/active_record/calculations.rb +225 -0
  18. data/lib/active_record/callbacks.rb +7 -7
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +162 -47
  20. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -1
  22. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +21 -1
  23. data/lib/active_record/connection_adapters/abstract_adapter.rb +34 -2
  24. data/lib/active_record/connection_adapters/db2_adapter.rb +107 -61
  25. data/lib/active_record/connection_adapters/mysql_adapter.rb +29 -6
  26. data/lib/active_record/connection_adapters/openbase_adapter.rb +349 -0
  27. data/lib/active_record/connection_adapters/{oci_adapter.rb → oracle_adapter.rb} +125 -59
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +24 -21
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +47 -8
  30. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +36 -16
  31. data/lib/active_record/connection_adapters/sybase_adapter.rb +684 -0
  32. data/lib/active_record/fixtures.rb +42 -17
  33. data/lib/active_record/locking.rb +36 -15
  34. data/lib/active_record/migration.rb +111 -8
  35. data/lib/active_record/observer.rb +25 -1
  36. data/lib/active_record/reflection.rb +103 -41
  37. data/lib/active_record/schema.rb +2 -2
  38. data/lib/active_record/schema_dumper.rb +55 -18
  39. data/lib/active_record/timestamp.rb +6 -6
  40. data/lib/active_record/validations.rb +65 -40
  41. data/lib/active_record/vendor/db2.rb +10 -5
  42. data/lib/active_record/vendor/simple.rb +693 -702
  43. data/lib/active_record/version.rb +2 -2
  44. data/rakefile +4 -4
  45. data/test/aaa_create_tables_test.rb +25 -6
  46. data/test/abstract_unit.rb +39 -1
  47. data/test/adapter_test.rb +31 -4
  48. data/test/associations_cascaded_eager_loading_test.rb +106 -0
  49. data/test/associations_go_eager_test.rb +85 -16
  50. data/test/associations_join_model_test.rb +338 -0
  51. data/test/associations_test.rb +129 -50
  52. data/test/base_test.rb +204 -49
  53. data/test/binary_test.rb +1 -1
  54. data/test/calculations_test.rb +169 -0
  55. data/test/callbacks_test.rb +5 -23
  56. data/test/class_inheritable_attributes_test.rb +1 -1
  57. data/test/column_alias_test.rb +1 -1
  58. data/test/connections/native_mysql/connection.rb +1 -0
  59. data/test/connections/native_openbase/connection.rb +22 -0
  60. data/test/connections/{native_oci → native_oracle}/connection.rb +7 -9
  61. data/test/connections/native_sqlite/connection.rb +1 -1
  62. data/test/connections/native_sqlite3/connection.rb +1 -0
  63. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -0
  64. data/test/connections/native_sybase/connection.rb +24 -0
  65. data/test/defaults_test.rb +18 -0
  66. data/test/deprecated_associations_test.rb +2 -2
  67. data/test/deprecated_finder_test.rb +0 -6
  68. data/test/finder_test.rb +26 -23
  69. data/test/fixtures/accounts.yml +10 -0
  70. data/test/fixtures/author.rb +31 -6
  71. data/test/fixtures/author_favorites.yml +4 -0
  72. data/test/fixtures/categories/special_categories.yml +9 -0
  73. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  74. data/test/fixtures/categories_posts.yml +4 -0
  75. data/test/fixtures/categorization.rb +5 -0
  76. data/test/fixtures/categorizations.yml +11 -0
  77. data/test/fixtures/category.rb +6 -0
  78. data/test/fixtures/company.rb +17 -5
  79. data/test/fixtures/company_in_module.rb +19 -5
  80. data/test/fixtures/db_definitions/db2.drop.sql +3 -0
  81. data/test/fixtures/db_definitions/db2.sql +121 -100
  82. data/test/fixtures/db_definitions/db22.sql +2 -2
  83. data/test/fixtures/db_definitions/firebird.drop.sql +4 -0
  84. data/test/fixtures/db_definitions/firebird.sql +26 -0
  85. data/test/fixtures/db_definitions/mysql.drop.sql +3 -0
  86. data/test/fixtures/db_definitions/mysql.sql +21 -1
  87. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  88. data/test/fixtures/db_definitions/openbase.sql +282 -0
  89. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  90. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  91. data/test/fixtures/db_definitions/{oci.drop.sql → oracle.drop.sql} +6 -0
  92. data/test/fixtures/db_definitions/{oci.sql → oracle.sql} +25 -4
  93. data/test/fixtures/db_definitions/{oci2.drop.sql → oracle2.drop.sql} +0 -0
  94. data/test/fixtures/db_definitions/{oci2.sql → oracle2.sql} +0 -0
  95. data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
  96. data/test/fixtures/db_definitions/postgresql.sql +22 -1
  97. data/test/fixtures/db_definitions/schema.rb +32 -0
  98. data/test/fixtures/db_definitions/sqlite.drop.sql +3 -0
  99. data/test/fixtures/db_definitions/sqlite.sql +18 -0
  100. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  101. data/test/fixtures/db_definitions/sqlserver.sql +23 -3
  102. data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
  103. data/test/fixtures/db_definitions/sybase.sql +204 -0
  104. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  106. data/test/fixtures/developers.yml +6 -1
  107. data/test/fixtures/developers_projects.yml +4 -0
  108. data/test/fixtures/funny_jokes.yml +14 -0
  109. data/test/fixtures/joke.rb +6 -0
  110. data/test/fixtures/legacy_thing.rb +3 -0
  111. data/test/fixtures/legacy_things.yml +3 -0
  112. data/test/fixtures/mixin.rb +1 -1
  113. data/test/fixtures/person.rb +4 -1
  114. data/test/fixtures/post.rb +26 -1
  115. data/test/fixtures/project.rb +1 -0
  116. data/test/fixtures/reader.rb +4 -0
  117. data/test/fixtures/readers.yml +4 -0
  118. data/test/fixtures/reply.rb +2 -1
  119. data/test/fixtures/tag.rb +5 -0
  120. data/test/fixtures/tagging.rb +6 -0
  121. data/test/fixtures/taggings.yml +18 -0
  122. data/test/fixtures/tags.yml +7 -0
  123. data/test/fixtures/tasks.yml +2 -2
  124. data/test/fixtures/topic.rb +2 -2
  125. data/test/fixtures/topics.yml +1 -0
  126. data/test/fixtures_test.rb +47 -13
  127. data/test/inheritance_test.rb +2 -2
  128. data/test/locking_test.rb +15 -1
  129. data/test/method_scoping_test.rb +248 -13
  130. data/test/migration_test.rb +68 -11
  131. data/test/mixin_nested_set_test.rb +1 -1
  132. data/test/modules_test.rb +6 -1
  133. data/test/readonly_test.rb +1 -1
  134. data/test/reflection_test.rb +63 -9
  135. data/test/schema_dumper_test.rb +41 -0
  136. data/test/{synonym_test_oci.rb → synonym_test_oracle.rb} +1 -1
  137. data/test/threaded_connections_test.rb +10 -0
  138. data/test/unconnected_test.rb +12 -5
  139. data/test/validations_test.rb +197 -10
  140. metadata +295 -260
  141. data/test/fixtures/db_definitions/create_oracle_db.bat +0 -0
  142. data/test/fixtures/db_definitions/create_oracle_db.sh +0 -0
  143. data/test/fixtures/fixture_database.sqlite +0 -0
  144. data/test/fixtures/fixture_database_2.sqlite +0 -0
@@ -75,6 +75,11 @@ module ActiveRecord
75
75
  configure_connection
76
76
  end
77
77
  end
78
+
79
+ def disconnect!
80
+ # Both postgres and postgres-pr respond to :close
81
+ @connection.close rescue nil
82
+ end
78
83
 
79
84
  def native_database_types
80
85
  {
@@ -96,6 +101,9 @@ module ActiveRecord
96
101
  true
97
102
  end
98
103
 
104
+ def table_alias_length
105
+ 63
106
+ end
99
107
 
100
108
  # QUOTING ==================================================
101
109
 
@@ -296,19 +304,23 @@ module ActiveRecord
296
304
  end
297
305
 
298
306
  def add_column(table_name, column_name, type, options = {})
299
- native_type = native_database_types[type]
300
- sql_commands = ["ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}"]
301
- if options[:default]
302
- sql_commands << "ALTER TABLE #{table_name} ALTER #{column_name} SET DEFAULT '#{options[:default]}'"
303
- end
304
- if options[:null] == false
305
- sql_commands << "ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL"
306
- end
307
- sql_commands.each { |cmd| execute(cmd) }
307
+ execute("ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}")
308
+ execute("ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL") if options[:null] == false
309
+ change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
308
310
  end
309
311
 
310
312
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
311
- execute = "ALTER TABLE #{table_name} ALTER #{column_name} TYPE #{type}"
313
+ begin
314
+ execute "ALTER TABLE #{table_name} ALTER #{column_name} TYPE #{type_to_sql(type, options[:limit])}"
315
+ rescue ActiveRecord::StatementInvalid
316
+ # This is PG7, so we use a more arcane way of doing it.
317
+ begin_db_transaction
318
+ add_column(table_name, "#{column_name}_ar_tmp", type, options)
319
+ execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit])})"
320
+ remove_column(table_name, column_name)
321
+ rename_column(table_name, "#{column_name}_ar_tmp", column_name)
322
+ commit_db_transaction
323
+ end
312
324
  change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
313
325
  end
314
326
 
@@ -321,13 +333,7 @@ module ActiveRecord
321
333
  end
322
334
 
323
335
  def remove_index(table_name, options) #:nodoc:
324
- if Hash === options
325
- index_name = options[:name]
326
- else
327
- index_name = "#{table_name}_#{options}_index"
328
- end
329
-
330
- execute "DROP INDEX #{index_name}"
336
+ execute "DROP INDEX #{index_name(table_name, options)}"
331
337
  end
332
338
 
333
339
 
@@ -470,10 +476,7 @@ module ActiveRecord
470
476
  return $1 if value =~ /^'(.*)'::(bpchar|text|character varying)$/
471
477
 
472
478
  # Numeric values
473
- return value if value =~ /^[0-9]+(\.[0-9]*)?/
474
-
475
- # Date / Time magic values
476
- return Time.now.to_s if value =~ /^now\(\)|^\('now'::text\)::(date|timestamp)/i
479
+ return value if value =~ /^-?[0-9]+(\.[0-9]*)?/
477
480
 
478
481
  # Fixed dates / times
479
482
  return $1 if value =~ /^'(.+)'::(date|timestamp)/
@@ -36,7 +36,7 @@ module ActiveRecord
36
36
 
37
37
  # "Downgrade" deprecated sqlite API
38
38
  if SQLite.const_defined?(:Version)
39
- ConnectionAdapters::SQLiteAdapter.new(db, logger)
39
+ ConnectionAdapters::SQLite2Adapter.new(db, logger)
40
40
  else
41
41
  ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
42
42
  end
@@ -98,6 +98,10 @@ module ActiveRecord
98
98
  def supports_migrations? #:nodoc:
99
99
  true
100
100
  end
101
+
102
+ def supports_count_distinct? #:nodoc:
103
+ false
104
+ end
101
105
 
102
106
  def native_database_types #:nodoc:
103
107
  {
@@ -130,7 +134,7 @@ module ActiveRecord
130
134
  # DATABASE STATEMENTS ======================================
131
135
 
132
136
  def execute(sql, name = nil) #:nodoc:
133
- log(sql, name) { @connection.execute(sql) }
137
+ catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
134
138
  end
135
139
 
136
140
  def update(sql, name = nil) #:nodoc:
@@ -168,15 +172,15 @@ module ActiveRecord
168
172
 
169
173
 
170
174
  def begin_db_transaction #:nodoc:
171
- @connection.transaction
175
+ catch_schema_changes { @connection.transaction }
172
176
  end
173
177
 
174
178
  def commit_db_transaction #:nodoc:
175
- @connection.commit
179
+ catch_schema_changes { @connection.commit }
176
180
  end
177
181
 
178
182
  def rollback_db_transaction #:nodoc:
179
- @connection.rollback
183
+ catch_schema_changes { @connection.rollback }
180
184
  end
181
185
 
182
186
 
@@ -189,9 +193,9 @@ module ActiveRecord
189
193
  end
190
194
 
191
195
  def columns(table_name, name = nil) #:nodoc:
192
- table_structure(table_name).map { |field|
196
+ table_structure(table_name).map do |field|
193
197
  SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
194
- }
198
+ end
195
199
  end
196
200
 
197
201
  def indexes(table_name, name = nil) #:nodoc:
@@ -326,9 +330,44 @@ module ActiveRecord
326
330
  @connection.execute sql
327
331
  end
328
332
  end
333
+
334
+ def catch_schema_changes
335
+ return yield
336
+ rescue ActiveRecord::StatementInvalid => exception
337
+ if exception.message =~ /database schema has changed/
338
+ reconnect!
339
+ retry
340
+ else
341
+ raise
342
+ end
343
+ end
344
+ end
345
+
346
+ class SQLite2Adapter < SQLiteAdapter # :nodoc:
347
+ # SQLite 2 does not support COUNT(DISTINCT) queries:
348
+ #
349
+ # select COUNT(DISTINCT ArtistID) from CDs;
350
+ #
351
+ # In order to get the number of artists we execute the following statement
352
+ #
353
+ # SELECT COUNT(ArtistID) FROM (SELECT DISTINCT ArtistID FROM CDs);
354
+ def execute(sql, name = nil) #:nodoc:
355
+ super(rewrite_count_distinct_queries(sql), name)
356
+ end
357
+
358
+ def rewrite_count_distinct_queries(sql)
359
+ if sql =~ /count\(distinct ([^\)]+)\)( AS \w+)? (.*)/i
360
+ distinct_column = $1
361
+ distinct_query = $3
362
+ column_name = distinct_column.split('.').last
363
+ "SELECT COUNT(#{column_name}) FROM (SELECT DISTINCT #{distinct_column} #{distinct_query})"
364
+ else
365
+ sql
366
+ end
367
+ end
329
368
  end
330
369
 
331
- class DeprecatedSQLiteAdapter < SQLiteAdapter # :nodoc:
370
+ class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
332
371
  def insert(sql, name = nil, pk = nil, id_value = nil)
333
372
  execute(sql, name = nil)
334
373
  id_value || @connection.last_insert_rowid
@@ -27,6 +27,7 @@ module ActiveRecord
27
27
  mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
28
28
  username = config[:username] ? config[:username].to_s : 'sa'
29
29
  password = config[:password] ? config[:password].to_s : ''
30
+ autocommit = config.key?(:autocommit) ? config[:autocommit] : true
30
31
  if mode == "ODBC"
31
32
  raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn)
32
33
  dsn = config[:dsn]
@@ -38,8 +39,7 @@ module ActiveRecord
38
39
  driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};"
39
40
  end
40
41
  conn = DBI.connect(driver_url, username, password)
41
-
42
- conn["AutoCommit"] = true
42
+ conn["AutoCommit"] = autocommit
43
43
  ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password])
44
44
  end
45
45
  end # class Base
@@ -48,8 +48,8 @@ module ActiveRecord
48
48
  class ColumnWithIdentity < Column# :nodoc:
49
49
  attr_reader :identity, :is_special, :scale
50
50
 
51
- def initialize(name, default, sql_type = nil, is_identity = false, scale_value = 0)
52
- super(name, default, sql_type)
51
+ def initialize(name, default, sql_type = nil, is_identity = false, null = true, scale_value = 0)
52
+ super(name, default, sql_type, null)
53
53
  @identity = is_identity
54
54
  @is_special = sql_type =~ /text|ntext|image/i ? true : false
55
55
  @scale = scale_value
@@ -216,12 +216,18 @@ module ActiveRecord
216
216
 
217
217
  # Reconnects to the database, returns false if no connection could be made.
218
218
  def reconnect!
219
- @connection.disconnect rescue nil
219
+ disconnect!
220
220
  @connection = DBI.connect(*@connection_options)
221
221
  rescue DBI::DatabaseError => e
222
222
  @logger.warn "#{adapter_name} reconnection failed: #{e.message}" if @logger
223
223
  false
224
224
  end
225
+
226
+ # Disconnects from the database
227
+
228
+ def disconnect!
229
+ @connection.disconnect rescue nil
230
+ end
225
231
 
226
232
  def select_all(sql, name = nil)
227
233
  select(sql, name)
@@ -237,14 +243,20 @@ module ActiveRecord
237
243
  return [] if table_name.blank?
238
244
  table_name = table_name.to_s if table_name.is_a?(Symbol)
239
245
  table_name = table_name.split('.')[-1] unless table_name.nil?
240
- sql = "SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, DATA_TYPE as ColType, COL_LENGTH('#{table_name}', COLUMN_NAME) as Length, COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity, NUMERIC_SCALE as Scale FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '#{table_name}'"
246
+ sql = "SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, DATA_TYPE as ColType, IS_NULLABLE As IsNullable, COL_LENGTH('#{table_name}', COLUMN_NAME) as Length, COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity, NUMERIC_SCALE as Scale FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '#{table_name}'"
241
247
  # Comment out if you want to have the Columns select statment logged.
242
- # Personnally, I think it adds unneccessary bloat to the log.
248
+ # Personally, I think it adds unnecessary bloat to the log.
243
249
  # If you do comment it out, make sure to un-comment the "result" line that follows
244
250
  result = log(sql, name) { @connection.select_all(sql) }
245
251
  #result = @connection.select_all(sql)
246
252
  columns = []
247
- result.each { |field| columns << ColumnWithIdentity.new(field[:ColName], field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue], "#{field[:ColType]}(#{field[:Length]})", field[:IsIdentity] == 1 ? true : false, field[:Scale]) }
253
+ result.each do |field|
254
+ default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue]
255
+ type = "#{field[:ColType]}(#{field[:Length]})"
256
+ is_identity = field[:IsIdentity] == 1
257
+ is_nullable = field[:IsNullable] == 'YES'
258
+ columns << ColumnWithIdentity.new(field[:ColName], default, type, is_identity, is_nullable, field[:Scale])
259
+ end
248
260
  columns
249
261
  end
250
262
 
@@ -318,7 +330,7 @@ module ActiveRecord
318
330
  def quote(value, column = nil)
319
331
  case value
320
332
  when String
321
- if column && column.type == :binary
333
+ if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
322
334
  "'#{quote_string(column.class.string_to_binary(value))}'"
323
335
  else
324
336
  "'#{quote_string(value)}'"
@@ -375,7 +387,9 @@ module ActiveRecord
375
387
  sql << " ) AS tmp2"
376
388
  end
377
389
  elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
378
- sql.sub!(/^\s*SELECT/i, "SELECT TOP #{options[:limit]}") unless options[:limit].nil?
390
+ sql.sub!(/^\s*SELECT([\s]*distinct)?/i) do
391
+ "SELECT#{$1} TOP #{options[:limit]}"
392
+ end unless options[:limit].nil?
379
393
  end
380
394
  end
381
395
 
@@ -391,6 +405,10 @@ module ActiveRecord
391
405
  def create_database(name)
392
406
  execute "CREATE DATABASE #{name}"
393
407
  end
408
+
409
+ def current_database
410
+ @connection.select_one("select DB_NAME()")[0]
411
+ end
394
412
 
395
413
  def tables(name = nil)
396
414
  execute("SELECT table_name from information_schema.tables WHERE table_type = 'BASE TABLE'", name).inject([]) do |tables, field|
@@ -454,7 +472,7 @@ module ActiveRecord
454
472
  def type_to_sql(type, limit = nil) #:nodoc:
455
473
  native = native_database_types[type]
456
474
  # if there's no :limit in the default type definition, assume that type doesn't support limits
457
- limit = native[:limit] ? limit || native[:limit] : nil
475
+ limit = limit || native[:limit]
458
476
  column_type_sql = native[:name]
459
477
  column_type_sql << "(#{limit})" if limit
460
478
  column_type_sql
@@ -512,11 +530,13 @@ module ActiveRecord
512
530
  end
513
531
 
514
532
  def change_order_direction(order)
515
- case order
516
- when /\bDESC\b/i then order.gsub(/\bDESC\b/i, "ASC")
517
- when /\bASC\b/i then order.gsub(/\bASC\b/i, "DESC")
518
- else String.new(order).split(',').join(' DESC,') + ' DESC'
519
- end
533
+ order.split(",").collect {|fragment|
534
+ case fragment
535
+ when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
536
+ when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
537
+ else String.new(fragment).split(',').join(' DESC,') + ' DESC'
538
+ end
539
+ }.join(",")
520
540
  end
521
541
 
522
542
  def get_special_columns(table_name)
@@ -0,0 +1,684 @@
1
+ # sybase_adaptor.rb
2
+ # Author: John Sheets <dev@metacasa.net>
3
+ # Date: 01 Mar 2006
4
+ #
5
+ # Based on code from Will Sobel (http://dev.rubyonrails.org/ticket/2030)
6
+ #
7
+ # 17 Mar 2006: Added support for migrations; fixed issues with :boolean columns.
8
+ #
9
+
10
+ require 'active_record/connection_adapters/abstract_adapter'
11
+
12
+ begin
13
+ require 'sybsql'
14
+
15
+ module ActiveRecord
16
+ class Base
17
+ # Establishes a connection to the database that's used by all Active Record objects
18
+ def self.sybase_connection(config) # :nodoc:
19
+ config = config.symbolize_keys
20
+
21
+ username = config[:username] ? config[:username].to_s : 'sa'
22
+ password = config[:password] ? config[:password].to_s : ''
23
+
24
+ if config.has_key?(:host)
25
+ host = config[:host]
26
+ else
27
+ raise ArgumentError, "No database server name specified. Missing argument: host."
28
+ end
29
+
30
+ if config.has_key?(:database)
31
+ database = config[:database]
32
+ else
33
+ raise ArgumentError, "No database specified. Missing argument: database."
34
+ end
35
+
36
+ ConnectionAdapters::SybaseAdapter.new(
37
+ SybSQL.new({'S' => host, 'U' => username, 'P' => password},
38
+ ConnectionAdapters::SybaseAdapterContext), database, logger)
39
+ end
40
+ end # class Base
41
+
42
+ module ConnectionAdapters
43
+
44
+ # ActiveRecord connection adapter for Sybase Open Client bindings
45
+ # (see http://raa.ruby-lang.org/project/sybase-ctlib).
46
+ #
47
+ # Options:
48
+ #
49
+ # * <tt>:host</tt> -- The name of the database server. No default, must be provided.
50
+ # * <tt>:database</tt> -- The name of the database. No default, must be provided.
51
+ # * <tt>:username</tt> -- Defaults to sa.
52
+ # * <tt>:password</tt> -- Defaults to empty string.
53
+ #
54
+ # Usage Notes:
55
+ #
56
+ # * The sybase-ctlib bindings do not support the DATE SQL column type; use DATETIME instead.
57
+ # * Table and column names are limited to 30 chars in Sybase 12.5
58
+ # * :binary columns not yet supported
59
+ # * :boolean columns use the BIT SQL type, which does not allow nulls or
60
+ # indexes. If a DEFAULT is not specified for ALTER TABLE commands, the
61
+ # column will be declared with DEFAULT 0 (false).
62
+ #
63
+ # Migrations:
64
+ #
65
+ # The Sybase adapter supports migrations, but for ALTER TABLE commands to
66
+ # work, the database must have the database option 'select into' set to
67
+ # 'true' with sp_dboption (see below). The sp_helpdb command lists the current
68
+ # options for all databases.
69
+ #
70
+ # 1> use mydb
71
+ # 2> go
72
+ # 1> master..sp_dboption mydb, "select into", true
73
+ # 2> go
74
+ # 1> checkpoint
75
+ # 2> go
76
+ class SybaseAdapter < AbstractAdapter # :nodoc:
77
+ class ColumnWithIdentity < Column
78
+ attr_reader :identity, :primary
79
+
80
+ def initialize(name, default, sql_type = nil, nullable = nil, identity = nil, primary = nil)
81
+ super(name, default, sql_type, nullable)
82
+ @default, @identity, @primary = type_cast(default), identity, primary
83
+ end
84
+
85
+ def simplified_type(field_type)
86
+ case field_type
87
+ when /int|bigint|smallint|tinyint/i then :integer
88
+ when /float|double|decimal|money|numeric|real|smallmoney/i then :float
89
+ when /text|ntext/i then :text
90
+ when /binary|image|varbinary/i then :binary
91
+ when /char|nchar|nvarchar|string|varchar/i then :string
92
+ when /bit/i then :boolean
93
+ when /datetime|smalldatetime/i then :datetime
94
+ else super
95
+ end
96
+ end
97
+
98
+ def self.string_to_binary(value)
99
+ "0x#{value.unpack("H*")[0]}"
100
+ end
101
+
102
+ def self.binary_to_string(value)
103
+ # FIXME: sybase-ctlib uses separate sql method for binary columns.
104
+ value
105
+ end
106
+ end # class ColumnWithIdentity
107
+
108
+ # Sybase adapter
109
+ def initialize(connection, database, logger = nil)
110
+ super(connection, logger)
111
+ context = connection.context
112
+ context.init(logger)
113
+ @limit = @offset = 0
114
+ unless connection.sql_norow("USE #{database}")
115
+ raise "Cannot USE #{database}"
116
+ end
117
+ end
118
+
119
+ def native_database_types
120
+ {
121
+ :primary_key => "numeric(9,0) IDENTITY PRIMARY KEY",
122
+ :string => { :name => "varchar", :limit => 255 },
123
+ :text => { :name => "text" },
124
+ :integer => { :name => "int" },
125
+ :float => { :name => "float", :limit => 8 },
126
+ :datetime => { :name => "datetime" },
127
+ :timestamp => { :name => "timestamp" },
128
+ :time => { :name => "time" },
129
+ :date => { :name => "datetime" },
130
+ :binary => { :name => "image"},
131
+ :boolean => { :name => "bit" }
132
+ }
133
+ end
134
+
135
+ def adapter_name
136
+ 'Sybase'
137
+ end
138
+
139
+ def active?
140
+ !(@connection.connection.nil? || @connection.connection_dead?)
141
+ end
142
+
143
+ def disconnect!
144
+ @connection.close rescue nil
145
+ end
146
+
147
+ def reconnect!
148
+ raise "Sybase Connection Adapter does not yet support reconnect!"
149
+ # disconnect!
150
+ # connect! # Not yet implemented
151
+ end
152
+
153
+ def table_alias_length
154
+ 30
155
+ end
156
+
157
+ # Check for a limit statement and parse out the limit and
158
+ # offset if specified. Remove the limit from the sql statement
159
+ # and call select.
160
+ def select_all(sql, name = nil)
161
+ select(sql, name)
162
+ end
163
+
164
+ # Remove limit clause from statement. This will almost always
165
+ # contain LIMIT 1 from the caller. set the rowcount to 1 before
166
+ # calling select.
167
+ def select_one(sql, name = nil)
168
+ result = select(sql, name)
169
+ result.nil? ? nil : result.first
170
+ end
171
+
172
+ def columns(table_name, name = nil)
173
+ table_structure(table_name).inject([]) do |columns, column|
174
+ name, default, type, nullable, identity, primary = column
175
+ columns << ColumnWithIdentity.new(name, default, type, nullable, identity, primary)
176
+ columns
177
+ end
178
+ end
179
+
180
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
181
+ begin
182
+ table_name = get_table_name(sql)
183
+ col = get_identity_column(table_name)
184
+ ii_enabled = false
185
+
186
+ if col != nil
187
+ if query_contains_identity_column(sql, col)
188
+ begin
189
+ execute enable_identity_insert(table_name, true)
190
+ ii_enabled = true
191
+ rescue Exception => e
192
+ raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON"
193
+ end
194
+ end
195
+ end
196
+
197
+ log(sql, name) do
198
+ execute(sql, name)
199
+ ident = select_one("SELECT @@IDENTITY AS last_id")["last_id"]
200
+ id_value || ident
201
+ end
202
+ ensure
203
+ if ii_enabled
204
+ begin
205
+ execute enable_identity_insert(table_name, false)
206
+ rescue Exception => e
207
+ raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF"
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ def execute(sql, name = nil)
214
+ log(sql, name) do
215
+ @connection.context.reset
216
+ @connection.set_rowcount(@limit || 0)
217
+ @limit = @offset = nil
218
+ @connection.sql_norow(sql)
219
+ if @connection.cmd_fail? or @connection.context.failed?
220
+ raise "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
221
+ end
222
+ end
223
+ # Return rows affected
224
+ @connection.results[0].row_count
225
+ end
226
+
227
+ alias_method :update, :execute
228
+ alias_method :delete, :execute
229
+
230
+ def begin_db_transaction() execute "BEGIN TRAN" end
231
+ def commit_db_transaction() execute "COMMIT TRAN" end
232
+ def rollback_db_transaction() execute "ROLLBACK TRAN" end
233
+
234
+ def tables(name = nil)
235
+ tables = []
236
+ select("select name from sysobjects where type='U'", name).each do |row|
237
+ tables << row['name']
238
+ end
239
+ tables
240
+ end
241
+
242
+ def indexes(table_name, name = nil)
243
+ indexes = []
244
+ select("exec sp_helpindex #{table_name}", name).each do |index|
245
+ unique = index["index_description"] =~ /unique/
246
+ primary = index["index_description"] =~ /^clustered/
247
+ if !primary
248
+ cols = index["index_keys"].split(", ").each { |col| col.strip! }
249
+ indexes << IndexDefinition.new(table_name, index["index_name"], unique, cols)
250
+ end
251
+ end
252
+ indexes
253
+ end
254
+
255
+ def quoted_true
256
+ "1"
257
+ end
258
+
259
+ def quoted_false
260
+ "0"
261
+ end
262
+
263
+ def quote(value, column = nil)
264
+ case value
265
+ when String
266
+ if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
267
+ "#{quote_string(column.class.string_to_binary(value))}"
268
+ elsif value =~ /^[+-]?[0-9]+$/o
269
+ value
270
+ else
271
+ "'#{quote_string(value)}'"
272
+ end
273
+ when NilClass then (column && column.type == :boolean) ? '0' : "NULL"
274
+ when TrueClass then '1'
275
+ when FalseClass then '0'
276
+ when Float, Fixnum, Bignum then value.to_s
277
+ when Date then "'#{value.to_s}'"
278
+ when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
279
+ else "'#{quote_string(value.to_yaml)}'"
280
+ end
281
+ end
282
+
283
+ def quote_column(type, value)
284
+ case type
285
+ when :boolean
286
+ case value
287
+ when String then value =~ /^[ty]/o ? 1 : 0
288
+ when true then 1
289
+ when false then 0
290
+ else value.to_i
291
+ end
292
+ when :integer then value.to_i
293
+ when :float then value.to_f
294
+ when :text, :string, :enum
295
+ case value
296
+ when String, Symbol, Fixnum, Float, Bignum, TrueClass, FalseClass
297
+ "'#{quote_string(value.to_s)}'"
298
+ else
299
+ "'#{quote_string(value.to_yaml)}'"
300
+ end
301
+ when :date, :datetime, :time
302
+ case value
303
+ when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
304
+ when Date then "'#{value.to_s}'"
305
+ else "'#{quote_string(value)}'"
306
+ end
307
+ else "'#{quote_string(value.to_yaml)}'"
308
+ end
309
+ end
310
+
311
+ def quote_string(s)
312
+ s.gsub(/'/, "''") # ' (for ruby-mode)
313
+ end
314
+
315
+ def quote_column_name(name)
316
+ "[#{name}]"
317
+ end
318
+
319
+ def add_limit_offset!(sql, options) # :nodoc:
320
+ @limit = options[:limit]
321
+ @offset = options[:offset]
322
+ if !normal_select?
323
+ # Use temp table to hack offset with Sybase
324
+ sql.sub!(/ FROM /i, ' INTO #artemp FROM ')
325
+ elsif zero_limit?
326
+ # "SET ROWCOUNT 0" turns off limits, so we have
327
+ # to use a cheap trick.
328
+ if sql =~ /WHERE/i
329
+ sql.sub!(/WHERE/i, 'WHERE 1 = 2 AND ')
330
+ elsif sql =~ /ORDER\s+BY/i
331
+ sql.sub!(/ORDER\s+BY/i, 'WHERE 1 = 2 ORDER BY')
332
+ else
333
+ sql << 'WHERE 1 = 2'
334
+ end
335
+ end
336
+ end
337
+
338
+ def supports_migrations? #:nodoc:
339
+ true
340
+ end
341
+
342
+ def rename_table(name, new_name)
343
+ execute "EXEC sp_rename '#{name}', '#{new_name}'"
344
+ end
345
+
346
+ def rename_column(table, column, new_column_name)
347
+ execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
348
+ end
349
+
350
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
351
+ sql_commands = ["ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"]
352
+ if options[:default]
353
+ remove_default_constraint(table_name, column_name)
354
+ sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{options[:default]} FOR #{column_name}"
355
+ end
356
+ sql_commands.each { |c| execute(c) }
357
+ end
358
+
359
+ def remove_column(table_name, column_name)
360
+ remove_default_constraint(table_name, column_name)
361
+ execute "ALTER TABLE #{table_name} DROP #{column_name}"
362
+ end
363
+
364
+ def remove_default_constraint(table_name, column_name)
365
+ defaults = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
366
+ defaults.each {|constraint|
367
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
368
+ }
369
+ end
370
+
371
+ def remove_index(table_name, options = {})
372
+ execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
373
+ end
374
+
375
+ def add_column_options!(sql, options) #:nodoc:
376
+ sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?
377
+
378
+ if check_null_for_column?(options[:column], sql)
379
+ sql << (options[:null] == false ? " NOT NULL" : " NULL")
380
+ end
381
+ sql
382
+ end
383
+
384
+ private
385
+ def check_null_for_column?(col, sql)
386
+ # Sybase columns are NOT NULL by default, so explicitly set NULL
387
+ # if :null option is omitted. Disallow NULLs for boolean.
388
+ type = col.nil? ? "" : col[:type]
389
+
390
+ # Ignore :null if a primary key
391
+ return false if type =~ /PRIMARY KEY/i
392
+
393
+ # Ignore :null if a :boolean or BIT column
394
+ if (sql =~ /\s+bit(\s+DEFAULT)?/i) || type == :boolean
395
+ # If no default clause found on a boolean column, add one.
396
+ sql << " DEFAULT 0" if $1.nil?
397
+ return false
398
+ end
399
+ true
400
+ end
401
+
402
+ # Return the last value of the identity global value.
403
+ def last_insert_id
404
+ @connection.sql("SELECT @@IDENTITY")
405
+ unless @connection.cmd_fail?
406
+ id = @connection.top_row_result.rows.first.first
407
+ if id
408
+ id = id.to_i
409
+ id = nil if id == 0
410
+ end
411
+ else
412
+ id = nil
413
+ end
414
+ id
415
+ end
416
+
417
+ def affected_rows(name = nil)
418
+ @connection.sql("SELECT @@ROWCOUNT")
419
+ unless @connection.cmd_fail?
420
+ count = @connection.top_row_result.rows.first.first
421
+ count = count.to_i if count
422
+ else
423
+ 0
424
+ end
425
+ end
426
+
427
+ def normal_select?
428
+ # If limit is not set at all, we can ignore offset;
429
+ # If limit *is* set but offset is zero, use normal select
430
+ # with simple SET ROWCOUNT. Thus, only use the temp table
431
+ # if limit is set and offset > 0.
432
+ has_limit = !@limit.nil?
433
+ has_offset = !@offset.nil? && @offset > 0
434
+ !has_limit || !has_offset
435
+ end
436
+
437
+ def zero_limit?
438
+ !@limit.nil? && @limit == 0
439
+ end
440
+
441
+ # Select limit number of rows starting at optional offset.
442
+ def select(sql, name = nil)
443
+ @connection.context.reset
444
+ log(sql, name) do
445
+ if normal_select?
446
+ # If limit is not explicitly set, return all results.
447
+ @logger.debug "Setting row count to (#{@limit || 'off'})" if @logger
448
+
449
+ # Run a normal select
450
+ @connection.set_rowcount(@limit || 0)
451
+ @connection.sql(sql)
452
+ else
453
+ # Select into a temp table and prune results
454
+ @logger.debug "Selecting #{@limit + (@offset || 0)} or fewer rows into #artemp" if @logger
455
+ @connection.set_rowcount(@limit + (@offset || 0))
456
+ @connection.sql_norow(sql) # Select into temp table
457
+ @logger.debug "Deleting #{@offset || 0} or fewer rows from #artemp" if @logger
458
+ @connection.set_rowcount(@offset || 0)
459
+ @connection.sql_norow("delete from #artemp") # Delete leading rows
460
+ @connection.set_rowcount(0)
461
+ @connection.sql("select * from #artemp") # Return the rest
462
+ end
463
+ end
464
+
465
+ rows = []
466
+ if @connection.context.failed? or @connection.cmd_fail?
467
+ raise StatementInvalid, "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
468
+ else
469
+ results = @connection.top_row_result
470
+ if results && results.rows.length > 0
471
+ fields = fixup_column_names(results.columns)
472
+ results.rows.each do |row|
473
+ hashed_row = {}
474
+ row.zip(fields) { |cell, column| hashed_row[column] = cell }
475
+ rows << hashed_row
476
+ end
477
+ end
478
+ end
479
+ @connection.sql_norow("drop table #artemp") if !normal_select?
480
+ @limit = @offset = nil
481
+ return rows
482
+ end
483
+
484
+ def enable_identity_insert(table_name, enable = true)
485
+ if has_identity_column(table_name)
486
+ "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
487
+ end
488
+ end
489
+
490
+ def get_table_name(sql)
491
+ if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
492
+ $1
493
+ elsif sql =~ /from\s+([^\(\s]+)\s*/i
494
+ $1
495
+ else
496
+ nil
497
+ end
498
+ end
499
+
500
+ def has_identity_column(table_name)
501
+ !get_identity_column(table_name).nil?
502
+ end
503
+
504
+ def get_identity_column(table_name)
505
+ @table_columns = {} unless @table_columns
506
+ @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
507
+ @table_columns[table_name].each do |col|
508
+ return col.name if col.identity
509
+ end
510
+
511
+ return nil
512
+ end
513
+
514
+ def query_contains_identity_column(sql, col)
515
+ sql =~ /\[#{col}\]/
516
+ end
517
+
518
+ # Remove trailing _ from names.
519
+ def fixup_column_names(columns)
520
+ columns.map { |column| column.sub(/_$/, '') }
521
+ end
522
+
523
+ def table_structure(table_name)
524
+ sql = <<SQLTEXT
525
+ SELECT col.name AS name, type.name AS type, col.prec, col.scale, col.length,
526
+ col.status, obj.sysstat2, def.text
527
+ FROM sysobjects obj, syscolumns col, systypes type, syscomments def
528
+ WHERE obj.id = col.id AND col.usertype = type.usertype AND col.cdefault *= def.id
529
+ AND obj.type = 'U' AND obj.name = '#{table_name}' ORDER BY col.colid
530
+ SQLTEXT
531
+ log(sql, "Get Column Info ") do
532
+ @connection.set_rowcount(0)
533
+ @connection.sql(sql)
534
+ end
535
+ if @connection.context.failed?
536
+ raise "SQL Command for table_structure for #{table_name} failed\nMessage: #{@connection.context.message}"
537
+ elsif !@connection.cmd_fail?
538
+ columns = []
539
+ results = @connection.top_row_result
540
+ results.rows.each do |row|
541
+ name, type, prec, scale, length, status, sysstat2, default = row
542
+ type = normalize_type(type, prec, scale, length)
543
+ default_value = nil
544
+ name.sub!(/_$/o, '')
545
+ if default =~ /DEFAULT\s+(.+)/o
546
+ default_value = $1.strip
547
+ default_value = default_value[1...-1] if default_value =~ /^['"]/o
548
+ end
549
+ nullable = (status & 8) == 8
550
+ identity = status >= 128
551
+ primary = (sysstat2 & 8) == 8
552
+
553
+ columns << [name, default_value, type, nullable, identity, primary]
554
+ end
555
+ columns
556
+ else
557
+ nil
558
+ end
559
+ end
560
+
561
+ def normalize_type(field_type, prec, scale, length)
562
+ if field_type =~ /numeric/i and (scale.nil? or scale == 0)
563
+ type = 'int'
564
+ elsif field_type =~ /money/i
565
+ type = 'numeric'
566
+ else
567
+ type = field_type
568
+ end
569
+ size = ''
570
+ if prec
571
+ size = "(#{prec})"
572
+ elsif length
573
+ size = "(#{length})"
574
+ end
575
+ return type + size
576
+ end
577
+
578
+ def default_value(value)
579
+ end
580
+ end # class SybaseAdapter
581
+
582
+ class SybaseAdapterContext < SybSQLContext
583
+ DEADLOCK = 1205
584
+ attr_reader :message
585
+
586
+ def init(logger = nil)
587
+ @deadlocked = false
588
+ @failed = false
589
+ @logger = logger
590
+ @message = nil
591
+ end
592
+
593
+ def srvmsgCB(con, msg)
594
+ # Do not log change of context messages.
595
+ if msg['severity'] == 10 or msg['severity'] == 0
596
+ return true
597
+ end
598
+
599
+ if msg['msgnumber'] == DEADLOCK
600
+ @deadlocked = true
601
+ else
602
+ @logger.info "SQL Command failed!" if @logger
603
+ @failed = true
604
+ end
605
+
606
+ if @logger
607
+ @logger.error "** SybSQLContext Server Message: **"
608
+ @logger.error " Message number #{msg['msgnumber']} Severity #{msg['severity']} State #{msg['state']} Line #{msg['line']}"
609
+ @logger.error " Server #{msg['srvname']}"
610
+ @logger.error " Procedure #{msg['proc']}"
611
+ @logger.error " Message String: #{msg['text']}"
612
+ end
613
+
614
+ @message = msg['text']
615
+
616
+ true
617
+ end
618
+
619
+ def deadlocked?
620
+ @deadlocked
621
+ end
622
+
623
+ def failed?
624
+ @failed
625
+ end
626
+
627
+ def reset
628
+ @deadlocked = false
629
+ @failed = false
630
+ @message = nil
631
+ end
632
+
633
+ def cltmsgCB(con, msg)
634
+ return true unless ( msg.kind_of?(Hash) )
635
+ unless ( msg[ "severity" ] ) then
636
+ return true
637
+ end
638
+
639
+ if @logger
640
+ @logger.error "** SybSQLContext Client-Message: **"
641
+ @logger.error " Message number: LAYER=#{msg[ 'layer' ]} ORIGIN=#{msg[ 'origin' ]} SEVERITY=#{msg[ 'severity' ]} NUMBER=#{msg[ 'number' ]}"
642
+ @logger.error " Message String: #{msg['msgstring']}"
643
+ @logger.error " OS Error: #{msg['osstring']}"
644
+
645
+ @message = msg['msgstring']
646
+ end
647
+
648
+ @failed = true
649
+
650
+ # Not retry , CS_CV_RETRY_FAIL( probability TimeOut )
651
+ if( msg[ 'severity' ] == "RETRY_FAIL" ) then
652
+ @timeout_p = true
653
+ return false
654
+ end
655
+
656
+ return true
657
+ end
658
+ end # class SybaseAdapterContext
659
+
660
+ end # module ConnectionAdapters
661
+ end # module ActiveRecord
662
+
663
+
664
+ # Allow identity inserts for fixtures.
665
+ require "active_record/fixtures"
666
+ class Fixtures
667
+ alias :original_insert_fixtures :insert_fixtures
668
+
669
+ def insert_fixtures
670
+ values.each do |fixture|
671
+ allow_identity_inserts table_name, true
672
+ @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
673
+ allow_identity_inserts table_name, false
674
+ end
675
+ end
676
+
677
+ def allow_identity_inserts(table_name, enable)
678
+ @connection.execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}" rescue nil
679
+ end
680
+ end
681
+
682
+ rescue LoadError => cannot_require_sybase
683
+ # Couldn't load sybase adapter
684
+ end