activerecord 1.14.4 → 1.15.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 (159) hide show
  1. data/CHANGELOG +400 -1
  2. data/README +2 -2
  3. data/RUNNING_UNIT_TESTS +21 -3
  4. data/Rakefile +55 -10
  5. data/lib/active_record.rb +10 -4
  6. data/lib/active_record/acts/list.rb +15 -4
  7. data/lib/active_record/acts/nested_set.rb +11 -12
  8. data/lib/active_record/acts/tree.rb +13 -14
  9. data/lib/active_record/aggregations.rb +46 -22
  10. data/lib/active_record/associations.rb +213 -162
  11. data/lib/active_record/associations/association_collection.rb +45 -15
  12. data/lib/active_record/associations/association_proxy.rb +32 -13
  13. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
  14. data/lib/active_record/associations/has_many_association.rb +37 -17
  15. data/lib/active_record/associations/has_many_through_association.rb +120 -30
  16. data/lib/active_record/associations/has_one_association.rb +1 -1
  17. data/lib/active_record/attribute_methods.rb +75 -0
  18. data/lib/active_record/base.rb +282 -203
  19. data/lib/active_record/calculations.rb +95 -54
  20. data/lib/active_record/callbacks.rb +13 -24
  21. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
  24. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
  28. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
  29. data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
  30. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  31. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
  32. data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
  33. data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
  34. data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
  35. data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
  36. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
  37. data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
  38. data/lib/active_record/deprecated_associations.rb +24 -10
  39. data/lib/active_record/deprecated_finders.rb +4 -1
  40. data/lib/active_record/fixtures.rb +37 -23
  41. data/lib/active_record/locking/optimistic.rb +106 -0
  42. data/lib/active_record/locking/pessimistic.rb +77 -0
  43. data/lib/active_record/migration.rb +8 -5
  44. data/lib/active_record/observer.rb +73 -34
  45. data/lib/active_record/reflection.rb +21 -7
  46. data/lib/active_record/schema_dumper.rb +33 -5
  47. data/lib/active_record/timestamp.rb +23 -34
  48. data/lib/active_record/transactions.rb +37 -30
  49. data/lib/active_record/validations.rb +46 -30
  50. data/lib/active_record/vendor/mysql.rb +20 -5
  51. data/lib/active_record/version.rb +2 -2
  52. data/lib/active_record/wrappings.rb +1 -2
  53. data/lib/active_record/xml_serialization.rb +308 -0
  54. data/test/aaa_create_tables_test.rb +5 -1
  55. data/test/abstract_unit.rb +18 -8
  56. data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
  57. data/test/adapter_test.rb +9 -7
  58. data/test/adapter_test_sqlserver.rb +81 -0
  59. data/test/aggregations_test.rb +29 -0
  60. data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
  61. data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
  62. data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
  63. data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
  64. data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
  65. data/test/associations_test.rb +339 -45
  66. data/test/attribute_methods_test.rb +49 -0
  67. data/test/base_test.rb +321 -67
  68. data/test/calculations_test.rb +48 -10
  69. data/test/callbacks_test.rb +13 -0
  70. data/test/connection_test_firebird.rb +8 -0
  71. data/test/connections/native_db2/connection.rb +18 -17
  72. data/test/connections/native_firebird/connection.rb +19 -17
  73. data/test/connections/native_frontbase/connection.rb +27 -0
  74. data/test/connections/native_mysql/connection.rb +18 -15
  75. data/test/connections/native_openbase/connection.rb +14 -15
  76. data/test/connections/native_oracle/connection.rb +16 -12
  77. data/test/connections/native_postgresql/connection.rb +16 -17
  78. data/test/connections/native_sqlite/connection.rb +3 -6
  79. data/test/connections/native_sqlite3/connection.rb +3 -6
  80. data/test/connections/native_sqlserver/connection.rb +16 -17
  81. data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
  82. data/test/connections/native_sybase/connection.rb +16 -17
  83. data/test/datatype_test_postgresql.rb +52 -0
  84. data/test/defaults_test.rb +52 -10
  85. data/test/deprecated_associations_test.rb +151 -107
  86. data/test/deprecated_finder_test.rb +83 -66
  87. data/test/empty_date_time_test.rb +25 -0
  88. data/test/finder_test.rb +118 -11
  89. data/test/fixtures/accounts.yml +6 -1
  90. data/test/fixtures/author.rb +27 -4
  91. data/test/fixtures/categorizations.yml +8 -2
  92. data/test/fixtures/category.rb +1 -2
  93. data/test/fixtures/comments.yml +0 -6
  94. data/test/fixtures/companies.yml +6 -1
  95. data/test/fixtures/company.rb +23 -1
  96. data/test/fixtures/company_in_module.rb +8 -10
  97. data/test/fixtures/customer.rb +2 -2
  98. data/test/fixtures/customers.yml +9 -0
  99. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  100. data/test/fixtures/db_definitions/db2.sql +9 -0
  101. data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
  102. data/test/fixtures/db_definitions/firebird.sql +13 -1
  103. data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
  104. data/test/fixtures/db_definitions/frontbase.sql +262 -0
  105. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  107. data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
  108. data/test/fixtures/db_definitions/mysql.sql +23 -14
  109. data/test/fixtures/db_definitions/openbase.sql +13 -1
  110. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  111. data/test/fixtures/db_definitions/oracle.sql +29 -2
  112. data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
  113. data/test/fixtures/db_definitions/postgresql.sql +13 -3
  114. data/test/fixtures/db_definitions/schema.rb +29 -1
  115. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  116. data/test/fixtures/db_definitions/sqlite.sql +12 -3
  117. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  118. data/test/fixtures/db_definitions/sqlserver.sql +35 -0
  119. data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
  120. data/test/fixtures/db_definitions/sybase.sql +13 -4
  121. data/test/fixtures/developer.rb +12 -0
  122. data/test/fixtures/edge.rb +5 -0
  123. data/test/fixtures/edges.yml +6 -0
  124. data/test/fixtures/funny_jokes.yml +3 -7
  125. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  126. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  127. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  128. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  129. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  130. data/test/fixtures/mixin.rb +15 -0
  131. data/test/fixtures/mixins.yml +38 -0
  132. data/test/fixtures/post.rb +3 -2
  133. data/test/fixtures/project.rb +3 -1
  134. data/test/fixtures/topic.rb +6 -1
  135. data/test/fixtures/topics.yml +4 -4
  136. data/test/fixtures/vertex.rb +9 -0
  137. data/test/fixtures/vertices.yml +4 -0
  138. data/test/fixtures_test.rb +45 -0
  139. data/test/inheritance_test.rb +67 -6
  140. data/test/lifecycle_test.rb +40 -19
  141. data/test/locking_test.rb +170 -26
  142. data/test/method_scoping_test.rb +2 -2
  143. data/test/migration_test.rb +387 -110
  144. data/test/migration_test_firebird.rb +124 -0
  145. data/test/mixin_nested_set_test.rb +14 -2
  146. data/test/mixin_test.rb +56 -18
  147. data/test/modules_test.rb +8 -2
  148. data/test/multiple_db_test.rb +2 -2
  149. data/test/pk_test.rb +1 -0
  150. data/test/reflection_test.rb +8 -2
  151. data/test/schema_authorization_test_postgresql.rb +75 -0
  152. data/test/schema_dumper_test.rb +40 -4
  153. data/test/table_name_test_sqlserver.rb +23 -0
  154. data/test/threaded_connections_test.rb +19 -16
  155. data/test/transactions_test.rb +86 -72
  156. data/test/validations_test.rb +126 -56
  157. data/test/xml_serialization_test.rb +125 -0
  158. metadata +45 -11
  159. data/lib/active_record/locking.rb +0 -79
