activerecord 1.15.6 → 2.0.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 (185) hide show
  1. data/CHANGELOG +2454 -34
  2. data/README +1 -1
  3. data/RUNNING_UNIT_TESTS +3 -34
  4. data/Rakefile +98 -77
  5. data/install.rb +1 -1
  6. data/lib/active_record.rb +13 -22
  7. data/lib/active_record/aggregations.rb +38 -49
  8. data/lib/active_record/associations.rb +452 -333
  9. data/lib/active_record/associations/association_collection.rb +66 -20
  10. data/lib/active_record/associations/association_proxy.rb +9 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
  12. data/lib/active_record/associations/has_many_association.rb +21 -57
  13. data/lib/active_record/associations/has_many_through_association.rb +38 -18
  14. data/lib/active_record/associations/has_one_association.rb +30 -14
  15. data/lib/active_record/attribute_methods.rb +253 -0
  16. data/lib/active_record/base.rb +719 -494
  17. data/lib/active_record/calculations.rb +62 -63
  18. data/lib/active_record/callbacks.rb +57 -83
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
  30. data/lib/active_record/fixtures.rb +503 -113
  31. data/lib/active_record/locking/optimistic.rb +72 -34
  32. data/lib/active_record/migration.rb +80 -57
  33. data/lib/active_record/observer.rb +13 -10
  34. data/lib/active_record/query_cache.rb +16 -57
  35. data/lib/active_record/reflection.rb +35 -38
  36. data/lib/active_record/schema.rb +5 -5
  37. data/lib/active_record/schema_dumper.rb +35 -13
  38. data/lib/active_record/serialization.rb +98 -0
  39. data/lib/active_record/serializers/json_serializer.rb +71 -0
  40. data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
  41. data/lib/active_record/timestamp.rb +20 -21
  42. data/lib/active_record/transactions.rb +39 -43
  43. data/lib/active_record/validations.rb +256 -107
  44. data/lib/active_record/version.rb +3 -3
  45. data/lib/activerecord.rb +1 -0
  46. data/test/aaa_create_tables_test.rb +15 -2
  47. data/test/abstract_unit.rb +24 -17
  48. data/test/active_schema_test_mysql.rb +20 -8
  49. data/test/adapter_test.rb +23 -5
  50. data/test/adapter_test_sqlserver.rb +15 -1
  51. data/test/aggregations_test.rb +16 -1
  52. data/test/all.sh +2 -2
  53. data/test/associations/ar_joins_test.rb +0 -0
  54. data/test/associations/callbacks_test.rb +51 -30
  55. data/test/associations/cascaded_eager_loading_test.rb +1 -29
  56. data/test/associations/eager_singularization_test.rb +145 -0
  57. data/test/associations/eager_test.rb +42 -6
  58. data/test/associations/extension_test.rb +6 -1
  59. data/test/associations/inner_join_association_test.rb +88 -0
  60. data/test/associations/join_model_test.rb +47 -16
  61. data/test/associations_test.rb +449 -226
  62. data/test/attribute_methods_test.rb +97 -0
  63. data/test/base_test.rb +251 -105
  64. data/test/binary_test.rb +22 -27
  65. data/test/calculations_test.rb +37 -5
  66. data/test/callbacks_test.rb +23 -0
  67. data/test/connection_test_firebird.rb +2 -2
  68. data/test/connection_test_mysql.rb +30 -0
  69. data/test/connections/native_mysql/connection.rb +3 -0
  70. data/test/connections/native_sqlite/connection.rb +5 -14
  71. data/test/connections/native_sqlite3/connection.rb +5 -14
  72. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  73. data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
  74. data/test/datatype_test_postgresql.rb +178 -27
  75. data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
  76. data/test/defaults_test.rb +8 -1
  77. data/test/deprecated_finder_test.rb +7 -128
  78. data/test/finder_test.rb +192 -54
  79. data/test/fixtures/all/developers.yml +0 -0
  80. data/test/fixtures/all/people.csv +0 -0
  81. data/test/fixtures/all/tasks.yml +0 -0
  82. data/test/fixtures/author.rb +12 -5
  83. data/test/fixtures/binaries.yml +130 -435
  84. data/test/fixtures/category.rb +6 -0
  85. data/test/fixtures/company.rb +8 -1
  86. data/test/fixtures/computer.rb +1 -0
  87. data/test/fixtures/contact.rb +16 -0
  88. data/test/fixtures/customer.rb +2 -2
  89. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  90. data/test/fixtures/db_definitions/db2.sql +4 -0
  91. data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
  92. data/test/fixtures/db_definitions/firebird.sql +6 -0
  93. data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
  94. data/test/fixtures/db_definitions/frontbase.sql +5 -0
  95. data/test/fixtures/db_definitions/openbase.sql +41 -25
  96. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  97. data/test/fixtures/db_definitions/oracle.sql +5 -0
  98. data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
  99. data/test/fixtures/db_definitions/postgresql.sql +87 -58
  100. data/test/fixtures/db_definitions/postgresql2.sql +1 -2
  101. data/test/fixtures/db_definitions/schema.rb +280 -0
  102. data/test/fixtures/db_definitions/schema2.rb +11 -0
  103. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  104. data/test/fixtures/db_definitions/sqlite.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/sybase.sql +4 -0
  107. data/test/fixtures/developer.rb +10 -0
  108. data/test/fixtures/example.log +1 -0
  109. data/test/fixtures/flowers.jpg +0 -0
  110. data/test/fixtures/item.rb +7 -0
  111. data/test/fixtures/items.yml +4 -0
  112. data/test/fixtures/joke.rb +0 -3
  113. data/test/fixtures/matey.rb +4 -0
  114. data/test/fixtures/mateys.yml +4 -0
  115. data/test/fixtures/minimalistic.rb +2 -0
  116. data/test/fixtures/minimalistics.yml +2 -0
  117. data/test/fixtures/mixins.yml +2 -100
  118. data/test/fixtures/parrot.rb +13 -0
  119. data/test/fixtures/parrots.yml +27 -0
  120. data/test/fixtures/parrots_pirates.yml +7 -0
  121. data/test/fixtures/pirate.rb +5 -0
  122. data/test/fixtures/pirates.yml +9 -0
  123. data/test/fixtures/post.rb +1 -0
  124. data/test/fixtures/project.rb +3 -2
  125. data/test/fixtures/reserved_words/distinct.yml +5 -0
  126. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  127. data/test/fixtures/reserved_words/group.yml +14 -0
  128. data/test/fixtures/reserved_words/select.yml +8 -0
  129. data/test/fixtures/reserved_words/values.yml +7 -0
  130. data/test/fixtures/ship.rb +3 -0
  131. data/test/fixtures/ships.yml +5 -0
  132. data/test/fixtures/tagging.rb +4 -0
  133. data/test/fixtures/taggings.yml +8 -1
  134. data/test/fixtures/topic.rb +13 -1
  135. data/test/fixtures/treasure.rb +4 -0
  136. data/test/fixtures/treasures.yml +10 -0
  137. data/test/fixtures_test.rb +205 -24
  138. data/test/inheritance_test.rb +7 -1
  139. data/test/json_serialization_test.rb +180 -0
  140. data/test/lifecycle_test.rb +1 -1
  141. data/test/locking_test.rb +85 -2
  142. data/test/migration_test.rb +206 -40
  143. data/test/mixin_test.rb +13 -515
  144. data/test/pk_test.rb +3 -6
  145. data/test/query_cache_test.rb +104 -0
  146. data/test/reflection_test.rb +16 -0
  147. data/test/reserved_word_test_mysql.rb +177 -0
  148. data/test/schema_dumper_test.rb +38 -3
  149. data/test/serialization_test.rb +47 -0
  150. data/test/transactions_test.rb +74 -23
  151. data/test/unconnected_test.rb +1 -1
  152. data/test/validations_test.rb +322 -32
  153. data/test/xml_serialization_test.rb +121 -44
  154. metadata +48 -41
  155. data/examples/associations.rb +0 -87
  156. data/examples/shared_setup.rb +0 -15
  157. data/examples/validation.rb +0 -85
  158. data/lib/active_record/acts/list.rb +0 -256
  159. data/lib/active_record/acts/nested_set.rb +0 -211
  160. data/lib/active_record/acts/tree.rb +0 -96
  161. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
  162. data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
  163. data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
  164. data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
  165. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
  166. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
  167. data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
  168. data/lib/active_record/deprecated_associations.rb +0 -104
  169. data/lib/active_record/deprecated_finders.rb +0 -44
  170. data/lib/active_record/vendor/simple.rb +0 -693
  171. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  172. data/lib/active_record/wrappings.rb +0 -58
  173. data/test/connections/native_sqlserver/connection.rb +0 -23
  174. data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
  175. data/test/deprecated_associations_test.rb +0 -396
  176. data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
  177. data/test/fixtures/db_definitions/mysql.sql +0 -234
  178. data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
  179. data/test/fixtures/db_definitions/mysql2.sql +0 -5
  180. data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
  181. data/test/fixtures/db_definitions/sqlserver.sql +0 -243
  182. data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
  183. data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
  184. data/test/fixtures/mixin.rb +0 -63
  185. data/test/mixin_nested_set_test.rb +0 -196
