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.
- data/CHANGELOG +400 -1
- data/README +2 -2
- data/RUNNING_UNIT_TESTS +21 -3
- data/Rakefile +55 -10
- data/lib/active_record.rb +10 -4
- data/lib/active_record/acts/list.rb +15 -4
- data/lib/active_record/acts/nested_set.rb +11 -12
- data/lib/active_record/acts/tree.rb +13 -14
- data/lib/active_record/aggregations.rb +46 -22
- data/lib/active_record/associations.rb +213 -162
- data/lib/active_record/associations/association_collection.rb +45 -15
- data/lib/active_record/associations/association_proxy.rb +32 -13
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
- data/lib/active_record/associations/has_many_association.rb +37 -17
- data/lib/active_record/associations/has_many_through_association.rb +120 -30
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/attribute_methods.rb +75 -0
- data/lib/active_record/base.rb +282 -203
- data/lib/active_record/calculations.rb +95 -54
- data/lib/active_record/callbacks.rb +13 -24
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
- data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
- data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
- data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
- data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
- data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
- data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
- data/lib/active_record/deprecated_associations.rb +24 -10
- data/lib/active_record/deprecated_finders.rb +4 -1
- data/lib/active_record/fixtures.rb +37 -23
- data/lib/active_record/locking/optimistic.rb +106 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/migration.rb +8 -5
- data/lib/active_record/observer.rb +73 -34
- data/lib/active_record/reflection.rb +21 -7
- data/lib/active_record/schema_dumper.rb +33 -5
- data/lib/active_record/timestamp.rb +23 -34
- data/lib/active_record/transactions.rb +37 -30
- data/lib/active_record/validations.rb +46 -30
- data/lib/active_record/vendor/mysql.rb +20 -5
- data/lib/active_record/version.rb +2 -2
- data/lib/active_record/wrappings.rb +1 -2
- data/lib/active_record/xml_serialization.rb +308 -0
- data/test/aaa_create_tables_test.rb +5 -1
- data/test/abstract_unit.rb +18 -8
- data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
- data/test/adapter_test.rb +9 -7
- data/test/adapter_test_sqlserver.rb +81 -0
- data/test/aggregations_test.rb +29 -0
- data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
- data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
- data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
- data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
- data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
- data/test/associations_test.rb +339 -45
- data/test/attribute_methods_test.rb +49 -0
- data/test/base_test.rb +321 -67
- data/test/calculations_test.rb +48 -10
- data/test/callbacks_test.rb +13 -0
- data/test/connection_test_firebird.rb +8 -0
- data/test/connections/native_db2/connection.rb +18 -17
- data/test/connections/native_firebird/connection.rb +19 -17
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +18 -15
- data/test/connections/native_openbase/connection.rb +14 -15
- data/test/connections/native_oracle/connection.rb +16 -12
- data/test/connections/native_postgresql/connection.rb +16 -17
- data/test/connections/native_sqlite/connection.rb +3 -6
- data/test/connections/native_sqlite3/connection.rb +3 -6
- data/test/connections/native_sqlserver/connection.rb +16 -17
- data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
- data/test/connections/native_sybase/connection.rb +16 -17
- data/test/datatype_test_postgresql.rb +52 -0
- data/test/defaults_test.rb +52 -10
- data/test/deprecated_associations_test.rb +151 -107
- data/test/deprecated_finder_test.rb +83 -66
- data/test/empty_date_time_test.rb +25 -0
- data/test/finder_test.rb +118 -11
- data/test/fixtures/accounts.yml +6 -1
- data/test/fixtures/author.rb +27 -4
- data/test/fixtures/categorizations.yml +8 -2
- data/test/fixtures/category.rb +1 -2
- data/test/fixtures/comments.yml +0 -6
- data/test/fixtures/companies.yml +6 -1
- data/test/fixtures/company.rb +23 -1
- data/test/fixtures/company_in_module.rb +8 -10
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/customers.yml +9 -0
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +9 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
- data/test/fixtures/db_definitions/firebird.sql +13 -1
- data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
- data/test/fixtures/db_definitions/frontbase.sql +262 -0
- data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase2.sql +4 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
- data/test/fixtures/db_definitions/mysql.sql +23 -14
- data/test/fixtures/db_definitions/openbase.sql +13 -1
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +29 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
- data/test/fixtures/db_definitions/postgresql.sql +13 -3
- data/test/fixtures/db_definitions/schema.rb +29 -1
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +12 -3
- data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlserver.sql +35 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
- data/test/fixtures/db_definitions/sybase.sql +13 -4
- data/test/fixtures/developer.rb +12 -0
- data/test/fixtures/edge.rb +5 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/funny_jokes.yml +3 -7
- data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
- data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
- data/test/fixtures/mixin.rb +15 -0
- data/test/fixtures/mixins.yml +38 -0
- data/test/fixtures/post.rb +3 -2
- data/test/fixtures/project.rb +3 -1
- data/test/fixtures/topic.rb +6 -1
- data/test/fixtures/topics.yml +4 -4
- data/test/fixtures/vertex.rb +9 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures_test.rb +45 -0
- data/test/inheritance_test.rb +67 -6
- data/test/lifecycle_test.rb +40 -19
- data/test/locking_test.rb +170 -26
- data/test/method_scoping_test.rb +2 -2
- data/test/migration_test.rb +387 -110
- data/test/migration_test_firebird.rb +124 -0
- data/test/mixin_nested_set_test.rb +14 -2
- data/test/mixin_test.rb +56 -18
- data/test/modules_test.rb +8 -2
- data/test/multiple_db_test.rb +2 -2
- data/test/pk_test.rb +1 -0
- data/test/reflection_test.rb +8 -2
- data/test/schema_authorization_test_postgresql.rb +75 -0
- data/test/schema_dumper_test.rb +40 -4
- data/test/table_name_test_sqlserver.rb +23 -0
- data/test/threaded_connections_test.rb +19 -16
- data/test/transactions_test.rb +86 -72
- data/test/validations_test.rb +126 -56
- data/test/xml_serialization_test.rb +125 -0
- metadata +45 -11
- 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
|
49
|
-
attr_reader :identity, :is_special
|
53
|
+
class SQLServerColumn < Column# :nodoc:
|
54
|
+
attr_reader :identity, :is_special
|
50
55
|
|
51
|
-
def initialize(name, default, sql_type = nil,
|
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 =
|
54
|
-
@is_special = sql_type =~ /text|ntext|image/i
|
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 /
|
63
|
-
when /
|
64
|
-
when /
|
65
|
-
when /
|
66
|
-
|
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
|
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
|
-
|
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
|
-
|
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 <<
|
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
|
-
|
265
|
-
|
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
|
-
|
300
|
-
|
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)
|
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
|
342
|
-
when Date then "'#{value.
|
343
|
-
|
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
|
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
|
-
|
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(
|
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
|
415
|
-
|
416
|
-
|
417
|
-
|
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
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
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
|
-
|
438
|
-
|
439
|
-
|
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
|
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
|
-
|
463
|
-
|
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
|
469
|
-
|
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
|
473
|
-
|
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
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
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
|
-
|
511
|
+
result << row_hash
|
493
512
|
end
|
494
513
|
end
|
495
|
-
|
514
|
+
result
|
496
515
|
end
|
497
516
|
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
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
|
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
|
529
|
-
|
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)
|