@@ -1,5 +1,8 @@
1
1
  require 'active_record/connection_adapters/abstract_adapter'
2
2
 
3
+ require 'bigdecimal'
4
+ require 'bigdecimal/util'
5
+
3
6
  # sqlserver_adapter.rb -- ActiveRecord adapter for Microsoft SQL Server
4
7
  #
5
8
  # Author: Joey Gibson <joey@joeygibson.com>
@@ -10,12 +13,14 @@ require 'active_record/connection_adapters/abstract_adapter'
10
13
  #
11
14
  # Modifications (ODBC): Mark Imbriaco <mark.imbriaco@pobox.com>
12
15
  # Date: 6/26/2005
13
- #
14
- # Current maintainer: Ryan Tomayko <rtomayko@gmail.com>
15
- #
16
+
16
17
  # Modifications (Migrations): Tom Ward <tom@popdog.net>
17
18
  # Date: 27/10/2005
18
19
  #
20
+ # Modifications (Numerous fixes as maintainer): Ryan Tomayko <rtomayko@gmail.com>
21
+ # Date: Up to July 2006
22
+
23
+ # Current maintainer: Tom Ward <tom@popdog.net>
19
24
 
20
25
  module ActiveRecord
21
26
  class Base
@@ -45,58 +50,49 @@ module ActiveRecord
45
50
  end # class Base