@@ -1,591 +0,0 @@
1
- require 'active_record/connection_adapters/abstract_adapter'
2
-
3
- require 'bigdecimal'
4
- require 'bigdecimal/util'
5
-
6
- # sqlserver_adapter.rb -- ActiveRecord adapter for Microsoft SQL Server
7
- #
8
- # Author: Joey Gibson <joey@joeygibson.com>
9
- # Date: 10/14/2004
10
- #
11
- # Modifications: DeLynn Berry <delynnb@megastarfinancial.com>
12
- # Date: 3/22/2005
13
- #
14
- # Modifications (ODBC): Mark Imbriaco <mark.imbriaco@pobox.com>
15
- # Date: 6/26/2005
16
-
17
- # Modifications (Migrations): Tom Ward <tom@popdog.net>
18
- # Date: 27/10/2005
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>
24
-
25
- module ActiveRecord
26
- class Base
27
- def self.sqlserver_connection(config) #:nodoc:
28
- require_library_or_gem 'dbi' unless self.class.const_defined?(:DBI)
29
-
30
- config = config.symbolize_keys
31
-
32
- mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
33
- username = config[:username] ? config[:username].to_s : 'sa'
34
- password = config[:password] ? config[:password].to_s : ''
35
- autocommit = config.key?(:autocommit) ? config[:autocommit] : true
36
- if mode == "ODBC"
37
- raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn)
38
- dsn = config[:dsn]
39
- driver_url = "DBI:ODBC:#{dsn}"
40
- else
41
- raise ArgumentError, "Missing Database. Argument ':database' must be set in order for this adapter to work." unless config.has_key?(:database)
42
- database = config[:database]
43
- host = config[:host] ? config[:host].to_s : 'localhost'
44
- driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};"
45
- end
46
- conn = DBI.connect(driver_url, username, password)
47
- conn["AutoCommit"] = autocommit
48
- ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password])
49
- end
50
- end # class Base
51
-
52
- module ConnectionAdapters
53
- class SQLServerColumn < Column# :nodoc:
54
- attr_reader :identity, :is_special
55
-
56
- def initialize(name, default, sql_type = nil, identity = false, null = true) # TODO: check ok to remove scale_value = 0
57
- super(name, default, sql_type, null)
58
- @identity = identity
59
- @is_special = sql_type =~ /text|ntext|image/i
60
- # TODO: check ok to remove @scale = scale_value
61
- # SQL Server only supports limits on *char and float types
62
- @limit = nil unless @type == :float or @type == :string
63
- end
64
-
65
- def simplified_type(field_type)
66
- case field_type
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
- end
73
- end
74
-
75
- def type_cast(value)
76
- return nil if value.nil?
77
- case type
78
- when :datetime then cast_to_datetime(value)
79
- when :timestamp then cast_to_time(value)
80
- when :time then cast_to_time(value)
81
- when :date then cast_to_datetime(value)
82
- when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
83
- else super
84
- end
85
- end
86
-
87
- def cast_to_time(value)
88
- return value if value.is_a?(Time)
89
- time_array = ParseDate.parsedate(value)
90
- Time.send(Base.default_timezone, *time_array) rescue nil
91
- end
92
-
93
- def cast_to_datetime(value)
94
- return value.to_time if value.is_a?(DBI::Timestamp)
95
-
96
- if value.is_a?(Time)
97
- if value.year != 0 and value.month != 0 and value.day != 0
98
- return value
99
- else
100
- return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
101
- end
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
-
108
- return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
109
- value
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
121
-
122
- # These methods will only allow the adapter to insert binary data with a length of 7K or less
123
- # because of a SQL Server statement length policy.
124
- def self.string_to_binary(value)
125
- value.gsub(/(\r|\n|\0|\x1a)/) do
126
- case $1
127
- when "\r" then "%00"
128
- when "\n" then "%01"
129
- when "\0" then "%02"
130
- when "\x1a" then "%03"
131
- end
132
- end
133
- end
134
-
135
- def self.binary_to_string(value)
136
- value.gsub(/(%00|%01|%02|%03)/) do
137
- case $1
138
- when "%00" then "\r"
139
- when "%01" then "\n"
140
- when "%02\0" then "\0"
141
- when "%03" then "\x1a"
142
- end
143
- end
144
- end
145
- end
146
-
147
- # In ADO mode, this adapter will ONLY work on Windows systems,
148
- # since it relies on Win32OLE, which, to my knowledge, is only
149
- # available on Windows.
150
- #
151
- # This mode also relies on the ADO support in the DBI module. If you are using the
152
- # one-click installer of Ruby, then you already have DBI installed, but
153
- # the ADO module is *NOT* installed. You will need to get the latest
154
- # source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/
155
- # unzip it, and copy the file
156
- # <tt>src/lib/dbd_ado/ADO.rb</tt>
157
- # to
158
- # <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt>
159
- # (you will more than likely need to create the ADO directory).
160
- # Once you've installed that file, you are ready to go.
161
- #
162
- # In ODBC mode, the adapter requires the ODBC support in the DBI module which requires
163
- # the Ruby ODBC module. Ruby ODBC 0.996 was used in development and testing,
164
- # and it is available at http://www.ch-werner.de/rubyodbc/
165
- #
166
- # Options:
167
- #
168
- # * <tt>:mode</tt> -- ADO or ODBC. Defaults to ADO.
169
- # * <tt>:username</tt> -- Defaults to sa.
170
- # * <tt>:password</tt> -- Defaults to empty string.
171
- #
172
- # ADO specific options:
173
- #
174
- # * <tt>:host</tt> -- Defaults to localhost.
175
- # * <tt>:database</tt> -- The name of the database. No default, must be provided.
176
- #
177
- # ODBC specific options:
178
- #
179
- # * <tt>:dsn</tt> -- Defaults to nothing.
180
- #
181
- # ADO code tested on Windows 2000 and higher systems,
182
- # running ruby 1.8.2 (2004-07-29) [i386-mswin32], and SQL Server 2000 SP3.
183
- #
184
- # ODBC code tested on a Fedora Core 4 system, running FreeTDS 0.63,
185
- # unixODBC 2.2.11, Ruby ODBC 0.996, Ruby DBI 0.0.23 and Ruby 1.8.2.
186
- # [Linux strongmad 2.6.11-1.1369_FC4 #1 Thu Jun 2 22:55:56 EDT 2005 i686 i686 i386 GNU/Linux]
187
- class SQLServerAdapter < AbstractAdapter
188
-
189
- def initialize(connection, logger, connection_options=nil)
190
- super(connection, logger)
191
- @connection_options = connection_options
192
- end
193
-
194
- def native_database_types
195
- {
196
- :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
197
- :string => { :name => "varchar", :limit => 255 },
198
- :text => { :name => "text" },
199
- :integer => { :name => "int" },
200
- :float => { :name => "float", :limit => 8 },
201
- :decimal => { :name => "decimal" },
202
- :datetime => { :name => "datetime" },
203
- :timestamp => { :name => "datetime" },
204
- :time => { :name => "datetime" },
205
- :date => { :name => "datetime" },
206
- :binary => { :name => "image"},
207
- :boolean => { :name => "bit"}
208
- }
209
- end
210
-
211
- def adapter_name
212
- 'SQLServer'
213
- end
214
-
215
- def supports_migrations? #:nodoc:
216
- true
217
- end
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
-
231
- # CONNECTION MANAGEMENT ====================================#
232
-
233
- # Returns true if the connection is active.
234
- def active?
235
- @connection.execute("SELECT 1").finish
236
- true
237
- rescue DBI::DatabaseError, DBI::InterfaceError
238
- false
239
- end
240
-
241
- # Reconnects to the database, returns false if no connection could be made.
242
- def reconnect!
243
- disconnect!
244
- @connection = DBI.connect(*@connection_options)
245
- rescue DBI::DatabaseError => e
246
- @logger.warn "#{adapter_name} reconnection failed: #{e.message}" if @logger
247
- false
248
- end
249
-
250
- # Disconnects from the database
251
-
252
- def disconnect!
253
- @connection.disconnect rescue nil
254
- end
255
-
256
- def columns(table_name, name = nil)
257
- return [] if table_name.blank?
258
- table_name = table_name.to_s if table_name.is_a?(Symbol)
259
- table_name = table_name.split('.')[-1] unless table_name.nil?
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
- }
275
- # Comment out if you want to have the Columns select statment logged.
276
- # Personally, I think it adds unnecessary bloat to the log.
277
- # If you do comment it out, make sure to un-comment the "result" line that follows
278
- result = log(sql, name) { @connection.select_all(sql) }
279
- #result = @connection.select_all(sql)
280
- columns = []
281
- result.each do |field|
282
- default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue]
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
288
- is_identity = field[:IsIdentity] == 1
289
- is_nullable = field[:IsNullable] == 'YES'
290
- columns << SQLServerColumn.new(field[:ColName], default, type, is_identity, is_nullable)
291
- end
292
- columns
293
- end
294
-
295
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
296
- execute(sql, name)
297
- id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"]
298
- end
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
-
308
- def execute(sql, name = nil)
309
- if sql =~ /^\s*INSERT/i && (table_name = query_requires_identity_insert?(sql))
310
- log(sql, name) do
311
- with_identity_insert_enabled(table_name) do
312
- @connection.execute(sql) do |handle|
313
- yield(handle) if block_given?
314
- end
315
- end
316
- end
317
- else
318
- log(sql, name) do
319
- @connection.execute(sql) do |handle|
320
- yield(handle) if block_given?
321
- end
322
- end
323
- end
324
- end
325
-
326
- def begin_db_transaction
327
- @connection["AutoCommit"] = false
328
- rescue Exception => e
329
- @connection["AutoCommit"] = true
330
- end
331
-
332
- def commit_db_transaction
333
- @connection.commit
334
- ensure
335
- @connection["AutoCommit"] = true
336
- end
337
-
338
- def rollback_db_transaction
339
- @connection.rollback
340
- ensure
341
- @connection["AutoCommit"] = true
342
- end
343
-
344
- def quote(value, column = nil)
345
- return value.quoted_id if value.respond_to?(:quoted_id)
346
-
347
- case value
348
- when TrueClass then '1'
349
- when FalseClass then '0'
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
353
- end
354
- end
355
-
356
- def quote_string(string)
357
- string.gsub(/\'/, "''")
358
- end
359
-
360
- def quote_column_name(name)
361
- "[#{name}]"
362
- end
363
-
364
- def add_limit_offset!(sql, options)
365
- if options[:limit] and options[:offset]
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
- if (options[:limit] + options[:offset]) >= total_rows
368
- options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
369
- end
370
- sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]} ")
371
- sql << ") AS tmp1"
372
- if options[:order]
373
- options[:order] = options[:order].split(',').map do |field|
374
- parts = field.split(" ")
375
- tc = parts[0]
376
- if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query
377
- tc.gsub!(/\./, '\\.\\[')
378
- tc << '\\]'
379
- end
380
- if sql =~ /#{tc} AS (t\d_r\d\d?)/
381
- parts[0] = $1
382
- elsif parts[0] =~ /\w+\.(\w+)/
383
- parts[0] = $1
384
- end
385
- parts.join(' ')
386
- end.join(', ')
387
- sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}"
388
- else
389
- sql << " ) AS tmp2"
390
- end
391
- elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
392
- sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
393
- "SELECT#{$1} TOP #{options[:limit]}"
394
- end unless options[:limit].nil?
395
- end
396
- end
397
-
398
- def recreate_database(name)
399
- drop_database(name)
400
- create_database(name)
401
- end
402
-
403
- def drop_database(name)
404
- execute "DROP DATABASE #{name}"
405
- end
406
-
407
- def create_database(name)
408
- execute "CREATE DATABASE #{name}"
409
- end
410
-
411
- def current_database
412
- @connection.select_one("select DB_NAME()")[0]
413
- end
414
-
415
- def tables(name = nil)
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
422
- end
423
- end
424
-
425
- def indexes(table_name, name = nil)
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
435
- end
436
- end
437
- indexes
438
- ensure
439
- ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = true
440
- end
441
-
442
- def rename_table(name, new_name)
443
- execute "EXEC sp_rename '#{name}', '#{new_name}'"
444
- end
445
-
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
-
456
- def rename_column(table, column, new_column_name)
457
- execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
458
- end
459
-
460
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
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)
463
- remove_default_constraint(table_name, 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}"
465
- end
466
- sql_commands.each {|c|
467
- execute(c)
468
- }
469
- end
470
-
471
- def remove_column(table_name, column_name)
472
- remove_check_constraints(table_name, column_name)
473
- remove_default_constraint(table_name, column_name)
474
- execute "ALTER TABLE [#{table_name}] DROP COLUMN [#{column_name}]"
475
- end
476
-
477
- def remove_default_constraint(table_name, column_name)
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|
481
- execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
482
- end
483
- end
484
-
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
491
- end
492
-
493
- def remove_index(table_name, options = {})
494
- execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}"
495
- end
496
-
497
- private
498
- def select(sql, name = nil)
499
- repair_special_columns(sql)
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
510
- end
511
- result << row_hash
512
- end
513
- end
514
- result
515
- end
516
-
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}"
532
- end
533
-
534
- def get_table_name(sql)
535
- if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
536
- $1
537
- elsif sql =~ /from\s+([^\(\s]+)\s*/i
538
- $1
539
- else
540
- nil
541
- end
542
- end
543
-
544
- def identity_column(table_name)
545
- @table_columns = {} unless @table_columns
546
- @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
547
- @table_columns[table_name].each do |col|
548
- return col.name if col.identity
549
- end
550
-
551
- return nil
552
- end
553
-
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
558
- end
559
-
560
- def change_order_direction(order)
561
- order.split(",").collect {|fragment|
562
- case fragment
563
- when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
564
- when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
565
- else String.new(fragment).split(',').join(' DESC,') + ' DESC'
566
- end
567
- }.join(",")
568
- end
569
-
570
- def get_special_columns(table_name)
571
- special = []
572
- @table_columns ||= {}
573
- @table_columns[table_name] ||= columns(table_name)
574
- @table_columns[table_name].each do |col|
575
- special << col.name if col.is_special
576
- end
577
- special
578
- end
579
-
580
- def repair_special_columns(sql)
581
- special_cols = get_special_columns(get_table_name(sql))
582
- for col in special_cols.to_a
583
- sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
584
- sql.gsub!(/ORDER BY #{col.to_s}/i, '')
585
- end
586
- sql
587
- end
588
-
589
- end #class SQLServerAdapter < AbstractAdapter
590
- end #module ConnectionAdapters
591
- end #module ActiveRecord