activerecord 1.1.0 → 1.2.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 (68) hide show
  1. data/CHANGELOG +250 -0
  2. data/README +17 -9
  3. data/dev-utils/eval_debugger.rb +1 -1
  4. data/install.rb +3 -1
  5. data/lib/active_record.rb +9 -2
  6. data/lib/active_record/acts/list.rb +178 -0
  7. data/lib/active_record/acts/tree.rb +44 -0
  8. data/lib/active_record/associations.rb +45 -8
  9. data/lib/active_record/associations/association_collection.rb +18 -9
  10. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +14 -13
  11. data/lib/active_record/associations/has_many_association.rb +21 -12
  12. data/lib/active_record/base.rb +137 -37
  13. data/lib/active_record/callbacks.rb +30 -25
  14. data/lib/active_record/connection_adapters/abstract_adapter.rb +57 -33
  15. data/lib/active_record/connection_adapters/mysql_adapter.rb +4 -0
  16. data/lib/active_record/connection_adapters/sqlite_adapter.rb +3 -2
  17. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +298 -0
  18. data/lib/active_record/fixtures.rb +241 -147
  19. data/lib/active_record/support/class_inheritable_attributes.rb +5 -2
  20. data/lib/active_record/support/inflector.rb +13 -12
  21. data/lib/active_record/support/misc.rb +6 -0
  22. data/lib/active_record/timestamp.rb +33 -0
  23. data/lib/active_record/transactions.rb +1 -1
  24. data/lib/active_record/validations.rb +294 -16
  25. data/rakefile +3 -7
  26. data/test/abstract_unit.rb +1 -4
  27. data/test/associations_test.rb +17 -4
  28. data/test/base_test.rb +37 -5
  29. data/test/connections/native_sqlserver/connection.rb +15 -0
  30. data/test/deprecated_associations_test.rb +40 -38
  31. data/test/finder_test.rb +82 -4
  32. data/test/fixtures/accounts.yml +8 -0
  33. data/test/fixtures/company.rb +6 -0
  34. data/test/fixtures/company_in_module.rb +1 -1
  35. data/test/fixtures/db_definitions/mysql.sql +13 -0
  36. data/test/fixtures/db_definitions/postgresql.sql +13 -0
  37. data/test/fixtures/db_definitions/sqlite.sql +14 -0
  38. data/test/fixtures/db_definitions/sqlserver.sql +110 -0
  39. data/test/fixtures/db_definitions/sqlserver2.sql +4 -0
  40. data/test/fixtures/developer.rb +2 -2
  41. data/test/fixtures/developers.yml +13 -0
  42. data/test/fixtures/fixture_database.sqlite +0 -0
  43. data/test/fixtures/fixture_database_2.sqlite +0 -0
  44. data/test/fixtures/mixin.rb +17 -0
  45. data/test/fixtures/mixins.yml +14 -0
  46. data/test/fixtures/naked/csv/accounts.csv +1 -0
  47. data/test/fixtures/naked/yml/accounts.yml +1 -0
  48. data/test/fixtures/naked/yml/companies.yml +1 -0
  49. data/test/fixtures/naked/yml/courses.yml +1 -0
  50. data/test/fixtures/project.rb +6 -0
  51. data/test/fixtures/reply.rb +14 -1
  52. data/test/fixtures/topic.rb +2 -2
  53. data/test/fixtures/topics/first +1 -0
  54. data/test/fixtures_test.rb +42 -12
  55. data/test/inflector_test.rb +2 -1
  56. data/test/inheritance_test.rb +22 -12
  57. data/test/mixin_test.rb +138 -0
  58. data/test/pk_test.rb +4 -2
  59. data/test/reflection_test.rb +3 -3
  60. data/test/transactions_test.rb +15 -0
  61. data/test/validations_test.rb +229 -4
  62. metadata +24 -10
  63. data/lib/active_record/associations.rb.orig +0 -555
  64. data/test/deprecated_associations_test.rb.orig +0 -334
  65. data/test/fixtures/accounts/signals37 +0 -3
  66. data/test/fixtures/accounts/unknown +0 -2
  67. data/test/fixtures/developers/david +0 -2
  68. data/test/fixtures/developers/jamis +0 -2