46
51
 
47
52
  module ConnectionAdapters
48
- class ColumnWithIdentity < Column# :nodoc:
49
- attr_reader :identity, :is_special, :scale
53
+ class SQLServerColumn < Column# :nodoc:
54
+ attr_reader :identity, :is_special
50
55
 
51
- def initialize(name, default, sql_type = nil, is_identity = false, null = true, scale_value = 0)
56
+ def initialize(name, default, sql_type = nil, identity = false, null = true) # TODO: check ok to remove scale_value = 0
52
57
  super(name, default, sql_type, null)
53
- @identity = is_identity
54
- @is_special = sql_type =~ /text|ntext|image/i ? true : false
55
- @scale = scale_value
58
+ @identity = identity
59
+ @is_special = sql_type =~ /text|ntext|image/i
60
+ # TODO: check ok to remove @scale = scale_value
56
61
  # SQL Server only supports limits on *char and float types
57
62
  @limit = nil unless @type == :float or @type == :string
58
63
  end
59
64
 
60
65
  def simplified_type(field_type)
61
66
  case field_type
62
- when /int|bigint|smallint|tinyint/i then :integer
63
- when /float|double|decimal|money|numeric|real|smallmoney/i then @scale == 0 ? :integer : :float
64
- when /datetime|smalldatetime/i then :datetime
65
- when /timestamp/i then :timestamp
66
- when /time/i then :time
67
- when /text|ntext/i then :text
68
- when /binary|image|varbinary/i then :binary
69
- when /char|nchar|nvarchar|string|varchar/i then :string
70
- when /bit/i then :boolean
71
- when /uniqueidentifier/i then :string
67
+ when /money/i then :decimal
68
+ when /image/i then :binary
69
+ when /bit/i then :boolean
70
+ when /uniqueidentifier/i then :string
71
+ else super
72
72
  end
73
73
  end
74
74
 
75
75
  def type_cast(value)
76
76
  return nil if value.nil? || value =~ /^\s*null\s*$/i
77
77
  case type
78
- when :string then value
79
- when :integer then value == true || value == false ? value == true ? 1 : 0 : value.to_i
80
- when :float then value.to_f
81
78
  when :datetime then cast_to_datetime(value)
82
79
  when :timestamp then cast_to_time(value)
83
80
  when :time then cast_to_time(value)
84
81
  when :date then cast_to_datetime(value)
85
82
  when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
86
- else value
83
+ else super
87
84
  end
88
85
  end
89
-
86
+
90
87
  def cast_to_time(value)
91
88
  return value if value.is_a?(Time)
92
89
  time_array = ParseDate.parsedate(value)
93
- time_array[0] ||= 2000
94
- time_array[1] ||= 1
95
- time_array[2] ||= 1
96
90
  Time.send(Base.default_timezone, *time_array) rescue nil
97
91
  end
98
92
 
99
93
  def cast_to_datetime(value)
94
+ return value.to_time if value.is_a?(DBI::Timestamp)
95
+
100
96
  if value.is_a?(Time)
101
97
  if value.year != 0 and value.month != 0 and value.day != 0
102
98
  return value
@@ -104,9 +100,24 @@ module ActiveRecord
104
100
  return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
105
101
  end
106
102
  end
103
+
104
+ if value.is_a?(DateTime)
105
+ return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
106
+ end
107
+
107
108
  return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
108
109
  value
109
110
  end
111
+
112
+ # TODO: Find less hack way to convert DateTime objects into Times
113
+
114
+ def self.string_to_time(value)
115
+ if value.is_a?(DateTime)
116
+ return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
117
+ else
118
+ super
119
+ end
120
+ end
110
121
 
111
122
  # These methods will only allow the adapter to insert binary data with a length of 7K or less
112
123
  # because of a SQL Server statement length policy.
@@ -187,6 +198,7 @@ module ActiveRecord
187
198
  :text => { :name => "text" },
188
199
  :integer => { :name => "int" },
189
200
  :float => { :name => "float", :limit => 8 },
201
+ :decimal => { :name => "decimal" },
190
202
  :datetime => { :name => "datetime" },
191
203
  :timestamp => { :name => "datetime" },
192
204
  :time => { :name => "datetime" },
@@ -204,11 +216,23 @@ module ActiveRecord
204
216
  true
205
217
  end
206
218
 
219
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
220
+ return super unless type.to_s == 'integer'
221
+
222
+ if limit.nil? || limit == 4
223
+ 'integer'
224
+ elsif limit < 4
225
+ 'smallint'
226
+ else
227
+ 'bigint'
228
+ end
229
+ end
230
+
207
231
  # CONNECTION MANAGEMENT ====================================#
208
232
 
209
233
  # Returns true if the connection is active.
210
234
  def active?
211
- @connection.execute("SELECT 1") { }
235
+ @connection.execute("SELECT 1").finish
212
236
  true
213
237
  rescue DBI::DatabaseError, DBI::InterfaceError
214
238
  false
@@ -229,21 +253,25 @@ module ActiveRecord
229
253
  @connection.disconnect rescue nil
230
254
  end
231
255
 
232
- def select_all(sql, name = nil)
233
- select(sql, name)
234
- end
235
-
236
- def select_one(sql, name = nil)
237
- add_limit!(sql, :limit => 1)
238
- result = select(sql, name)
239
- result.nil? ? nil : result.first
240
- end
241
-
242
256
  def columns(table_name, name = nil)
243
257
  return [] if table_name.blank?
244
258
  table_name = table_name.to_s if table_name.is_a?(Symbol)
245
259
  table_name = table_name.split('.')[-1] unless table_name.nil?
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}'"
260
+ table_name = table_name.gsub(/[\[\]]/, '')
261
+ sql = %Q{
262
+ SELECT
263
+ cols.COLUMN_NAME as ColName,
264
+ cols.COLUMN_DEFAULT as DefaultValue,
265
+ cols.NUMERIC_SCALE as numeric_scale,
266
+ cols.NUMERIC_PRECISION as numeric_precision,
267
+ cols.DATA_TYPE as ColType,
268
+ cols.IS_NULLABLE As IsNullable,
269
+ COL_LENGTH(cols.TABLE_NAME, cols.COLUMN_NAME) as Length,
270
+ COLUMNPROPERTY(OBJECT_ID(cols.TABLE_NAME), cols.COLUMN_NAME, 'IsIdentity') as IsIdentity,
271
+ cols.NUMERIC_SCALE as Scale
272
+ FROM INFORMATION_SCHEMA.COLUMNS cols
273
+ WHERE cols.TABLE_NAME = '#{table_name}'
274
+ }
247
275
  # Comment out if you want to have the Columns select statment logged.
248
276
  # Personally, I think it adds unnecessary bloat to the log.
249
277
  # If you do comment it out, make sure to un-comment the "result" line that follows
@@ -252,63 +280,49 @@ module ActiveRecord
252
280
  columns = []
253
281
  result.each do |field|
254
282
  default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue]
255
- type = "#{field[:ColType]}(#{field[:Length]})"
283
+ if field[:ColType] =~ /numeric|decimal/i
284
+ type = "#{field[:ColType]}(#{field[:numeric_precision]},#{field[:numeric_scale]})"
285
+ else
286
+ type = "#{field[:ColType]}(#{field[:Length]})"
287
+ end
256
288
  is_identity = field[:IsIdentity] == 1