@@ -2,7 +2,7 @@ require 'observer'
2
2
 
3
3
  module ActiveRecord
4
4
  # Callbacks are hooks into the lifecycle of an Active Record object that allows you to trigger logic
5
- # before or after an alteration of the object state. This can be used to make sure that associated and
5
+ # before or after an alteration of the object state. This can be used to make sure that associated and
6
6
  # dependent objects are deleted when destroy is called (by overwriting before_destroy) or to massage attributes
7
7
  # before they're validated (by overwriting before_validation). As an example of the callbacks initiated, consider
8
8
  # the Base#save call:
@@ -14,19 +14,19 @@ module ActiveRecord
14
14
  # * (-) validate
15
15
  # * (-) validate_on_create
16
16
  # * (4) after_validation
17
- # * (5) after_validation_on_create
17
+ # * (5) after_validation_on_create
18
18
  # * (6) before_save
19
19
  # * (7) before_create
20
20
  # * (-) create
21
21
  # * (8) after_create
22
22
  # * (9) after_save
23
- #
23
+ #
24
24
  # That's a total of nine callbacks, which gives you immense power to react and prepare for each state in the
25
25
  # Active Record lifecyle.
26
26
  #
27
27
  # Examples:
28
28
  # class CreditCard < ActiveRecord::Base
29
- # # Strip everything but digits, so the user can specify "555 234 34" or
29
+ # # Strip everything but digits, so the user can specify "555 234 34" or
30
30
  # # "5552-3434" or both will mean "55523434"
31
31
  # def before_validation_on_create
32
32
  # self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
@@ -73,13 +73,13 @@ module ActiveRecord
73
73
  # def before_destroy() destroy_readers end
74
74
  # end
75
75
  #
76
- # In that case, Reply#destroy would only run +destroy_readers+ and _not_ +destroy_author+. So use the callback macros when
76
+ # In that case, Reply#destroy would only run +destroy_readers+ and _not_ +destroy_author+. So use the callback macros when
77
77
  # you want to ensure that a certain callback is called for the entire hierarchy and the regular overwriteable methods when you
78
78
  # want to leave it up to each descendent to decide whether they want to call +super+ and trigger the inherited callbacks.
79
79
  #
80
80
  # == Types of callbacks
81
81
  #
82
- # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
82
+ # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
83
83
  # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the
84
84
  # recommended approaches, inline methods using a proc is some times appropriate (such as for creating mix-ins), and inline
85
85
  # eval methods are deprecated.
@@ -115,7 +115,7 @@ module ActiveRecord
115
115
  # def after_save(record)
116
116
  # record.credit_card_number = decrypt(record.credit_card_number)
117
117
  # end
118
- #
118
+ #
119
119
  # alias_method :after_initialize, :after_save
120
120
  #
121
121
  # private
@@ -142,7 +142,7 @@ module ActiveRecord
142
142
  # inline callbacks can be stacked just like the regular ones:
143
143
  #
144
144
  # class Topic < ActiveRecord::Base
145
- # before_destroy 'self.class.delete_all "parent_id = #{id}"',
145
+ # before_destroy 'self.class.delete_all "parent_id = #{id}"',
146
146
  # 'puts "Evaluated after parents are destroyed"'
147
147
  # end
148
148
  #
@@ -153,8 +153,8 @@ module ActiveRecord
153
153
  # after_initialize can only be declared using an explicit implementation. So using the inheritable callback queue for after_find and
154
154
  # after_initialize won't work.
155
155
  module Callbacks