257
289
  is_nullable = field[:IsNullable] == 'YES'
258
- columns << ColumnWithIdentity.new(field[:ColName], default, type, is_identity, is_nullable, field[:Scale])
290
+ columns << SQLServerColumn.new(field[:ColName], default, type, is_identity, is_nullable)
259
291
  end
260
292
  columns
261
293
  end
262
294
 
263
295
  def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
264
- begin
265
- table_name = get_table_name(sql)
266
- col = get_identity_column(table_name)
267
- ii_enabled = false
268
-
269
- if col != nil
270
- if query_contains_identity_column(sql, col)
271
- begin
272
- execute enable_identity_insert(table_name, true)
273
- ii_enabled = true
274
- rescue Exception => e
275
- raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON"
276
- end
277
- end
278
- end
279
- log(sql, name) do
280
- @connection.execute(sql)
281
- id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"]
282
- end
283
- ensure
284
- if ii_enabled
285
- begin
286
- execute enable_identity_insert(table_name, false)
287
- rescue Exception => e
288
- raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF"
289
- end
290
- end
291
- end
296
+ execute(sql, name)
297
+ id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"]
292
298
  end
293
299
 
300
+ def update(sql, name = nil)
301
+ execute(sql, name) do |handle|
302
+ handle.rows
303
+ end || select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]
304
+ end
305
+
306
+ alias_method :delete, :update
307
+
294
308
  def execute(sql, name = nil)
295
- if sql =~ /^\s*INSERT/i
296
- insert(sql, name)
297
- elsif sql =~ /^\s*UPDATE|^\s*DELETE/i
309
+ if sql =~ /^\s*INSERT/i && (table_name = query_requires_identity_insert?(sql))
298
310
  log(sql, name) do
299
- @connection.execute(sql)
300
- retVal = select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]
311
+ with_identity_insert_enabled(table_name) do
312
+ @connection.execute(sql) do |handle|
313
+ yield(handle) if block_given?
314
+ end
315
+ end
301
316
  end
302
317
  else
303
- log(sql, name) { @connection.execute(sql) }
318
+ log(sql, name) do
319
+ @connection.execute(sql) do |handle|
320
+ yield(handle) if block_given?
321
+ end
322
+ end
304
323
  end
305
324
  end
306
325
 
307
- def update(sql, name = nil)
308
- execute(sql, name)
309
- end
310
- alias_method :delete, :update
311
-
312
326
  def begin_db_transaction
313
327
  @connection["AutoCommit"] = false
314
328
  rescue Exception => e
@@ -328,20 +342,14 @@ module ActiveRecord
328
342
  end
329
343
 
330
344
  def quote(value, column = nil)
345
+ return value.quoted_id if value.respond_to?(:quoted_id)
346
+
331
347
  case value
332
- when String
333
- if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
334
- "'#{quote_string(column.class.string_to_binary(value))}'"
335
- else
336
- "'#{quote_string(value)}'"
337
- end
338
- when NilClass then "NULL"
339
348
  when TrueClass then '1'
340
349
  when FalseClass then '0'
341
- when Float, Fixnum, Bignum then value.to_s
342
- when Date then "'#{value.to_s}'"
343
- when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
344
- else "'#{quote_string(value.to_yaml)}'"
350
+ when Time, DateTime then "'#{value.strftime("%Y%m%d %H:%M:%S")}'"
351
+ when Date then "'#{value.strftime("%Y%m%d")}'"
352
+ else super
345
353
  end
346
354
  end
347
355
 
@@ -349,25 +357,17 @@ module ActiveRecord
349
357
  string.gsub(/\'/, "''")
350
358
  end
351
359
 
352
- def quoted_true
353
- "1"
354
- end
355
-
356
- def quoted_false
357
- "0"
358
- end
359
-
360
360
  def quote_column_name(name)
361
361
  "[#{name}]"
362
362
  end
363
363
 
364
364
  def add_limit_offset!(sql, options)
365
365
  if options[:limit] and options[:offset]
366
- total_rows = @connection.select_all("SELECT count(*) as TotalRows from (#{sql.gsub(/\bSELECT\b/i, "SELECT TOP 1000000000")}) tally")[0][:TotalRows].to_i
366
+ total_rows = @connection.select_all("SELECT count(*) as TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally")[0][:TotalRows].to_i
367
367
  if (options[:limit] + options[:offset]) >= total_rows
368
368
  options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
369
369
  end
370
- sql.sub!(/^\s*SELECT/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT TOP #{options[:limit] + options[:offset]} ")
370
+ sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]} ")
371
371
  sql << ") AS tmp1"
372
372
  if options[:order]
373
373
  options[:order] = options[:order].split(',').map do |field|
@@ -378,7 +378,9 @@ module ActiveRecord
378
378
  tc << '\\]'
379
379
  end
380
380
  if sql =~ /#{tc} AS (t\d_r\d\d?)/
381
- parts[0] = $1
381
+ parts[0] = $1
382
+ elsif parts[0] =~ /\w+\.(\w+)/
383
+ parts[0] = $1
382
384
  end
383
385
  parts.join(' ')
384
386
  end.join(', ')
@@ -387,7 +389,7 @@ module ActiveRecord
387
389
  sql << " ) AS tmp2"
388
390
  end
389
391
  elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
390
- sql.sub!(/^\s*SELECT([\s]*distinct)?/i) do
392
+ sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
391
393
  "SELECT#{$1} TOP #{options[:limit]}"
392
394
  end unless options[:limit].nil?
393
395
  end
@@ -411,42 +413,55 @@ module ActiveRecord
411
413
  end
412
414
 
413
415
  def tables(name = nil)
414
- execute("SELECT table_name from information_schema.tables WHERE table_type = 'BASE TABLE'", name).inject([]) do |tables, field|
415
- table_name = field[0]
416
- tables << table_name unless table_name == 'dtproperties'
417
- tables
416
+ execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", name) do |sth|
417
+ sth.inject([]) do |tables, field|
418
+ table_name = field[0]
419
+ tables << table_name unless table_name == 'dtproperties'
420
+ tables
421
+ end
418
422
  end
419
423
  end
420
424
 
421
425
  def indexes(table_name, name = nil)
422
- indexes = []
423
- execute("EXEC sp_helpindex #{table_name}", name).each do |index|
424
- unique = index[1] =~ /unique/
425
- primary = index[1] =~ /primary key/
426
- if !primary
427
- indexes << IndexDefinition.new(table_name, index[0], unique, index[2].split(", "))
426
+ ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = false
427
+ indexes = []
428
+ execute("EXEC sp_helpindex #{table_name}", name) do |sth|
429
+ sth.each do |index|
430
+ unique = index[1] =~ /unique/
431
+ primary = index[1] =~ /primary key/
432
+ if !primary
433
+ indexes << IndexDefinition.new(table_name, index[0], unique, index[2].split(", "))
434
+ end
428
435
  end
429
436
  end
430
437
  indexes
438
+ ensure
439
+ ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = true
431
440
  end
432
441
 
433
442
  def rename_table(name, new_name)
434
443
  execute "EXEC sp_rename '#{name}', '#{new_name}'"
435
444
  end
436
445
 
437
- def remove_column(table_name, column_name)
438
- execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
439
- end
440
-
446
+ # Adds a new column to the named table.
447
+ # See TableDefinition#column for details of the options you can use.
448
+ def add_column(table_name, column_name, type, options = {})
449
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
450
+ add_column_options!(add_column_sql, options)
451
+ # TODO: Add support to mimic date columns, using constraints to mark them as such in the database
452
+ # add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date
453
+ execute(add_column_sql)
454
+ end
455
+
441
456
  def rename_column(table, column, new_column_name)
442
457
  execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
443
458
  end
444
459
 
445
460
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
446
- sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit])}"]
447
- if options[:default]
461
+ sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"]
462
+ if options_include_default?(options)
448
463
  remove_default_constraint(table_name, column_name)
449
- sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{options[:default]} FOR #{column_name}"
464
+ sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(options[:default], options[:column])} FOR #{column_name}"
450
465
  end