156
- CALLBACKS = %w(
157
- after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
156
+ CALLBACKS = %w(
157
+ after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
158
158
  after_validation before_validation_on_create after_validation_on_create before_validation_on_update
159
159
  after_validation_on_update before_destroy after_destroy
160
160
  )
@@ -210,10 +210,11 @@ module ActiveRecord
210
210
  # def after_initialize() end
211
211
  def initialize_with_callbacks(attributes = nil) #:nodoc:
212
212
  initialize_without_callbacks(attributes)
213
- yield self if block_given?
213
+ result = yield self if block_given?
214
214
  after_initialize if respond_to_without_attributes?(:after_initialize)
215
+ result
215
216
  end
216
-
217
+
217
218
  # Is called _before_ Base.save (regardless of whether it's a create or update save).
218
219
  def before_save() end
219
220
 
@@ -221,8 +222,9 @@ module ActiveRecord
221
222
  def after_save() end
222
223
  def create_or_update_with_callbacks #:nodoc:
223
224
  callback(:before_save)
224
- create_or_update_without_callbacks
225
+ result = create_or_update_without_callbacks
225
226
  callback(:after_save)
227
+ result
226
228
  end
227
229
 
228
230
  # Is called _before_ Base.save on new objects that haven't been saved yet (no record exists).
@@ -232,8 +234,9 @@ module ActiveRecord
232
234
  def after_create() end
233
235
  def create_with_callbacks #:nodoc:
234
236
  callback(:before_create)
235
- create_without_callbacks
237
+ result = create_without_callbacks
236
238
  callback(:after_create)
239
+ result
237
240
  end
238
241
 
239
242
  # Is called _before_ Base.save on existing objects that has a record.
@@ -244,8 +247,9 @@ module ActiveRecord
244
247
 
245
248
  def update_with_callbacks #:nodoc:
246
249
  callback(:before_update)
247
- update_without_callbacks
250
+ result = update_without_callbacks
248
251
  callback(:after_update)
252
+ result
249
253
  end
250
254
 
251
255
  # Is called _before_ Validations.validate (which is part of the Base.save call).
@@ -262,11 +266,11 @@ module ActiveRecord
262
266
  # that haven't been saved yet (no record exists).
263
267
  def after_validation_on_create() end
264
268
 
265
- # Is called _before_ Validations.validate (which is part of the Base.save call) on
269
+ # Is called _before_ Validations.validate (which is part of the Base.save call) on
266
270
  # existing objects that has a record.
267
271
  def before_validation_on_update() end
268
272
 
269
- # Is called _after_ Validations.validate (which is part of the Base.save call) on
273
+ # Is called _after_ Validations.validate (which is part of the Base.save call) on
270
274
  # existing objects that has a record.
271
275
  def after_validation_on_update() end
272
276
 
@@ -278,7 +282,7 @@ module ActiveRecord
278
282
 
279
283
  callback(:after_validation)
280
284
  if new_record? then callback(:after_validation_on_create) else callback(:after_validation_on_update) end
281
-
285
+
282
286
  return result
283
287
  end
284
288
 
@@ -289,8 +293,9 @@ module ActiveRecord
289
293
  def after_destroy() end
290
294
  def destroy_with_callbacks #:nodoc:
291
295
  callback(:before_destroy)
292
- destroy_without_callbacks
296
+ result = destroy_without_callbacks
293
297
  callback(:after_destroy)
298
+ result
294
299
  end
295
300
 
296
301
  def callback(callback_method) #:nodoc:
@@ -302,7 +307,7 @@ module ActiveRecord
302
307
  def run_callbacks(callback_method)
303
308
  filters = self.class.read_inheritable_attribute(callback_method.to_s)
304
309
  if filters.nil? then return end
305
- filters.each do |filter|
310
+ filters.each do |filter|
306
311
  if Symbol === filter
307
312
  self.send(filter)
308
313
  elsif String === filter
@@ -313,25 +318,25 @@ module ActiveRecord
313
318
  filter.send(callback_method, self)
314
319
  else
315
320
  raise(
316
- ActiveRecordError,
321
+ ActiveRecordError,
317
322
  "Filters need to be either a symbol, string (to be eval'ed), proc/method, or " +
318
323
  "class implementing a static filter method"
319
324
  )
320
325
  end
321
326
  end
322
327
  end
323
-
328
+
324
329
  def filter_block?(filter)
325
330
  filter.respond_to?("call") && (filter.arity == 1 || filter.arity == -1)
326
331
  end
327
-
332
+
328
333
  def filter_class?(filter, callback_method)
329
334
  filter.respond_to?(callback_method)
330
335
  end
331
-
336
+
332
337
  def notify(callback_method) #:nodoc:
333
338
  self.class.changed
334
339
  self.class.notify_observers(callback_method, self)
335
340
  end
336
341
  end
337
- end
342
+ end
@@ -75,21 +75,30 @@ module ActiveRecord
75
75
  # end
76
76
  #
77
77
  # Courses.establish_connection( ... )
78
- def self.establish_connection(spec)
79
- if spec.instance_of? ConnectionSpecification
80
- @@defined_connections[self] = spec
81
- else
82
- if spec.nil? then raise AdapterNotSpecified end
83
- symbolize_strings_in_hash(spec)
84
- unless spec.key?(:adapter) then raise AdapterNotSpecified end
85
-
86
- adapter_method = "#{spec[:adapter]}_connection"
87
- unless methods.include?(adapter_method) then raise AdapterNotFound end
88
- remove_connection
89
- @@defined_connections[self] = ConnectionSpecification.new(spec, adapter_method)
78
+ def self.establish_connection(spec = nil)
79
+ case spec
80
+ when nil
81
+ raise AdapterNotSpecified unless defined? RAILS_ENV
82
+ establish_connection(RAILS_ENV)
83
+ when ConnectionSpecification
84
+ @@defined_connections[self] = spec
85
+ when Symbol, String
86
+ if configuration = configurations[spec.to_s]
87
+ establish_connection(configuration)
88
+ else
89
+ raise AdapterNotSpecified
90
+ end
91
+ else
92
+ spec = symbolize_strings_in_hash(spec)
93
+ unless spec.key?(:adapter) then raise AdapterNotSpecified end
94
+
95
+ adapter_method = "#{spec[:adapter]}_connection"
96
+ unless respond_to?(adapter_method) then raise AdapterNotFound end
97
+ remove_connection
98
+ establish_connection(ConnectionSpecification.new(spec, adapter_method))
90
99
  end
91
100
  end
92
-
101
+
93
102
  # Locate the connection of the nearest super class. This can be an
94
103
  # active or defined connections: if it is the latter, it will be
95
104
  # opened and set as the active connection for the class it was defined
@@ -144,11 +153,9 @@ module ActiveRecord
144
153
 
145
154
  # Converts all strings in a hash to symbols.
146
155
  def self.symbolize_strings_in_hash(hash)
147
- hash.each do |key, value|
148
- if key.class == String
149
- hash.delete key
150
- hash[key.intern] = value
151
- end
156
+ hash.inject({}) do |hash_with_symbolized_strings, pair|
157
+ hash_with_symbolized_strings[pair.first.to_sym] = pair.last
158
+ hash_with_symbolized_strings
152
159
  end
153
160
  end
154
161
  end
@@ -175,6 +182,8 @@ module ActiveRecord
175
182
  when :float then Float
176
183
  when :datetime then Time
177
184
  when :date then Date
185
+ when :timestamp then Time
186
+ when :time then Time
178
187
  when :text, :string then String
179
188
  when :boolean then Object
180
189
  end
@@ -183,13 +192,15 @@ module ActiveRecord
183
192
  def type_cast(value)
184
193
  if value.nil? then return nil end
185
194
  case type
186
- when :string then value
187
- when :text then value
188
- when :integer then value.to_i
189
- when :float then value.to_f
190
- when :datetime then string_to_time(value)
191
- when :date then string_to_date(value)
192
- when :boolean then (value == "t" or value == true ? true : false)
195
+ when :string then value
196
+ when :text then value
197
+ when :integer then value.to_i
198
+ when :float then value.to_f
199
+ when :datetime then string_to_time(value)
200
+ when :timestamp then string_to_time(value)
201
+ when :time then string_to_dummy_time(value)
202
+ when :date then string_to_date(value)
203
+ when :boolean then (value == "t" or value == true ? true : false)
193
204
  else value
194
205
  end
195
206
  end
@@ -213,6 +224,14 @@ module ActiveRecord
213
224
  Time.local(*time_array) rescue nil
214
225
  end
215
226
 
227
+ def string_to_dummy_time(string)
228
+ return string if Time === string
229
+ time_array = ParseDate.parsedate(string)
230
+ # pad the resulting array with dummy date information
231
+ time_array[0] = 2000; time_array[1] = 1; time_array[2] = 1;
232
+ Time.local(*time_array) rescue nil
233
+ end
234
+
216
235
  def extract_limit(sql_type)
217
236
  $1.to_i if sql_type =~ /\((.*)\)/
218
237
  end
@@ -223,8 +242,12 @@ module ActiveRecord
223
242
  :integer
224
243
  when /float|double|decimal|numeric/i
225
244
  :float
226
- when /time/i
245
+ when /datetime/i
227
246
  :datetime
247
+ when /timestamp/i
248
+ :timestamp
249
+ when /time/i
250
+ :time
228
251
  when /date/i
229
252
  :date
230
253
  when /(c|b)lob/i, /text/i
@@ -301,13 +324,14 @@ module ActiveRecord
301
324
 
302
325
  def quote(value, column = nil)
303
326
  case value
304
- when String then "'#{quote_string(value)}'" # ' (for ruby-mode)
305
- when NilClass then "NULL"
306
- when TrueClass then (column && column.type == :boolean ? "'t'" : "1")
307
- when FalseClass then (column && column.type == :boolean ? "'f'" : "0")
308
- when Float, Fixnum, Bignum, Date then "'#{value.to_s}'"
309
- when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
310
- else "'#{quote_string(value.to_yaml)}'"
327
+ when String then "'#{quote_string(value)}'" # ' (for ruby-mode)
328
+ when NilClass then "NULL"
329
+ when TrueClass then (column && column.type == :boolean ? "'t'" : "1")
330
+ when FalseClass then (column && column.type == :boolean ? "'f'" : "0")
331
+ when Float, Fixnum, Bignum then value.to_s
332
+ when Date then "'#{value.to_s}'"
333
+ when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
334
+ else "'#{quote_string(value.to_yaml)}'"
311
335
  end
312
336
  end
313
337
 
@@ -117,6 +117,10 @@ module ActiveRecord
117
117
  execute "CREATE DATABASE #{name}"
118
118
  end
119
119
 
120
+ def quote_string(s)
121
+ Mysql::quote(s)
122
+ end
123
+
120
124
  private
121
125
  def select(sql, name = nil)
122
126
  result = nil
@@ -12,7 +12,8 @@ module ActiveRecord
12
12
  unless config.has_key?(:dbfile)
13
13
  raise ArgumentError, "No database file specified. Missing argument: dbfile"
14
14
  end
15
-
15
+
16
+ config[:dbfile] = File.expand_path(config[:dbfile], RAILS_ROOT) if Object.const_defined?(:RAILS_ROOT)
16
17
  db = SQLite::Database.new(config[:dbfile], 0)
17
18
 
18
19
  db.show_datatypes = "ON" if !defined? SQLite::Version
@@ -102,4 +103,4 @@ module ActiveRecord
102
103
  end
103
104
  end
104
105
  end
105
- end
106
+ end
@@ -0,0 +1,298 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+
3
+ # sqlserver_adapter.rb -- ActiveRecord adapter for Microsoft SQL Server
4
+ #
5
+ # Author: Joey Gibson <joey@joeygibson.com>
6
+ # Date: 10/14/2004
7
+ #
8
+ # REQUIREMENTS:
9
+ #
10
+ # This adapter will ONLY work on Windows systems, since it relies on Win32OLE, which,
11
+ # to my knowledge, is only available on Window.
12
+ #
13
+ # It relies on the ADO support in the DBI module. If you are using the
14
+ # one-click installer of Ruby, then you already have DBI installed, but
15
+ # the ADO module is *NOT* installed. You will need to get the latest
16
+ # source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/
17
+ # unzip it, and copy the file src/lib/dbd_ado/ADO.rb to
18
+ # X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb (you will need to create
19
+ # the ADO directory). Once you've installed that file, you are ready to go.
20
+ #
21
+ # This module uses the ADO-style DSNs for connection. For example:
22
+ # "DBI:ADO:Provider=SQLOLEDB;Data Source=(local);Initial Catalog=test;User Id=sa;Password=password;"
23
+ # with User Id replaced with your proper login, and Password with your
24
+ # password.
25
+ #
26
+ # I have tested this code on a WindowsXP Pro SP1 system,
27
+ # ruby 1.8.2 (2004-07-29) [i386-mswin32], SQL Server 2000.
28
+ #
29
+ module ActiveRecord
30
+ class Base
31
+ def self.sqlserver_connection(config)
32
+ require_library_or_gem 'dbi' unless self.class.const_defined?(:DBI)
33
+ class_eval { include ActiveRecord::SQLServerBaseExtensions }
34
+
35
+ symbolize_strings_in_hash(config)
36
+
37
+ if config.has_key? :dsn
38
+ dsn = config[:dsn]
39
+ else
40
+ raise ArgumentError, "No DSN specified"
41
+ end
42
+
43
+ conn = DBI.connect(dsn)
44
+ conn["AutoCommit"] = true
45
+
46
+ ConnectionAdapters::SQLServerAdapter.new(conn, logger)
47
+ end
48
+ end
49
+
50
+ module SQLServerBaseExtensions #:nodoc:
51
+ def self.append_features(base)
52
+ super
53
+ base.extend(ClassMethods)
54
+ end
55
+
56
+ module ClassMethods #:nodoc:
57
+ def find_first(conditions = nil, orderings = nil)
58
+ sql = "SELECT TOP 1 * FROM #{table_name} "
59
+ add_conditions!(sql, conditions)
60
+ sql << "ORDER BY #{orderings} " unless orderings.nil?
61
+
62
+ record = connection.select_one(sql, "#{name} Load First")
63
+ instantiate(record) unless record.nil?
64
+ end
65
+
66
+ def find_all(conditions = nil, orderings = nil, limit = nil, joins = nil)
67
+ sql = "SELECT "
68
+ sql << "TOP #{limit} " unless limit.nil?
69
+ sql << " * FROM #{table_name} "
70
+ sql << "#{joins} " if joins
71
+ add_conditions!(sql, conditions)
72
+ sql << "ORDER BY #{orderings} " unless orderings.nil?
73
+
74
+ find_by_sql(sql)
75
+ end
76
+ end
77
+
78
+ def attributes_with_quotes
79
+ columns_hash = self.class.columns_hash
80
+
81
+ attrs = @attributes.dup
82
+
83
+ attrs = attrs.reject do |name, value|
84
+ columns_hash[name].identity
85
+ end
86
+
87
+ attrs.inject({}) do |attrs_quoted, pair|
88
+ attrs_quoted[pair.first] = quote(pair.last, columns_hash[pair.first])
89
+ attrs_quoted
90
+ end
91
+ end
92
+ end
93
+
94
+ module ConnectionAdapters
95
+ class ColumnWithIdentity < Column# :nodoc:
96
+ attr_reader :identity
97
+
98
+ def initialize(name, default, sql_type = nil, is_identity = false)
99
+ super(name, default, sql_type)
100
+
101
+ @identity = is_identity
102
+ end
103
+ end
104
+
105
+ class SQLServerAdapter < AbstractAdapter # :nodoc:
106
+ def quote_column_name(name)
107
+ " [#{name}] "
108
+ end
109
+
110
+ def select_all(sql, name = nil)
111
+ select(sql, name)
112
+ end
113
+
114
+ def select_one(sql, name = nil)
115
+ result = select(sql, name)
116
+ result.nil? ? nil : result.first
117
+ end
118
+
119
+ def columns(table_name, name = nil)
120
+ sql = <<EOL
121
+ SELECT s.name AS TableName, c.id AS ColId, c.name AS ColName, t.name AS ColType, c.length AS Length,
122
+ c.AutoVal AS IsIdentity,
123
+ c.cdefault AS DefaultId, com.text AS DefaultValue
124
+ FROM syscolumns AS c
125
+ JOIN systypes AS t ON (c.xtype = t.xtype AND c.usertype = t.usertype)
126
+ JOIN sysobjects AS s ON (c.id = s.id)
127
+ LEFT OUTER JOIN syscomments AS com ON (c.cdefault = com.id)
128
+ WHERE s.name = '#{table_name}'
129
+ EOL
130
+
131
+ columns = []
132
+
133
+ log(sql, name, @connection) do |conn|
134
+ conn.select_all(sql) do |row|
135
+ default_value = row[:DefaultValue]
136
+
137
+ if default_value =~ /null/i
138
+ default_value = nil
139
+ else
140
+ default_value =~ /\(([^)]+)\)/
141
+ default_value = $1
142
+ end
143
+
144
+ col = ColumnWithIdentity.new(row[:ColName], default_value, "#{row[:ColType]}(#{row[:Length]})", row[:IsIdentity] != nil)
145
+
146
+ columns << col
147
+ end
148
+ end
149
+
150
+ columns
151
+ end
152
+
153
+ def insert(sql, name = nil, pk = nil, id_value = nil)
154
+ begin
155
+ table_name = get_table_name(sql)
156
+
157
+ col = get_identity_column(table_name)
158
+
159
+ ii_enabled = false
160
+
161
+ if col != nil
162
+ if query_contains_identity_column(sql, col)
163
+ begin
164
+ execute enable_identity_insert(table_name, true)
165
+ ii_enabled = true
166
+ rescue Exception => e
167
+ # Coulnd't turn on IDENTITY_INSERT
168
+ end
169
+ end
170
+ end
171
+
172
+ log(sql, name, @connection) do |conn|
173
+ conn.execute(sql)
174
+
175
+ select_one("SELECT @@IDENTITY AS Ident")["Ident"]
176
+ end
177
+ ensure
178
+ if ii_enabled
179
+ begin
180
+ execute enable_identity_insert(table_name, false)
181
+
182
+ rescue Exception => e
183
+ # Couldn't turn off IDENTITY_INSERT
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ def execute(sql, name = nil)
190
+ if sql =~ /^INSERT/i
191
+ insert(sql, name)
192
+ else
193
+ log(sql, name, @connection) do |conn|
194
+ conn.execute(sql)
195
+ end
196
+ end
197
+ end
198
+
199
+ alias_method :update, :execute
200
+ alias_method :delete, :execute
201
+
202
+ def begin_db_transaction
203
+ begin
204
+ @connection["AutoCommit"] = false
205
+ rescue Exception => e
206
+ @connection["AutoCommit"] = true
207
+ end
208
+ end
209
+
210
+ def commit_db_transaction
211
+ begin
212
+ @connection.commit
213
+ ensure
214
+ @connection["AutoCommit"] = true
215
+ end
216
+ end
217
+
218
+ def rollback_db_transaction
219
+ begin
220
+ @connection.rollback
221
+ ensure
222
+ @connection["AutoCommit"] = true
223
+ end
224
+ end
225
+
226
+ def recreate_database(name)
227
+ drop_database(name)
228
+ create_database(name)
229
+ end
230
+
231
+ def drop_database(name)
232
+ execute "DROP DATABASE #{name}"
233
+ end
234
+
235
+ def create_database(name)
236
+ execute "CREATE DATABASE #{name}"
237
+ end
238
+
239
+ private
240
+ def select(sql, name = nil)
241
+ rows = []
242
+
243
+ log(sql, name, @connection) do |conn|
244
+ conn.select_all(sql) do |row|
245
+ record = {}
246
+
247
+ row.column_names.each do |col|
248
+ record[col] = row[col]
249
+ end
250
+
251
+ rows << record
252
+ end
253
+ end
254
+
255
+ rows
256
+ end
257
+
258
+ def enable_identity_insert(table_name, enable = true)
259
+ if has_identity_column(table_name)
260
+ "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
261
+ end
262
+ end
263
+
264
+ def get_table_name(sql)
265
+ if sql =~ /into\s*([^\s]+)\s*/i or
266
+ sql =~ /update\s*([^\s]+)\s*/i
267
+ $1
268
+ else
269
+ nil
270
+ end
271
+ end
272
+
273
+ def has_identity_column(table_name)
274
+ return get_identity_column(table_name) != nil
275
+ end
276
+
277
+ def get_identity_column(table_name)
278
+ if not @table_columns
279
+ @table_columns = {}
280
+ end
281
+
282
+ if @table_columns[table_name] == nil
283
+ @table_columns[table_name] = columns(table_name)
284
+ end
285
+
286
+ @table_columns[table_name].each do |col|
287
+ return col.name if col.identity
288
+ end
289
+
290
+ return nil
291
+ end
292
+
293
+ def query_contains_identity_column(sql, col)
294
+ return sql =~ /[\(\.\,]\s*#{col}/
295
+ end
296
+ end
297
+ end
298
+ end