451
466
  sql_commands.each {|c|
452
467
  execute(c)
@@ -454,51 +469,66 @@ module ActiveRecord
454
469
  end
455
470
 
456
471
  def remove_column(table_name, column_name)
472
+ remove_check_constraints(table_name, column_name)
457
473
  remove_default_constraint(table_name, column_name)
458
- execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
474
+ execute "ALTER TABLE [#{table_name}] DROP COLUMN [#{column_name}]"
459
475
  end
460
476
 
461
477
  def remove_default_constraint(table_name, column_name)
462
- 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"
463
- defaults.each {|constraint|
478
+ constraints = 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"
479
+
480
+ constraints.each do |constraint|
464
481
  execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
465
- }
482
+ end
466
483
  end
467
484
 
468
- def remove_index(table_name, options = {})
469
- execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
485
+ def remove_check_constraints(table_name, column_name)
486
+ # TODO remove all constraints in single method
487
+ constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
488
+ constraints.each do |constraint|
489
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
490
+ end
470
491
  end
471
-
472
- def type_to_sql(type, limit = nil) #:nodoc:
473
- native = native_database_types[type]
474
- # if there's no :limit in the default type definition, assume that type doesn't support limits
475
- limit = limit || native[:limit]
476
- column_type_sql = native[:name]
477
- column_type_sql << "(#{limit})" if limit
478
- column_type_sql
492
+
493
+ def remove_index(table_name, options = {})
494
+ execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}"
479
495
  end
480
496
 
481
- private
497
+ private
482
498
  def select(sql, name = nil)
483
- rows = []
484
499
  repair_special_columns(sql)
485
- log(sql, name) do
486
- @connection.select_all(sql) do |row|
487
- record = {}
488
- row.column_names.each do |col|
489
- record[col] = row[col]
490
- record[col] = record[col].to_time if record[col].is_a? DBI::Timestamp
500
+
501
+ result = []
502
+ execute(sql) do |handle|
503
+ handle.each do |row|
504
+ row_hash = {}
505
+ row.each_with_index do |value, i|
506
+ if value.is_a? DBI::Timestamp
507
+ value = DateTime.new(value.year, value.month, value.day, value.hour, value.minute, value.sec)
508
+ end
509
+ row_hash[handle.column_names[i]] = value
491
510
  end
492
- rows << record
511
+ result << row_hash
493
512
  end
494
513
  end
495
- rows
514
+ result
496
515
  end
497
516
 
498
- def enable_identity_insert(table_name, enable = true)
499
- if has_identity_column(table_name)
500
- "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
501
- end
517
+ # Turns IDENTITY_INSERT ON for table during execution of the block
518
+ # N.B. This sets the state of IDENTITY_INSERT to OFF after the
519
+ # block has been executed without regard to its previous state
520
+
521
+ def with_identity_insert_enabled(table_name, &block)
522
+ set_identity_insert(table_name, true)
523
+ yield
524
+ ensure
525
+ set_identity_insert(table_name, false)
526
+ end
527
+
528
+ def set_identity_insert(table_name, enable = true)
529
+ execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
530
+ rescue Exception => e
531
+ raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
502
532
  end
503
533
 
504
534
  def get_table_name(sql)
@@ -511,11 +541,7 @@ module ActiveRecord
511
541
  end
512
542
  end
513
543
 
514
- def has_identity_column(table_name)
515
- !get_identity_column(table_name).nil?
516
- end
517
-
518
- def get_identity_column(table_name)
544
+ def identity_column(table_name)
519
545
  @table_columns = {} unless @table_columns
520
546
  @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
521
547
  @table_columns[table_name].each do |col|
@@ -525,8 +551,10 @@ module ActiveRecord
525
551
  return nil
526
552
  end
527
553
 
528
- def query_contains_identity_column(sql, col)
529
- sql =~ /\[#{col}\]/
554
+ def query_requires_identity_insert?(sql)
555
+ table_name = get_table_name(sql)
556
+ id_column = identity_column(table_name)
557
+ sql =~ /\[#{id_column}\]/ ? table_name : nil
530
558
  end
531
559
 
532
560
  def change_order_direction(order)