activerecord-sqlserver-adapter 2.3.24 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +5 -108
- data/MIT-LICENSE +1 -1
- data/README.rdoc +33 -61
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +57 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +57 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +336 -0
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +33 -0
- data/lib/active_record/connection_adapters/sqlserver/query_cache.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +61 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +373 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +131 -1121
- data/lib/arel/engines/sql/compilers/sqlserver_compiler.rb +267 -0
- metadata +26 -76
- data/RUNNING_UNIT_TESTS +0 -31
- data/Rakefile +0 -60
- data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/active_record.rb +0 -151
- data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/odbc.rb +0 -40
- data/test/cases/aaaa_create_tables_test_sqlserver.rb +0 -19
- data/test/cases/adapter_test_sqlserver.rb +0 -755
- data/test/cases/attribute_methods_test_sqlserver.rb +0 -33
- data/test/cases/basics_test_sqlserver.rb +0 -86
- data/test/cases/calculations_test_sqlserver.rb +0 -20
- data/test/cases/column_test_sqlserver.rb +0 -354
- data/test/cases/connection_test_sqlserver.rb +0 -148
- data/test/cases/eager_association_test_sqlserver.rb +0 -42
- data/test/cases/execute_procedure_test_sqlserver.rb +0 -35
- data/test/cases/inheritance_test_sqlserver.rb +0 -28
- data/test/cases/method_scoping_test_sqlserver.rb +0 -28
- data/test/cases/migration_test_sqlserver.rb +0 -108
- data/test/cases/named_scope_test_sqlserver.rb +0 -21
- data/test/cases/offset_and_limit_test_sqlserver.rb +0 -108
- data/test/cases/pessimistic_locking_test_sqlserver.rb +0 -125
- data/test/cases/query_cache_test_sqlserver.rb +0 -24
- data/test/cases/schema_dumper_test_sqlserver.rb +0 -72
- data/test/cases/specific_schema_test_sqlserver.rb +0 -154
- data/test/cases/sqlserver_helper.rb +0 -140
- data/test/cases/table_name_test_sqlserver.rb +0 -38
- data/test/cases/transaction_test_sqlserver.rb +0 -93
- data/test/cases/unicode_test_sqlserver.rb +0 -54
- data/test/cases/validations_test_sqlserver.rb +0 -18
- data/test/connections/native_sqlserver/connection.rb +0 -26
- data/test/connections/native_sqlserver_odbc/connection.rb +0 -28
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +0 -11
- data/test/schema/sqlserver_specific_schema.rb +0 -113
@@ -0,0 +1,373 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module SchemaStatements
|
5
|
+
|
6
|
+
def native_database_types
|
7
|
+
{
|
8
|
+
:primary_key => "int NOT NULL IDENTITY(1,1) PRIMARY KEY",
|
9
|
+
:string => { :name => native_string_database_type, :limit => 255 },
|
10
|
+
:text => { :name => native_text_database_type },
|
11
|
+
:integer => { :name => "int", :limit => 4 },
|
12
|
+
:float => { :name => "float", :limit => 8 },
|
13
|
+
:decimal => { :name => "decimal" },
|
14
|
+
:datetime => { :name => "datetime" },
|
15
|
+
:timestamp => { :name => "datetime" },
|
16
|
+
:time => { :name => native_time_database_type },
|
17
|
+
:date => { :name => native_date_database_type },
|
18
|
+
:binary => { :name => native_binary_database_type },
|
19
|
+
:boolean => { :name => "bit"},
|
20
|
+
# These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
|
21
|
+
:char => { :name => 'char' },
|
22
|
+
:varchar_max => { :name => 'varchar(max)' },
|
23
|
+
:nchar => { :name => "nchar" },
|
24
|
+
:nvarchar => { :name => "nvarchar", :limit => 255 },
|
25
|
+
:nvarchar_max => { :name => "nvarchar(max)" },
|
26
|
+
:ntext => { :name => "ntext" }
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def tables(name = nil)
|
31
|
+
info_schema_query do
|
32
|
+
select_values "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME <> 'dtproperties'"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def table_exists?(table_name)
|
37
|
+
unquoted_table_name = unqualify_table_name(table_name)
|
38
|
+
super || tables.include?(unquoted_table_name) || views.include?(unquoted_table_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def indexes(table_name, name = nil)
|
42
|
+
unquoted_table_name = unqualify_table_name(table_name)
|
43
|
+
select("EXEC sp_helpindex #{quote_table_name(unquoted_table_name)}",name).inject([]) do |indexes,index|
|
44
|
+
index = index.with_indifferent_access
|
45
|
+
if index[:index_description] =~ /primary key/
|
46
|
+
indexes
|
47
|
+
else
|
48
|
+
name = index[:index_name]
|
49
|
+
unique = index[:index_description] =~ /unique/
|
50
|
+
columns = index[:index_keys].split(',').map do |column|
|
51
|
+
column.strip!
|
52
|
+
column.gsub! '(-)', '' if column.ends_with?('(-)')
|
53
|
+
column
|
54
|
+
end
|
55
|
+
indexes << IndexDefinition.new(table_name, name, unique, columns)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def columns(table_name, name = nil)
|
61
|
+
return [] if table_name.blank?
|
62
|
+
cache_key = unqualify_table_name(table_name)
|
63
|
+
@sqlserver_columns_cache[cache_key] ||= column_definitions(table_name).collect do |ci|
|
64
|
+
sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
|
65
|
+
SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_table(table_name, options = {})
|
70
|
+
super
|
71
|
+
remove_sqlserver_columns_cache_for(table_name)
|
72
|
+
end
|
73
|
+
|
74
|
+
def rename_table(table_name, new_name)
|
75
|
+
do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
|
76
|
+
end
|
77
|
+
|
78
|
+
def drop_table(table_name, options = {})
|
79
|
+
super
|
80
|
+
remove_sqlserver_columns_cache_for(table_name)
|
81
|
+
end
|
82
|
+
|
83
|
+
def add_column(table_name, column_name, type, options = {})
|
84
|
+
super
|
85
|
+
remove_sqlserver_columns_cache_for(table_name)
|
86
|
+
end
|
87
|
+
|
88
|
+
def remove_column(table_name, *column_names)
|
89
|
+
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
|
90
|
+
column_names.flatten.each do |column_name|
|
91
|
+
remove_check_constraints(table_name, column_name)
|
92
|
+
remove_default_constraint(table_name, column_name)
|
93
|
+
remove_indexes(table_name, column_name)
|
94
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
95
|
+
end
|
96
|
+
remove_sqlserver_columns_cache_for(table_name)
|
97
|
+
end
|
98
|
+
|
99
|
+
def change_column(table_name, column_name, type, options = {})
|
100
|
+
sql_commands = []
|
101
|
+
column_object = columns(table_name).detect { |c| c.name.to_s == column_name.to_s }
|
102
|
+
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
103
|
+
change_column_sql << " NOT NULL" if options[:null] == false
|
104
|
+
sql_commands << change_column_sql
|
105
|
+
if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
|
106
|
+
remove_default_constraint(table_name,column_name)
|
107
|
+
end
|
108
|
+
if options_include_default?(options)
|
109
|
+
remove_sqlserver_columns_cache_for(table_name)
|
110
|
+
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name,column_name)} DEFAULT #{quote(options[:default])} FOR #{quote_column_name(column_name)}"
|
111
|
+
end
|
112
|
+
sql_commands.each { |c| do_execute(c) }
|
113
|
+
remove_sqlserver_columns_cache_for(table_name)
|
114
|
+
end
|
115
|
+
|
116
|
+
def change_column_default(table_name, column_name, default)
|
117
|
+
remove_default_constraint(table_name, column_name)
|
118
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
|
119
|
+
remove_sqlserver_columns_cache_for(table_name)
|
120
|
+
end
|
121
|
+
|
122
|
+
def rename_column(table_name, column_name, new_column_name)
|
123
|
+
detect_column_for!(table_name,column_name)
|
124
|
+
do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
|
125
|
+
remove_sqlserver_columns_cache_for(table_name)
|
126
|
+
end
|
127
|
+
|
128
|
+
def remove_index!(table_name, index_name)
|
129
|
+
do_execute "DROP INDEX #{quote_table_name(table_name)}.#{quote_column_name(index_name)}"
|
130
|
+
end
|
131
|
+
|
132
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
133
|
+
type_limitable = ['string','integer','float','char','nchar','varchar','nvarchar'].include?(type.to_s)
|
134
|
+
limit = nil unless type_limitable
|
135
|
+
case type.to_s
|
136
|
+
when 'integer'
|
137
|
+
case limit
|
138
|
+
when 1..2 then 'smallint'
|
139
|
+
when 3..4, nil then 'integer'
|
140
|
+
when 5..8 then 'bigint'
|
141
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
142
|
+
end
|
143
|
+
else
|
144
|
+
super
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
149
|
+
column = detect_column_for!(table_name,column_name)
|
150
|
+
unless null || default.nil?
|
151
|
+
do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
152
|
+
end
|
153
|
+
sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
|
154
|
+
sql << " NOT NULL" unless null
|
155
|
+
do_execute sql
|
156
|
+
end
|
157
|
+
|
158
|
+
# === SQLServer Specific ======================================== #
|
159
|
+
|
160
|
+
def views(name = nil)
|
161
|
+
@sqlserver_views_cache ||=
|
162
|
+
info_schema_query { select_values("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME NOT IN ('sysconstraints','syssegments')") }
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
protected
|
167
|
+
|
168
|
+
# === SQLServer Specific ======================================== #
|
169
|
+
|
170
|
+
def column_definitions(table_name)
|
171
|
+
db_name = unqualify_db_name(table_name)
|
172
|
+
db_name_with_period = "#{db_name}." if db_name
|
173
|
+
table_name = unqualify_table_name(table_name)
|
174
|
+
sql = %{
|
175
|
+
SELECT
|
176
|
+
columns.TABLE_NAME as table_name,
|
177
|
+
columns.COLUMN_NAME as name,
|
178
|
+
columns.DATA_TYPE as type,
|
179
|
+
columns.COLUMN_DEFAULT as default_value,
|
180
|
+
columns.NUMERIC_SCALE as numeric_scale,
|
181
|
+
columns.NUMERIC_PRECISION as numeric_precision,
|
182
|
+
CASE
|
183
|
+
WHEN columns.DATA_TYPE IN ('nchar','nvarchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
|
184
|
+
ELSE COL_LENGTH(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
|
185
|
+
END as length,
|
186
|
+
CASE
|
187
|
+
WHEN columns.IS_NULLABLE = 'YES' THEN 1
|
188
|
+
ELSE NULL
|
189
|
+
END as is_nullable,
|
190
|
+
CASE
|
191
|
+
WHEN COLUMNPROPERTY(OBJECT_ID(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME), columns.COLUMN_NAME, 'IsIdentity') = 0 THEN NULL
|
192
|
+
ELSE 1
|
193
|
+
END as is_identity
|
194
|
+
FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
|
195
|
+
WHERE columns.TABLE_NAME = '#{table_name}'
|
196
|
+
ORDER BY columns.ordinal_position
|
197
|
+
}.gsub(/[ \t\r\n]+/,' ')
|
198
|
+
results = info_schema_query { select(sql,nil) }
|
199
|
+
results.collect do |ci|
|
200
|
+
ci = ci.symbolize_keys
|
201
|
+
ci[:type] = case ci[:type]
|
202
|
+
when /^bit|image|text|ntext|datetime$/
|
203
|
+
ci[:type]
|
204
|
+
when /^numeric|decimal$/i
|
205
|
+
"#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
|
206
|
+
when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
|
207
|
+
ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
|
208
|
+
else
|
209
|
+
ci[:type]
|
210
|
+
end
|
211
|
+
if ci[:default_value].nil? && views.include?(table_name)
|
212
|
+
real_table_name = table_name_or_views_table_name(table_name)
|
213
|
+
real_column_name = views_real_column_name(table_name,ci[:name])
|
214
|
+
col_default_sql = "SELECT c.COLUMN_DEFAULT FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_NAME = '#{real_table_name}' AND c.COLUMN_NAME = '#{real_column_name}'"
|
215
|
+
ci[:default_value] = info_schema_query { select_value(col_default_sql) }
|
216
|
+
end
|
217
|
+
ci[:default_value] = case ci[:default_value]
|
218
|
+
when nil, '(null)', '(NULL)'
|
219
|
+
nil
|
220
|
+
when /\A\((\w+\(\))\)\Z/
|
221
|
+
ci[:default_function] = $1
|
222
|
+
nil
|
223
|
+
else
|
224
|
+
match_data = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/m)
|
225
|
+
match_data ? match_data[1] : nil
|
226
|
+
end
|
227
|
+
ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
|
228
|
+
ci
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def remove_check_constraints(table_name, column_name)
|
233
|
+
constraints = info_schema_query { select_values("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'") }
|
234
|
+
constraints.each do |constraint|
|
235
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def remove_default_constraint(table_name, column_name)
|
240
|
+
select_all("EXEC sp_helpconstraint '#{quote_string(table_name)}','nomsg'").select do |row|
|
241
|
+
row['constraint_type'] == "DEFAULT on column #{column_name}"
|
242
|
+
end.each do |row|
|
243
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def remove_indexes(table_name, column_name)
|
248
|
+
indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
|
249
|
+
remove_index(table_name, {:name => index.name})
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# === SQLServer Specific (Misc Helpers) ========================= #
|
254
|
+
|
255
|
+
def info_schema_query
|
256
|
+
log_info_schema_queries ? yield : ActiveRecord::Base.silence{ yield }
|
257
|
+
end
|
258
|
+
|
259
|
+
def unqualify_table_name(table_name)
|
260
|
+
table_name.to_s.split('.').last.tr('[]','')
|
261
|
+
end
|
262
|
+
|
263
|
+
def unqualify_db_name(table_name)
|
264
|
+
table_names = table_name.to_s.split('.')
|
265
|
+
table_names.length == 3 ? table_names.first.tr('[]','') : nil
|
266
|
+
end
|
267
|
+
|
268
|
+
def get_table_name(sql)
|
269
|
+
if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
270
|
+
$1 || $2
|
271
|
+
elsif sql =~ /from\s+([^\(\s]+)\s*/i
|
272
|
+
$1
|
273
|
+
else
|
274
|
+
nil
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def default_constraint_name(table_name, column_name)
|
279
|
+
"DF_#{table_name}_#{column_name}"
|
280
|
+
end
|
281
|
+
|
282
|
+
def detect_column_for!(table_name, column_name)
|
283
|
+
unless column = columns(table_name).detect { |c| c.name == column_name.to_s }
|
284
|
+
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
285
|
+
end
|
286
|
+
column
|
287
|
+
end
|
288
|
+
|
289
|
+
# === SQLServer Specific (View Reflection) ====================== #
|
290
|
+
|
291
|
+
def view_table_name(table_name)
|
292
|
+
view_info = view_information(table_name)
|
293
|
+
view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
|
294
|
+
end
|
295
|
+
|
296
|
+
def view_information(table_name)
|
297
|
+
table_name = unqualify_table_name(table_name)
|
298
|
+
@sqlserver_view_information_cache[table_name] ||= begin
|
299
|
+
view_info = info_schema_query { select_one("SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'") }
|
300
|
+
if view_info
|
301
|
+
view_info = view_info.with_indifferent_access
|
302
|
+
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
|
303
|
+
view_info[:VIEW_DEFINITION] = info_schema_query { select_values("EXEC sp_helptext #{table_name}").join }
|
304
|
+
end
|
305
|
+
end
|
306
|
+
view_info
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def table_name_or_views_table_name(table_name)
|
311
|
+
unquoted_table_name = unqualify_table_name(table_name)
|
312
|
+
views.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
|
313
|
+
end
|
314
|
+
|
315
|
+
def views_real_column_name(table_name,column_name)
|
316
|
+
view_definition = view_information(table_name)[:VIEW_DEFINITION]
|
317
|
+
match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
|
318
|
+
match_data ? match_data[1] : column_name
|
319
|
+
end
|
320
|
+
|
321
|
+
# === SQLServer Specific (Column/View Caches) =================== #
|
322
|
+
|
323
|
+
def remove_sqlserver_columns_cache_for(table_name)
|
324
|
+
cache_key = unqualify_table_name(table_name)
|
325
|
+
@sqlserver_columns_cache[cache_key] = nil
|
326
|
+
initialize_sqlserver_caches(false)
|
327
|
+
end
|
328
|
+
|
329
|
+
def initialize_sqlserver_caches(reset_columns=true)
|
330
|
+
@sqlserver_columns_cache = {} if reset_columns
|
331
|
+
@sqlserver_views_cache = nil
|
332
|
+
@sqlserver_view_information_cache = {}
|
333
|
+
end
|
334
|
+
|
335
|
+
# === SQLServer Specific (Identity Inserts) ===================== #
|
336
|
+
|
337
|
+
def query_requires_identity_insert?(sql)
|
338
|
+
if insert_sql?(sql)
|
339
|
+
table_name = get_table_name(sql)
|
340
|
+
id_column = identity_column(table_name)
|
341
|
+
id_column && sql =~ /^\s*INSERT[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
|
342
|
+
else
|
343
|
+
false
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def insert_sql?(sql)
|
348
|
+
!(sql =~ /^\s*INSERT/i).nil?
|
349
|
+
end
|
350
|
+
|
351
|
+
def with_identity_insert_enabled(table_name)
|
352
|
+
table_name = quote_table_name(table_name_or_views_table_name(table_name))
|
353
|
+
set_identity_insert(table_name, true)
|
354
|
+
yield
|
355
|
+
ensure
|
356
|
+
set_identity_insert(table_name, false)
|
357
|
+
end
|
358
|
+
|
359
|
+
def set_identity_insert(table_name, enable = true)
|
360
|
+
sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
361
|
+
do_execute(sql,'IDENTITY_INSERT')
|
362
|
+
rescue Exception => e
|
363
|
+
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
|
364
|
+
end
|
365
|
+
|
366
|
+
def identity_column(table_name)
|
367
|
+
columns(table_name).detect(&:is_identity?)
|
368
|
+
end
|
369
|
+
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
@@ -1,6 +1,14 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
require 'active_record/connection_adapters/abstract_adapter'
|
3
|
-
require 'active_record/connection_adapters/
|
3
|
+
require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
|
4
|
+
require 'active_record/connection_adapters/sqlserver/database_limits'
|
5
|
+
require 'active_record/connection_adapters/sqlserver/database_statements'
|
6
|
+
require 'active_record/connection_adapters/sqlserver/errors'
|
7
|
+
require 'active_record/connection_adapters/sqlserver/query_cache'
|
8
|
+
require 'active_record/connection_adapters/sqlserver/schema_statements'
|
9
|
+
require 'active_record/connection_adapters/sqlserver/quoting'
|
10
|
+
require 'active_support/core_ext/kernel/requires'
|
11
|
+
require 'active_support/core_ext/string'
|
4
12
|
require 'base64'
|
5
13
|
|
6
14
|
module ActiveRecord
|
@@ -9,22 +17,25 @@ module ActiveRecord
|
|
9
17
|
|
10
18
|
def self.sqlserver_connection(config) #:nodoc:
|
11
19
|
config = config.dup.symbolize_keys!
|
12
|
-
config.reverse_merge! :mode => :
|
20
|
+
config.reverse_merge! :mode => :odbc, :host => 'localhost', :username => 'sa', :password => ''
|
13
21
|
mode = config[:mode].to_s.downcase.underscore.to_sym
|
14
22
|
case mode
|
15
|
-
when :dblib
|
16
|
-
require_library_or_gem 'tiny_tds'
|
17
|
-
warn("TinyTds v0.4.3 or higher required. Using #{TinyTds::VERSION}") unless TinyTds::Client.instance_methods.map(&:to_s).include?("active?")
|
18
23
|
when :odbc
|
19
|
-
require_library_or_gem 'odbc' unless defined?(ODBC)
|
20
|
-
require 'active_record/connection_adapters/sqlserver_adapter/core_ext/odbc'
|
21
24
|
raise ArgumentError, 'Missing :dsn configuration.' unless config.has_key?(:dsn)
|
25
|
+
if RUBY_VERSION < '1.9'
|
26
|
+
require_library_or_gem 'odbc'
|
27
|
+
else
|
28
|
+
begin
|
29
|
+
# TODO: [ODBC] Change this to 'odbc_utf8'
|
30
|
+
require_library_or_gem 'odbc'
|
31
|
+
rescue LoadError
|
32
|
+
require_library_or_gem 'odbc'
|
33
|
+
end
|
34
|
+
end unless ['::ODBC','::ODBC_UTF8','::ODBC_NONE'].any? { |odbc_ns| odbc_ns.constantize rescue nil }
|
35
|
+
require 'active_record/connection_adapters/sqlserver/core_ext/odbc'
|
22
36
|
when :adonet
|
23
37
|
require 'System.Data'
|
24
38
|
raise ArgumentError, 'Missing :database configuration.' unless config.has_key?(:database)
|
25
|
-
when :ado
|
26
|
-
raise NotImplementedError, 'Please use version 2.3.1 of the adapter for ADO connections. Future versions support ADO.NET.'
|
27
|
-
raise ArgumentError, 'Missing :database configuration.' unless config.has_key?(:database)
|
28
39
|
else
|
29
40
|
raise ArgumentError, "Unknown connection mode in #{config.inspect}."
|
30
41
|
end
|
@@ -54,26 +65,44 @@ module ActiveRecord
|
|
54
65
|
|
55
66
|
class << self
|
56
67
|
|
68
|
+
def string_to_utf8_encoding(value)
|
69
|
+
value.force_encoding('UTF-8') rescue value
|
70
|
+
end
|
71
|
+
|
57
72
|
def string_to_binary(value)
|
73
|
+
value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
|
58
74
|
"0x#{value.unpack("H*")[0]}"
|
59
75
|
end
|
60
76
|
|
61
77
|
def binary_to_string(value)
|
78
|
+
value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
|
62
79
|
value =~ /[^[:xdigit:]]/ ? value : [value].pack('H*')
|
63
80
|
end
|
64
81
|
|
65
82
|
end
|
66
83
|
|
67
|
-
def
|
68
|
-
|
84
|
+
def type_cast(value)
|
85
|
+
if value && type == :string && is_utf8?
|
86
|
+
self.class.string_to_utf8_encoding(value)
|
87
|
+
else
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def type_cast_code(var_name)
|
93
|
+
if type == :string && is_utf8?
|
94
|
+
"#{self.class.name}.string_to_utf8_encoding(#{var_name})"
|
95
|
+
else
|
96
|
+
super
|
97
|
+
end
|
69
98
|
end
|
70
99
|
|
71
|
-
def
|
72
|
-
@
|
100
|
+
def is_identity?
|
101
|
+
@sqlserver_options[:is_identity]
|
73
102
|
end
|
74
103
|
|
75
104
|
def is_utf8?
|
76
|
-
|
105
|
+
sql_type =~ /nvarchar|ntext|nchar/i
|
77
106
|
end
|
78
107
|
|
79
108
|
def default_function
|
@@ -124,7 +153,6 @@ module ActiveRecord
|
|
124
153
|
when /uniqueidentifier/i then :string
|
125
154
|
when /datetime/i then simplified_datetime
|
126
155
|
when /varchar\(max\)/ then :text
|
127
|
-
when /timestamp/ then :binary
|
128
156
|
else super
|
129
157
|
end
|
130
158
|
end
|
@@ -141,55 +169,38 @@ module ActiveRecord
|
|
141
169
|
end
|
142
170
|
end
|
143
171
|
|
144
|
-
end #SQLServerColumn
|
172
|
+
end #class SQLServerColumn
|
145
173
|
|
146
|
-
|
147
174
|
class SQLServerAdapter < AbstractAdapter
|
148
175
|
|
176
|
+
include Sqlserver::Quoting
|
177
|
+
include Sqlserver::DatabaseStatements
|
178
|
+
include Sqlserver::SchemaStatements
|
179
|
+
include Sqlserver::DatabaseLimits
|
180
|
+
include Sqlserver::QueryCache
|
181
|
+
include Sqlserver::Errors
|
182
|
+
|
149
183
|
ADAPTER_NAME = 'SQLServer'.freeze
|
150
|
-
VERSION = '
|
184
|
+
VERSION = '3.0.0'.freeze
|
151
185
|
DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+(\d{4})/
|
152
|
-
SUPPORTED_VERSIONS = [
|
153
|
-
LIMITABLE_TYPES = ['string','integer','float','char','nchar','varchar','nvarchar'].to_set.freeze
|
154
|
-
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
155
|
-
LOST_CONNECTION_EXCEPTIONS = {
|
156
|
-
:dblib => ['TinyTds::Error'],
|
157
|
-
:odbc => ['ODBC::Error'],
|
158
|
-
:adonet => ['TypeError','System::Data::SqlClient::SqlException']
|
159
|
-
}
|
160
|
-
LOST_CONNECTION_MESSAGES = {
|
161
|
-
:dblib => [/closed connection/, /dead or not enabled/, /server failed/i],
|
162
|
-
:odbc => [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i],
|
163
|
-
:adonet => [/current state is closed/, /network-related/]
|
164
|
-
}
|
186
|
+
SUPPORTED_VERSIONS = [2005,2008].freeze
|
165
187
|
|
166
|
-
attr_reader :database_version, :database_year,
|
167
|
-
:connection_supports_native_types
|
168
188
|
cattr_accessor :native_text_database_type, :native_binary_database_type, :native_string_database_type,
|
169
|
-
:log_info_schema_queries, :enable_default_unicode_types, :auto_connect
|
170
|
-
|
171
|
-
class << self
|
172
|
-
|
173
|
-
def type_limitable?(type)
|
174
|
-
LIMITABLE_TYPES.include?(type.to_s)
|
175
|
-
end
|
176
|
-
|
177
|
-
end
|
189
|
+
:log_info_schema_queries, :enable_default_unicode_types, :auto_connect,
|
190
|
+
:cs_equality_operator
|
178
191
|
|
179
192
|
def initialize(logger,config)
|
180
193
|
@connection_options = config
|
181
194
|
connect
|
182
|
-
super(
|
183
|
-
@database_version = info_schema_query { select_value('SELECT @@version') }
|
184
|
-
@database_year = DATABASE_VERSION_REGEXP.match(@database_version)[1].to_i rescue 0
|
195
|
+
super(raw_connection, logger)
|
185
196
|
initialize_sqlserver_caches
|
186
197
|
use_database
|
187
|
-
unless SUPPORTED_VERSIONS.include?(
|
198
|
+
unless SUPPORTED_VERSIONS.include?(database_year)
|
188
199
|
raise NotImplementedError, "Currently, only #{SUPPORTED_VERSIONS.to_sentence} are supported."
|
189
200
|
end
|
190
201
|
end
|
191
202
|
|
192
|
-
#
|
203
|
+
# === Abstract Adapter ========================================== #
|
193
204
|
|
194
205
|
def adapter_name
|
195
206
|
ADAPTER_NAME
|
@@ -199,116 +210,22 @@ module ActiveRecord
|
|
199
210
|
true
|
200
211
|
end
|
201
212
|
|
202
|
-
def
|
213
|
+
def supports_primary_key?
|
203
214
|
true
|
204
215
|
end
|
205
216
|
|
206
|
-
def
|
217
|
+
def supports_count_distinct?
|
207
218
|
true
|
208
219
|
end
|
209
220
|
|
210
|
-
def
|
221
|
+
def supports_ddl_transactions?
|
211
222
|
true
|
212
223
|
end
|
213
224
|
|
214
|
-
def
|
215
|
-
|
216
|
-
end
|
217
|
-
|
218
|
-
def sqlserver_2005?
|
219
|
-
@database_year == 2005
|
220
|
-
end
|
221
|
-
|
222
|
-
def sqlserver_2008?
|
223
|
-
@database_year == 2008
|
224
|
-
end
|
225
|
-
|
226
|
-
def version
|
227
|
-
self.class::VERSION
|
228
|
-
end
|
229
|
-
|
230
|
-
def inspect
|
231
|
-
"#<#{self.class} version: #{version}, year: #{@database_year}, connection_options: #{@connection_options.inspect}>"
|
232
|
-
end
|
233
|
-
|
234
|
-
def auto_connect
|
235
|
-
@@auto_connect.is_a?(FalseClass) ? false : true
|
236
|
-
end
|
237
|
-
|
238
|
-
def native_string_database_type
|
239
|
-
@@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
|
240
|
-
end
|
241
|
-
|
242
|
-
def native_text_database_type
|
243
|
-
@@native_text_database_type ||
|
244
|
-
if sqlserver_2005? || sqlserver_2008?
|
245
|
-
enable_default_unicode_types ? 'nvarchar(max)' : 'varchar(max)'
|
246
|
-
else
|
247
|
-
enable_default_unicode_types ? 'ntext' : 'text'
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
def native_time_database_type
|
252
|
-
sqlserver_2008? ? 'time' : 'datetime'
|
253
|
-
end
|
254
|
-
|
255
|
-
def native_date_database_type
|
256
|
-
sqlserver_2008? ? 'date' : 'datetime'
|
257
|
-
end
|
258
|
-
|
259
|
-
def native_binary_database_type
|
260
|
-
@@native_binary_database_type || ((sqlserver_2005? || sqlserver_2008?) ? 'varbinary(max)' : 'image')
|
261
|
-
end
|
262
|
-
|
263
|
-
|
264
|
-
# QUOTING ==================================================#
|
265
|
-
|
266
|
-
def quote(value, column = nil)
|
267
|
-
case value
|
268
|
-
when String, ActiveSupport::Multibyte::Chars
|
269
|
-
if column && column.type == :binary
|
270
|
-
column.class.string_to_binary(value)
|
271
|
-
elsif value.is_utf8? || (column && column.type == :string)
|
272
|
-
"N'#{quote_string(value)}'"
|
273
|
-
else
|
274
|
-
super
|
275
|
-
end
|
276
|
-
else
|
277
|
-
super
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
def quote_string(string)
|
282
|
-
string.to_s.gsub(/\'/, "''")
|
283
|
-
end
|
284
|
-
|
285
|
-
def quote_column_name(name)
|
286
|
-
@sqlserver_quoted_column_and_table_names[name] ||=
|
287
|
-
name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n.to_s.gsub(']', ']]')}]" }.join('.')
|
288
|
-
end
|
289
|
-
|
290
|
-
def quote_table_name(name)
|
291
|
-
quote_column_name(name)
|
292
|
-
end
|
293
|
-
|
294
|
-
def quoted_true
|
295
|
-
QUOTED_TRUE
|
296
|
-
end
|
297
|
-
|
298
|
-
def quoted_false
|
299
|
-
QUOTED_FALSE
|
300
|
-
end
|
301
|
-
|
302
|
-
def quoted_date(value)
|
303
|
-
if value.acts_like?(:time) && value.respond_to?(:usec)
|
304
|
-
"#{super}.#{sprintf("%03d",value.usec/1000)}"
|
305
|
-
else
|
306
|
-
super
|
307
|
-
end
|
225
|
+
def supports_savepoints?
|
226
|
+
true
|
308
227
|
end
|
309
228
|
|
310
|
-
# REFERENTIAL INTEGRITY ====================================#
|
311
|
-
|
312
229
|
def disable_referential_integrity
|
313
230
|
do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'"
|
314
231
|
yield
|
@@ -316,13 +233,9 @@ module ActiveRecord
|
|
316
233
|
do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'"
|
317
234
|
end
|
318
235
|
|
319
|
-
#
|
236
|
+
# === Abstract Adapter (Connection Management) ================== #
|
320
237
|
|
321
238
|
def active?
|
322
|
-
case @connection_options[:mode]
|
323
|
-
when :dblib
|
324
|
-
@connection.active?
|
325
|
-
end
|
326
239
|
raw_connection_do("SELECT 1")
|
327
240
|
true
|
328
241
|
rescue *lost_connection_exceptions
|
@@ -336,521 +249,113 @@ module ActiveRecord
|
|
336
249
|
end
|
337
250
|
|
338
251
|
def disconnect!
|
339
|
-
case
|
340
|
-
when :dblib
|
341
|
-
@connection.close rescue nil
|
252
|
+
case connection_mode
|
342
253
|
when :odbc
|
343
|
-
|
254
|
+
raw_connection.disconnect rescue nil
|
344
255
|
else :adonet
|
345
|
-
|
256
|
+
raw_connection.close rescue nil
|
346
257
|
end
|
347
258
|
end
|
348
259
|
|
349
|
-
|
350
|
-
|
351
|
-
def user_options
|
352
|
-
info_schema_query do
|
353
|
-
select_rows("dbcc useroptions").inject(HashWithIndifferentAccess.new) do |values,row|
|
354
|
-
set_option = row[0].gsub(/\s+/,'_')
|
355
|
-
user_value = row[1]
|
356
|
-
values[set_option] = user_value
|
357
|
-
values
|
358
|
-
end
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
VALID_ISOLATION_LEVELS = ["READ COMMITTED", "READ UNCOMMITTED", "REPEATABLE READ", "SERIALIZABLE", "SNAPSHOT"]
|
363
|
-
|
364
|
-
def run_with_isolation_level(isolation_level)
|
365
|
-
raise ArgumentError, "Invalid isolation level, #{isolation_level}. Supported levels include #{VALID_ISOLATION_LEVELS.to_sentence}." if !VALID_ISOLATION_LEVELS.include?(isolation_level.upcase)
|
366
|
-
initial_isolation_level = user_options[:isolation_level] || "READ COMMITTED"
|
367
|
-
do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
|
368
|
-
begin
|
369
|
-
yield
|
370
|
-
ensure
|
371
|
-
do_execute "SET TRANSACTION ISOLATION LEVEL #{initial_isolation_level}"
|
372
|
-
end if block_given?
|
373
|
-
end
|
374
|
-
|
375
|
-
def select_one(sql, name = nil)
|
376
|
-
result = raw_select sql, name, :fetch => :one
|
377
|
-
(result && result.first.present?) ? result.first : nil
|
378
|
-
end
|
379
|
-
|
380
|
-
def select_one_with_query_cache(*args)
|
381
|
-
if @query_cache_enabled
|
382
|
-
cache_sql(args.first) { select_one_without_query_cache(*args) }
|
383
|
-
else
|
384
|
-
select_one_without_query_cache(*args)
|
385
|
-
end
|
386
|
-
end
|
387
|
-
alias_method_chain :select_one, :query_cache
|
388
|
-
|
389
|
-
def select_rows(sql, name = nil)
|
390
|
-
raw_select sql, name, :fetch => :rows
|
391
|
-
end
|
392
|
-
|
393
|
-
def execute(sql, name = nil, skip_logging = false)
|
394
|
-
if id_insert_table_name = query_requires_identity_insert?(sql)
|
395
|
-
with_identity_insert_enabled(id_insert_table_name) { do_execute(sql,name) }
|
396
|
-
else
|
397
|
-
do_execute(sql,name)
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
|
-
def execute_procedure(proc_name, *variables)
|
402
|
-
vars = variables.map{ |v| quote(v) }.join(', ')
|
403
|
-
sql = "EXEC #{proc_name} #{vars}".strip
|
404
|
-
name = 'Execute Procedure'
|
405
|
-
log(sql, name) do
|
406
|
-
case @connection_options[:mode]
|
407
|
-
when :dblib
|
408
|
-
result = @connection.execute(sql)
|
409
|
-
result.each(:as => :hash, :cache_rows => true) do |row|
|
410
|
-
r = row.with_indifferent_access
|
411
|
-
yield(r) if block_given?
|
412
|
-
end
|
413
|
-
result.each.map{ |row| row.is_a?(Hash) ? row.with_indifferent_access : row }
|
414
|
-
when :odbc
|
415
|
-
results = []
|
416
|
-
raw_connection_run(sql) do |handle|
|
417
|
-
get_rows = lambda {
|
418
|
-
rows = handle_to_names_and_values handle, :fetch => :all
|
419
|
-
rows.each_with_index { |r,i| rows[i] = r.with_indifferent_access }
|
420
|
-
results << rows
|
421
|
-
}
|
422
|
-
get_rows.call
|
423
|
-
while handle_more_results?(handle)
|
424
|
-
get_rows.call
|
425
|
-
end
|
426
|
-
end
|
427
|
-
results.many? ? results : results.first
|
428
|
-
when :adonet
|
429
|
-
results = []
|
430
|
-
results << select(sql, name).map { |r| r.with_indifferent_access }
|
431
|
-
results.many? ? results : results.first
|
432
|
-
end
|
433
|
-
end
|
434
|
-
end
|
435
|
-
|
436
|
-
def use_database(database=nil)
|
437
|
-
database ||= @connection_options[:database]
|
438
|
-
do_execute "USE #{quote_table_name(database)}" unless database.blank?
|
439
|
-
end
|
440
|
-
|
441
|
-
def outside_transaction?
|
442
|
-
info_schema_query { select_value("SELECT @@TRANCOUNT") == 0 }
|
260
|
+
def reset!
|
261
|
+
remove_database_connections_and_rollback { }
|
443
262
|
end
|
444
263
|
|
445
|
-
|
446
|
-
do_execute "BEGIN TRANSACTION"
|
447
|
-
end
|
448
|
-
|
449
|
-
def commit_db_transaction
|
450
|
-
do_execute "COMMIT TRANSACTION"
|
451
|
-
end
|
452
|
-
|
453
|
-
def rollback_db_transaction
|
454
|
-
do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
|
455
|
-
end
|
264
|
+
# === Abstract Adapter (Misc Support) =========================== #
|
456
265
|
|
457
|
-
def
|
458
|
-
|
266
|
+
def pk_and_sequence_for(table_name)
|
267
|
+
idcol = identity_column(table_name)
|
268
|
+
idcol ? [idcol.name,nil] : nil
|
459
269
|
end
|
460
270
|
|
461
|
-
def
|
462
|
-
|
463
|
-
|
464
|
-
def rollback_to_savepoint
|
465
|
-
do_execute "ROLLBACK TRANSACTION #{current_savepoint_name}"
|
466
|
-
end
|
467
|
-
|
468
|
-
def add_limit_offset!(sql, options)
|
469
|
-
# Validate and/or convert integers for :limit and :offets options.
|
470
|
-
if options[:offset]
|
471
|
-
raise ArgumentError, "offset should have a limit" unless options[:limit]
|
472
|
-
unless options[:offset].kind_of?(Integer)
|
473
|
-
if options[:offset] =~ /^\d+$/
|
474
|
-
options[:offset] = options[:offset].to_i
|
475
|
-
else
|
476
|
-
raise ArgumentError, "offset should be an integer"
|
477
|
-
end
|
478
|
-
end
|
479
|
-
end
|
480
|
-
if options[:limit] && !(options[:limit].kind_of?(Integer))
|
481
|
-
if options[:limit] =~ /^\d+$/
|
482
|
-
options[:limit] = options[:limit].to_i
|
483
|
-
else
|
484
|
-
raise ArgumentError, "limit should be an integer"
|
485
|
-
end
|
486
|
-
end
|
487
|
-
# The business of adding limit/offset
|
488
|
-
if options[:limit] and options[:offset]
|
489
|
-
tally_sql = "SELECT count(*) as TotalRows from (#{sql.sub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally"
|
490
|
-
add_lock! tally_sql, options
|
491
|
-
total_rows = select_value(tally_sql).to_i
|
492
|
-
if (options[:limit] + options[:offset]) >= total_rows
|
493
|
-
options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
|
494
|
-
end
|
495
|
-
# Make sure we do not need a special limit/offset for association limiting. http://gist.github.com/25118
|
496
|
-
add_limit_offset_for_association_limiting!(sql,options) and return if sql_for_association_limiting?(sql)
|
497
|
-
# Wrap the SQL query in a bunch of outer SQL queries that emulate proper LIMIT,OFFSET support.
|
498
|
-
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]}")
|
499
|
-
sql << ") AS tmp1"
|
500
|
-
if options[:order]
|
501
|
-
order = options[:order].split(',').map do |field|
|
502
|
-
order_by_column, order_direction = field.split(" ")
|
503
|
-
order_by_column = quote_column_name(order_by_column)
|
504
|
-
# Investigate the SQL query to figure out if the order_by_column has been renamed.
|
505
|
-
if sql =~ /#{Regexp.escape(order_by_column)} AS (t\d+_r\d+)/
|
506
|
-
# Fx "[foo].[bar] AS t4_r2" was found in the SQL. Use the column alias (ie 't4_r2') for the subsequent orderings
|
507
|
-
order_by_column = $1
|
508
|
-
elsif order_by_column =~ /\w+\.\[?(\w+)\]?/
|
509
|
-
order_by_column = $1
|
510
|
-
else
|
511
|
-
# It doesn't appear that the column name has been renamed as part of the query. Use just the column
|
512
|
-
# name rather than the full identifier for the outer queries.
|
513
|
-
order_by_column = order_by_column.split('.').last
|
514
|
-
end
|
515
|
-
# Put the column name and eventual direction back together
|
516
|
-
[order_by_column, order_direction].join(' ').strip
|
517
|
-
end.join(', ')
|
518
|
-
sql << " ORDER BY #{change_order_direction(order)}) AS tmp2 ORDER BY #{order}"
|
519
|
-
else
|
520
|
-
sql << ") AS tmp2"
|
521
|
-
end
|
522
|
-
elsif options[:limit] && sql !~ /^\s*SELECT (@@|COUNT\()/i
|
523
|
-
if md = sql.match(/^(\s*SELECT)(\s+DISTINCT)?(.*)/im)
|
524
|
-
sql.replace "#{md[1]}#{md[2]} TOP #{options[:limit]}#{md[3]}"
|
525
|
-
else
|
526
|
-
# Account for building SQL fragments without SELECT yet. See #update_all and #limited_update_conditions.
|
527
|
-
sql.replace "TOP #{options[:limit]} #{sql}"
|
528
|
-
end
|
529
|
-
end
|
271
|
+
def primary_key(table_name)
|
272
|
+
identity_column(table_name).try(:name)
|
530
273
|
end
|
531
274
|
|
532
|
-
|
533
|
-
# http://blog.sqlauthority.com/2007/04/27/sql-server-2005-locking-hints-and-examples/
|
534
|
-
return unless options[:lock]
|
535
|
-
lock_type = options[:lock] == true ? 'WITH(HOLDLOCK, ROWLOCK)' : options[:lock]
|
536
|
-
sql.gsub! %r|LEFT OUTER JOIN\s+(.*?)\s+ON|im, "LEFT OUTER JOIN \\1 #{lock_type} ON"
|
537
|
-
sql.gsub! %r{FROM\s([\w\[\]\.]+)}im, "FROM \\1 #{lock_type}"
|
538
|
-
end
|
275
|
+
# === SQLServer Specific (DB Reflection) ======================== #
|
539
276
|
|
540
|
-
def
|
541
|
-
|
277
|
+
def database_version
|
278
|
+
@database_version ||= info_schema_query { select_value('SELECT @@version') }
|
542
279
|
end
|
543
280
|
|
544
|
-
def
|
545
|
-
|
546
|
-
end
|
547
|
-
|
548
|
-
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
|
549
|
-
match_data = where_sql.match(/^(.*?[\]\) ])WHERE[\[\( ]/)
|
550
|
-
limit = match_data[1]
|
551
|
-
where_sql.sub!(limit,'')
|
552
|
-
"WHERE #{quoted_primary_key} IN (SELECT #{limit} #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
|
553
|
-
end
|
554
|
-
|
555
|
-
def newid_function
|
556
|
-
select_value "SELECT NEWID()"
|
557
|
-
end
|
558
|
-
|
559
|
-
def newsequentialid_function
|
560
|
-
select_value "SELECT NEWSEQUENTIALID()"
|
561
|
-
end
|
562
|
-
|
563
|
-
# SCHEMA STATEMENTS ========================================#
|
564
|
-
|
565
|
-
def native_database_types
|
566
|
-
@native_database_types ||= initialize_native_database_types.freeze
|
567
|
-
end
|
568
|
-
|
569
|
-
def table_alias_length
|
570
|
-
128
|
571
|
-
end
|
572
|
-
|
573
|
-
def tables(name = nil)
|
574
|
-
info_schema_query do
|
575
|
-
select_values "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME <> 'dtproperties'"
|
576
|
-
end
|
281
|
+
def database_year
|
282
|
+
DATABASE_VERSION_REGEXP.match(database_version)[1].to_i
|
577
283
|
end
|
578
284
|
|
579
|
-
def
|
580
|
-
|
581
|
-
info_schema_query { select_values("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME NOT IN ('sysconstraints','syssegments')") }
|
285
|
+
def sqlserver?
|
286
|
+
true
|
582
287
|
end
|
583
288
|
|
584
|
-
def
|
585
|
-
|
586
|
-
@sqlserver_view_information_cache[table_name] ||= begin
|
587
|
-
view_info = info_schema_query { select_one("SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'") }
|
588
|
-
if view_info
|
589
|
-
view_info = view_info.with_indifferent_access
|
590
|
-
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
|
591
|
-
view_info[:VIEW_DEFINITION] = info_schema_query do
|
592
|
-
begin
|
593
|
-
select_values("EXEC sp_helptext #{quote_table_name(table_name)}").join
|
594
|
-
rescue
|
595
|
-
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
|
596
|
-
end
|
597
|
-
end
|
598
|
-
end
|
599
|
-
end
|
600
|
-
view_info
|
601
|
-
end
|
289
|
+
def sqlserver_2005?
|
290
|
+
database_year == 2005
|
602
291
|
end
|
603
292
|
|
604
|
-
def
|
605
|
-
|
606
|
-
view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
|
293
|
+
def sqlserver_2008?
|
294
|
+
database_year == 2008
|
607
295
|
end
|
608
296
|
|
609
|
-
def
|
610
|
-
|
297
|
+
def version
|
298
|
+
self.class::VERSION
|
611
299
|
end
|
612
300
|
|
613
|
-
def
|
614
|
-
|
615
|
-
data = select("EXEC sp_helpindex #{quote_table_name(unquoted_table_name)}",name) rescue []
|
616
|
-
data.inject([]) do |indexes,index|
|
617
|
-
index = index.with_indifferent_access
|
618
|
-
if index[:index_description] =~ /primary key/
|
619
|
-
indexes
|
620
|
-
else
|
621
|
-
name = index[:index_name]
|
622
|
-
unique = index[:index_description] =~ /unique/
|
623
|
-
columns = index[:index_keys].split(',').map do |column|
|
624
|
-
column.strip!
|
625
|
-
column.gsub! '(-)', '' if column.ends_with?('(-)')
|
626
|
-
column
|
627
|
-
end
|
628
|
-
indexes << IndexDefinition.new(table_name, name, unique, columns)
|
629
|
-
end
|
630
|
-
end
|
301
|
+
def inspect
|
302
|
+
"#<#{self.class} version: #{version}, year: #{database_year}, connection_options: #{@connection_options.inspect}>"
|
631
303
|
end
|
632
304
|
|
633
|
-
def
|
634
|
-
|
635
|
-
cache_key = unqualify_table_name(table_name)
|
636
|
-
@sqlserver_columns_cache[cache_key] ||= column_definitions(table_name).collect do |ci|
|
637
|
-
sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
|
638
|
-
SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
|
639
|
-
end
|
305
|
+
def auto_connect
|
306
|
+
@@auto_connect.is_a?(FalseClass) ? false : true
|
640
307
|
end
|
641
308
|
|
642
|
-
def
|
643
|
-
|
644
|
-
remove_sqlserver_columns_cache_for(table_name)
|
309
|
+
def native_string_database_type
|
310
|
+
@@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
|
645
311
|
end
|
646
312
|
|
647
|
-
def
|
648
|
-
|
313
|
+
def native_text_database_type
|
314
|
+
@@native_text_database_type || enable_default_unicode_types ? 'nvarchar(max)' : 'varchar(max)'
|
649
315
|
end
|
650
316
|
|
651
|
-
def
|
652
|
-
|
653
|
-
remove_sqlserver_columns_cache_for(table_name)
|
317
|
+
def native_time_database_type
|
318
|
+
sqlserver_2008? ? 'time' : 'datetime'
|
654
319
|
end
|
655
320
|
|
656
|
-
def
|
657
|
-
|
658
|
-
remove_sqlserver_columns_cache_for(table_name)
|
321
|
+
def native_date_database_type
|
322
|
+
sqlserver_2008? ? 'date' : 'datetime'
|
659
323
|
end
|
660
324
|
|
661
|
-
def
|
662
|
-
|
663
|
-
column_names.flatten.each do |column_name|
|
664
|
-
remove_check_constraints(table_name, column_name)
|
665
|
-
remove_default_constraint(table_name, column_name)
|
666
|
-
remove_indexes(table_name, column_name)
|
667
|
-
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
668
|
-
end
|
669
|
-
remove_sqlserver_columns_cache_for(table_name)
|
325
|
+
def native_binary_database_type
|
326
|
+
@@native_binary_database_type || 'varbinary(max)'
|
670
327
|
end
|
671
328
|
|
672
|
-
def
|
673
|
-
|
674
|
-
column_object = columns(table_name).detect { |c| c.name.to_s == column_name.to_s }
|
675
|
-
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
676
|
-
change_column_sql << " NOT NULL" if options[:null] == false
|
677
|
-
sql_commands << change_column_sql
|
678
|
-
if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
|
679
|
-
remove_default_constraint(table_name,column_name)
|
680
|
-
end
|
681
|
-
if options_include_default?(options)
|
682
|
-
remove_sqlserver_columns_cache_for(table_name)
|
683
|
-
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_name(table_name,column_name)} DEFAULT #{quote(options[:default])} FOR #{quote_column_name(column_name)}"
|
684
|
-
end
|
685
|
-
sql_commands.each { |c| do_execute(c) }
|
686
|
-
remove_sqlserver_columns_cache_for(table_name)
|
329
|
+
def cs_equality_operator
|
330
|
+
@@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS ='
|
687
331
|
end
|
688
332
|
|
689
|
-
def change_column_default(table_name, column_name, default)
|
690
|
-
remove_default_constraint(table_name, column_name)
|
691
|
-
do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_name(table_name, column_name)} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
|
692
|
-
remove_sqlserver_columns_cache_for(table_name)
|
693
|
-
end
|
694
333
|
|
695
|
-
|
696
|
-
column_for(table_name,column_name)
|
697
|
-
do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
|
698
|
-
remove_sqlserver_columns_cache_for(table_name)
|
699
|
-
end
|
334
|
+
protected
|
700
335
|
|
701
|
-
|
702
|
-
do_execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}" rescue nil
|
703
|
-
end
|
336
|
+
# === Abstract Adapter (Misc Support) =========================== #
|
704
337
|
|
705
|
-
def
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
714
|
-
end
|
338
|
+
def translate_exception(e, message)
|
339
|
+
case message
|
340
|
+
when /cannot insert duplicate key .* with unique index/i
|
341
|
+
RecordNotUnique.new(message,e)
|
342
|
+
when /conflicted with the foreign key constraint/i
|
343
|
+
InvalidForeignKey.new(message,e)
|
344
|
+
when *lost_connection_messages
|
345
|
+
LostConnection.new(message,e)
|
715
346
|
else
|
716
347
|
super
|
717
348
|
end
|
718
349
|
end
|
719
350
|
|
720
|
-
|
721
|
-
# Disertation http://gist.github.com/24073
|
722
|
-
# Information http://weblogs.sqlteam.com/jeffs/archive/2007/12/13/select-distinct-order-by-error.aspx
|
723
|
-
return sql if options[:order].blank?
|
724
|
-
columns = sql.match(/SELECT\s+DISTINCT(.*?)FROM/)[1].strip
|
725
|
-
sql.sub!(/SELECT\s+DISTINCT/,'SELECT')
|
726
|
-
sql << "GROUP BY #{columns} ORDER BY #{order_to_min_set(options[:order])}"
|
727
|
-
end
|
728
|
-
|
729
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
730
|
-
column = column_for(table_name,column_name)
|
731
|
-
unless null || default.nil?
|
732
|
-
do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
733
|
-
end
|
734
|
-
sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
|
735
|
-
sql << " NOT NULL" unless null
|
736
|
-
do_execute sql
|
737
|
-
end
|
738
|
-
|
739
|
-
def pk_and_sequence_for(table_name)
|
740
|
-
idcol = identity_column(table_name)
|
741
|
-
idcol ? [idcol.name,nil] : nil
|
742
|
-
end
|
743
|
-
|
744
|
-
# RAKE UTILITY METHODS =====================================#
|
745
|
-
|
746
|
-
def recreate_database
|
747
|
-
remove_database_connections_and_rollback do
|
748
|
-
do_execute "EXEC sp_MSforeachtable 'DROP TABLE ?'"
|
749
|
-
end
|
750
|
-
end
|
751
|
-
|
752
|
-
def recreate_database!(database=nil)
|
753
|
-
current_db = current_database
|
754
|
-
database ||= current_db
|
755
|
-
this_db = database.to_s == current_db
|
756
|
-
do_execute 'USE master' if this_db
|
757
|
-
drop_database(database)
|
758
|
-
create_database(database)
|
759
|
-
ensure
|
760
|
-
use_database(current_db) if this_db
|
761
|
-
end
|
762
|
-
|
763
|
-
# Remove existing connections and rollback any transactions if we received the message
|
764
|
-
# 'Cannot drop the database 'test' because it is currently in use'
|
765
|
-
def drop_database(database)
|
766
|
-
retry_count = 0
|
767
|
-
max_retries = 1
|
768
|
-
begin
|
769
|
-
do_execute "DROP DATABASE #{quote_table_name(database)}"
|
770
|
-
rescue ActiveRecord::StatementInvalid => err
|
771
|
-
if err.message =~ /because it is currently in use/i
|
772
|
-
raise if retry_count >= max_retries
|
773
|
-
retry_count += 1
|
774
|
-
remove_database_connections_and_rollback(database)
|
775
|
-
retry
|
776
|
-
elsif err.message =~ /does not exist/i
|
777
|
-
nil
|
778
|
-
else
|
779
|
-
raise
|
780
|
-
end
|
781
|
-
end
|
782
|
-
end
|
783
|
-
|
784
|
-
def create_database(database)
|
785
|
-
do_execute "CREATE DATABASE #{quote_table_name(database)}"
|
786
|
-
end
|
787
|
-
|
788
|
-
def current_database
|
789
|
-
select_value 'SELECT DB_NAME()'
|
790
|
-
end
|
791
|
-
|
792
|
-
def charset
|
793
|
-
select_value "SELECT SERVERPROPERTY('SqlCharSetName')"
|
794
|
-
end
|
795
|
-
|
796
|
-
# This should disconnect all other users and rollback any transactions for SQL 2000 and 2005
|
797
|
-
# http://sqlserver2000.databases.aspfaq.com/how-do-i-drop-a-sql-server-database.html
|
798
|
-
def remove_database_connections_and_rollback(database=nil)
|
799
|
-
database ||= current_database
|
800
|
-
do_execute "ALTER DATABASE #{quote_table_name(database)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
|
801
|
-
begin
|
802
|
-
yield
|
803
|
-
ensure
|
804
|
-
do_execute "ALTER DATABASE #{quote_table_name(database)} SET MULTI_USER"
|
805
|
-
end if block_given?
|
806
|
-
end
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
protected
|
811
|
-
|
812
|
-
# CONNECTION MANAGEMENT ====================================#
|
351
|
+
# === SQLServer Specific (Connection Management) ================ #
|
813
352
|
|
814
353
|
def connect
|
815
354
|
config = @connection_options
|
816
|
-
@connection = case
|
817
|
-
when :dblib
|
818
|
-
appname = config[:appname] || configure_application_name || Rails.application.class.name.split('::').first rescue nil
|
819
|
-
login_timeout = config[:login_timeout].present? ? config[:login_timeout].to_i : nil
|
820
|
-
timeout = config[:timeout].present? ? config[:timeout].to_i/1000 : nil
|
821
|
-
encoding = config[:encoding].present? ? config[:encoding] : nil
|
822
|
-
TinyTds::Client.new({
|
823
|
-
:dataserver => config[:dataserver],
|
824
|
-
:host => config[:host],
|
825
|
-
:port => config[:port],
|
826
|
-
:username => config[:username],
|
827
|
-
:password => config[:password],
|
828
|
-
:database => config[:database],
|
829
|
-
:appname => appname,
|
830
|
-
:login_timeout => login_timeout,
|
831
|
-
:timeout => timeout,
|
832
|
-
:encoding => encoding
|
833
|
-
}).tap do |client|
|
834
|
-
client.execute("SET ANSI_DEFAULTS ON").do
|
835
|
-
client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
|
836
|
-
client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
|
837
|
-
end
|
355
|
+
@connection = case connection_mode
|
838
356
|
when :odbc
|
839
|
-
|
840
|
-
|
841
|
-
d.name = config[:dsn_name] || 'Driver1'
|
842
|
-
d.attrs = config[:dsn].split(';').map{ |atr| atr.split('=') }.reject{ |kv| kv.size != 2 }.inject({}){ |h,kv| k,v = kv ; h[k] = v ; h }
|
843
|
-
end
|
844
|
-
ODBC::Database.new.drvconnect(driver)
|
845
|
-
else
|
846
|
-
ODBC.connect config[:dsn], config[:username], config[:password]
|
847
|
-
end.tap do |c|
|
848
|
-
if c.respond_to?(:use_time)
|
849
|
-
c.use_time = true
|
850
|
-
c.use_utc = ActiveRecord::Base.default_timezone == :utc
|
851
|
-
@connection_supports_native_types = true
|
852
|
-
end
|
853
|
-
end
|
357
|
+
odbc = ['::ODBC','::ODBC_UTF8','::ODBC_NONE'].detect{ |odbc_ns| odbc_ns.constantize rescue nil }.constantize
|
358
|
+
odbc.connect config[:dsn], config[:username], config[:password]
|
854
359
|
when :adonet
|
855
360
|
System::Data::SqlClient::SqlConnection.new.tap do |connection|
|
856
361
|
connection.connection_string = System::Data::SqlClient::SqlConnectionStringBuilder.new.tap do |cs|
|
@@ -868,40 +373,25 @@ module ActiveRecord
|
|
868
373
|
connection.open
|
869
374
|
end
|
870
375
|
end
|
871
|
-
configure_connection
|
872
376
|
rescue
|
873
377
|
raise unless @auto_connecting
|
874
378
|
end
|
875
379
|
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
# For example:
|
885
|
-
# "myapp_#{$$}_#{Thread.current.object_id}".to(29)
|
886
|
-
def configure_application_name
|
887
|
-
end
|
888
|
-
|
889
|
-
def lost_connection_exceptions
|
890
|
-
exceptions = LOST_CONNECTION_EXCEPTIONS[@connection_options[:mode]]
|
891
|
-
@lost_connection_exceptions ||= exceptions ? exceptions.map(&:constantize) : []
|
892
|
-
end
|
893
|
-
|
894
|
-
def lost_connection_messages
|
895
|
-
LOST_CONNECTION_MESSAGES[@connection_options[:mode]]
|
380
|
+
def remove_database_connections_and_rollback(database=nil)
|
381
|
+
database ||= current_database
|
382
|
+
do_execute "ALTER DATABASE #{quote_table_name(database)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
|
383
|
+
begin
|
384
|
+
yield
|
385
|
+
ensure
|
386
|
+
do_execute "ALTER DATABASE #{quote_table_name(database)} SET MULTI_USER"
|
387
|
+
end if block_given?
|
896
388
|
end
|
897
389
|
|
898
390
|
def with_auto_reconnect
|
899
391
|
begin
|
900
392
|
yield
|
901
|
-
rescue
|
902
|
-
if
|
903
|
-
retry if auto_reconnected?
|
904
|
-
end
|
393
|
+
rescue Exception => e
|
394
|
+
retry if translate_exception(e,e.message).is_a?(LostConnection) && auto_reconnected?
|
905
395
|
raise
|
906
396
|
end
|
907
397
|
end
|
@@ -922,488 +412,8 @@ module ActiveRecord
|
|
922
412
|
@auto_connecting = false
|
923
413
|
end
|
924
414
|
|
925
|
-
def
|
926
|
-
|
927
|
-
case @connection_options[:mode]
|
928
|
-
when :dblib
|
929
|
-
@connection.execute(sql)
|
930
|
-
when :odbc
|
931
|
-
block_given? ? @connection.run_block(sql) { |handle| yield(handle) } : @connection.run(sql)
|
932
|
-
else :adonet
|
933
|
-
@connection.create_command.tap{ |cmd| cmd.command_text = sql }.execute_reader
|
934
|
-
end
|
935
|
-
end
|
936
|
-
end
|
937
|
-
|
938
|
-
def raw_connection_do(sql)
|
939
|
-
case @connection_options[:mode]
|
940
|
-
when :dblib
|
941
|
-
@insert_sql ? @connection.execute(sql).insert : @connection.execute(sql).do
|
942
|
-
when :odbc
|
943
|
-
@connection.do(sql)
|
944
|
-
else :adonet
|
945
|
-
@connection.create_command.tap{ |cmd| cmd.command_text = sql }.execute_non_query
|
946
|
-
end
|
947
|
-
end
|
948
|
-
|
949
|
-
def finish_statement_handle(handle)
|
950
|
-
case @connection_options[:mode]
|
951
|
-
when :dblib
|
952
|
-
handle.cancel if handle
|
953
|
-
when :odbc
|
954
|
-
handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
|
955
|
-
when :adonet
|
956
|
-
handle.close if handle && handle.respond_to?(:close) && !handle.is_closed
|
957
|
-
handle.dispose if handle && handle.respond_to?(:dispose)
|
958
|
-
end
|
959
|
-
handle
|
960
|
-
end
|
961
|
-
|
962
|
-
# DATABASE STATEMENTS ======================================
|
963
|
-
|
964
|
-
def select(sql, name = nil)
|
965
|
-
raw_select sql, name, :fetch => :all
|
966
|
-
end
|
967
|
-
|
968
|
-
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
969
|
-
@insert_sql = true
|
970
|
-
case @connection_options[:mode]
|
971
|
-
when :dblib
|
972
|
-
execute(sql, name) || id_value
|
973
|
-
else
|
974
|
-
super || select_value("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
|
975
|
-
end
|
976
|
-
ensure
|
977
|
-
@insert_sql = false
|
978
|
-
end
|
979
|
-
|
980
|
-
def update_sql(sql, name = nil)
|
981
|
-
@update_sql = true
|
982
|
-
case @connection_options[:mode]
|
983
|
-
when :dblib
|
984
|
-
sqlserver_2000? ? execute(sql, name) && select_value('SELECT @@ROWCOUNT AS AffectedRows') : execute(sql, name)
|
985
|
-
else
|
986
|
-
execute(sql, name)
|
987
|
-
select_value('SELECT @@ROWCOUNT AS AffectedRows')
|
988
|
-
end
|
989
|
-
ensure
|
990
|
-
@update_sql = false
|
991
|
-
end
|
992
|
-
|
993
|
-
def info_schema_query
|
994
|
-
log_info_schema_queries ? yield : ActiveRecord::Base.silence{ yield }
|
995
|
-
end
|
996
|
-
|
997
|
-
def do_execute(sql,name=nil)
|
998
|
-
log(sql, name || 'EXECUTE') do
|
999
|
-
with_auto_reconnect { raw_connection_do(sql) }
|
1000
|
-
end
|
1001
|
-
end
|
1002
|
-
|
1003
|
-
def raw_select(sql, name=nil, options={})
|
1004
|
-
log(sql,name) do
|
1005
|
-
begin
|
1006
|
-
handle = raw_connection_run(sql)
|
1007
|
-
handle_to_names_and_values(handle, options)
|
1008
|
-
ensure
|
1009
|
-
finish_statement_handle(handle)
|
1010
|
-
end
|
1011
|
-
end
|
1012
|
-
end
|
1013
|
-
|
1014
|
-
def handle_more_results?(handle)
|
1015
|
-
case @connection_options[:mode]
|
1016
|
-
when :dblib
|
1017
|
-
when :odbc
|
1018
|
-
handle.more_results
|
1019
|
-
when :adonet
|
1020
|
-
handle.next_result
|
1021
|
-
end
|
1022
|
-
end
|
1023
|
-
|
1024
|
-
def handle_to_names_and_values(handle, options={})
|
1025
|
-
case @connection_options[:mode]
|
1026
|
-
when :dblib
|
1027
|
-
handle_to_names_and_values_dblib(handle, options)
|
1028
|
-
when :odbc
|
1029
|
-
handle_to_names_and_values_odbc(handle, options)
|
1030
|
-
when :adonet
|
1031
|
-
handle_to_names_and_values_adonet(handle, options)
|
1032
|
-
end
|
1033
|
-
end
|
1034
|
-
|
1035
|
-
def handle_to_names_and_values_dblib(handle, options={})
|
1036
|
-
query_options = {}.tap do |qo|
|
1037
|
-
qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
|
1038
|
-
qo[:first] = true if options[:fetch] == :one
|
1039
|
-
qo[:as] = options[:fetch] == :rows ? :array : :hash
|
1040
|
-
end
|
1041
|
-
handle.each(query_options)
|
1042
|
-
end
|
1043
|
-
|
1044
|
-
def handle_to_names_and_values_odbc(handle, options={})
|
1045
|
-
@connection.use_utc = ActiveRecord::Base.default_timezone == :utc if @connection_supports_native_types
|
1046
|
-
case options[:fetch]
|
1047
|
-
when :all, :one
|
1048
|
-
if @connection_supports_native_types
|
1049
|
-
if options[:fetch] == :all
|
1050
|
-
handle.each_hash || []
|
1051
|
-
else
|
1052
|
-
row = handle.fetch_hash
|
1053
|
-
rows = row ? [row] : [[]]
|
1054
|
-
end
|
1055
|
-
else
|
1056
|
-
rows = if options[:fetch] == :all
|
1057
|
-
handle.fetch_all || []
|
1058
|
-
else
|
1059
|
-
row = handle.fetch
|
1060
|
-
row ? [row] : [[]]
|
1061
|
-
end
|
1062
|
-
names = handle.columns(true).map{ |c| c.name }
|
1063
|
-
names_and_values = []
|
1064
|
-
rows.each do |row|
|
1065
|
-
h = {}
|
1066
|
-
i = 0
|
1067
|
-
while i < row.size
|
1068
|
-
v = row[i]
|
1069
|
-
h[names[i]] = v.respond_to?(:to_sqlserver_string) ? v.to_sqlserver_string : v
|
1070
|
-
i += 1
|
1071
|
-
end
|
1072
|
-
names_and_values << h
|
1073
|
-
end
|
1074
|
-
names_and_values
|
1075
|
-
end
|
1076
|
-
when :rows
|
1077
|
-
rows = handle.fetch_all || []
|
1078
|
-
return rows if @connection_supports_native_types
|
1079
|
-
rows.each do |row|
|
1080
|
-
i = 0
|
1081
|
-
while i < row.size
|
1082
|
-
v = row[i]
|
1083
|
-
row[i] = v.to_sqlserver_string if v.respond_to?(:to_sqlserver_string)
|
1084
|
-
i += 1
|
1085
|
-
end
|
1086
|
-
end
|
1087
|
-
rows
|
1088
|
-
end
|
1089
|
-
end
|
1090
|
-
|
1091
|
-
def handle_to_names_and_values_adonet(handle, options={})
|
1092
|
-
if handle.has_rows
|
1093
|
-
names = []
|
1094
|
-
rows = []
|
1095
|
-
fields_named = options[:fetch] == :rows
|
1096
|
-
one_row_only = options[:fetch] == :one
|
1097
|
-
while handle.read
|
1098
|
-
row = []
|
1099
|
-
handle.visible_field_count.times do |row_index|
|
1100
|
-
value = handle.get_value(row_index)
|
1101
|
-
value = case value
|
1102
|
-
when System::String
|
1103
|
-
value.to_s
|
1104
|
-
when System::DBNull
|
1105
|
-
nil
|
1106
|
-
when System::DateTime
|
1107
|
-
value.to_string("yyyy-MM-dd HH:mm:ss.fff").to_s
|
1108
|
-
when @@array_of_bytes ||= System::Array[System::Byte]
|
1109
|
-
String.new(value)
|
1110
|
-
else
|
1111
|
-
value
|
1112
|
-
end
|
1113
|
-
row << value
|
1114
|
-
names << handle.get_name(row_index).to_s unless fields_named
|
1115
|
-
break if one_row_only
|
1116
|
-
end
|
1117
|
-
rows << row
|
1118
|
-
fields_named = true
|
1119
|
-
end
|
1120
|
-
else
|
1121
|
-
rows = []
|
1122
|
-
end
|
1123
|
-
if options[:fetch] != :rows
|
1124
|
-
names_and_values = []
|
1125
|
-
rows.each do |row|
|
1126
|
-
h = {}
|
1127
|
-
i = 0
|
1128
|
-
while i < row.size
|
1129
|
-
h[names[i]] = row[i]
|
1130
|
-
i += 1
|
1131
|
-
end
|
1132
|
-
names_and_values << h
|
1133
|
-
end
|
1134
|
-
names_and_values
|
1135
|
-
else
|
1136
|
-
rows
|
1137
|
-
end
|
1138
|
-
end
|
1139
|
-
|
1140
|
-
def add_limit_offset_for_association_limiting!(sql, options)
|
1141
|
-
sql.replace %|
|
1142
|
-
SET NOCOUNT ON
|
1143
|
-
DECLARE @row_number TABLE (row int identity(1,1), id int)
|
1144
|
-
INSERT INTO @row_number (id)
|
1145
|
-
#{sql}
|
1146
|
-
SET NOCOUNT OFF
|
1147
|
-
SELECT id FROM (
|
1148
|
-
SELECT TOP #{options[:limit]} * FROM (
|
1149
|
-
SELECT TOP #{options[:limit] + options[:offset]} * FROM @row_number ORDER BY row
|
1150
|
-
) AS tmp1 ORDER BY row DESC
|
1151
|
-
) AS tmp2 ORDER BY row
|
1152
|
-
|.gsub(/[ \t\r\n]+/,' ')
|
1153
|
-
end
|
1154
|
-
|
1155
|
-
# SCHEMA STATEMENTS ========================================#
|
1156
|
-
|
1157
|
-
def remove_check_constraints(table_name, column_name)
|
1158
|
-
constraints = info_schema_query { select_values("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'") }
|
1159
|
-
constraints.each do |constraint|
|
1160
|
-
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
|
1161
|
-
end
|
1162
|
-
end
|
1163
|
-
|
1164
|
-
def remove_default_constraint(table_name, column_name)
|
1165
|
-
select_all("EXEC sp_helpconstraint '#{quote_string(table_name)}','nomsg'").flatten.select do |row|
|
1166
|
-
row['constraint_type'] == "DEFAULT on column #{column_name}"
|
1167
|
-
end.each do |row|
|
1168
|
-
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
|
1169
|
-
end
|
1170
|
-
end
|
1171
|
-
|
1172
|
-
def remove_indexes(table_name, column_name)
|
1173
|
-
indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
|
1174
|
-
remove_index(table_name, {:name => index.name})
|
1175
|
-
end
|
1176
|
-
end
|
1177
|
-
|
1178
|
-
def default_name(table_name, column_name)
|
1179
|
-
"DF_#{table_name}_#{column_name}"
|
1180
|
-
end
|
1181
|
-
|
1182
|
-
# IDENTITY INSERTS =========================================#
|
1183
|
-
|
1184
|
-
def with_identity_insert_enabled(table_name)
|
1185
|
-
table_name = quote_table_name(table_name_or_views_table_name(table_name))
|
1186
|
-
set_identity_insert(table_name, true)
|
1187
|
-
yield
|
1188
|
-
ensure
|
1189
|
-
set_identity_insert(table_name, false)
|
1190
|
-
end
|
1191
|
-
|
1192
|
-
def set_identity_insert(table_name, enable = true)
|
1193
|
-
sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
1194
|
-
do_execute(sql,'IDENTITY_INSERT')
|
1195
|
-
rescue Exception => e
|
1196
|
-
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
|
1197
|
-
end
|
1198
|
-
|
1199
|
-
def query_requires_identity_insert?(sql)
|
1200
|
-
if insert_sql?(sql)
|
1201
|
-
table_name = get_table_name(sql)
|
1202
|
-
id_column = identity_column(table_name)
|
1203
|
-
id_column && sql =~ /^\s*INSERT[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
|
1204
|
-
else
|
1205
|
-
false
|
1206
|
-
end
|
1207
|
-
end
|
1208
|
-
|
1209
|
-
def identity_column(table_name)
|
1210
|
-
columns(table_name).detect(&:is_identity?)
|
1211
|
-
end
|
1212
|
-
|
1213
|
-
def table_name_or_views_table_name(table_name)
|
1214
|
-
unquoted_table_name = unqualify_table_name(table_name)
|
1215
|
-
views.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
|
1216
|
-
end
|
1217
|
-
|
1218
|
-
# HELPER METHODS ===========================================#
|
1219
|
-
|
1220
|
-
def insert_sql?(sql)
|
1221
|
-
!(sql =~ /^\s*INSERT/i).nil?
|
1222
|
-
end
|
1223
|
-
|
1224
|
-
def unqualify_table_name(table_name)
|
1225
|
-
table_name.to_s.split('.').last.gsub(/[\[\]]/,'')
|
1226
|
-
end
|
1227
|
-
|
1228
|
-
def unqualify_db_name(table_name)
|
1229
|
-
table_names = table_name.to_s.split('.')
|
1230
|
-
table_names.length == 3 ? table_names.first.tr('[]','') : nil
|
1231
|
-
end
|
1232
|
-
|
1233
|
-
def get_table_name(sql)
|
1234
|
-
if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
1235
|
-
$1 || $2
|
1236
|
-
elsif sql =~ /from\s+([^\(\s]+)\s*/i
|
1237
|
-
$1
|
1238
|
-
else
|
1239
|
-
nil
|
1240
|
-
end
|
1241
|
-
end
|
1242
|
-
|
1243
|
-
def orders_and_dirs_set(order)
|
1244
|
-
orders = order.sub('ORDER BY','').split(',').map(&:strip).reject(&:blank?)
|
1245
|
-
orders_dirs = orders.map do |ord|
|
1246
|
-
dir = nil
|
1247
|
-
ord.sub!(/\b(asc|desc)$/i) do |match|
|
1248
|
-
if match
|
1249
|
-
dir = match.upcase.strip
|
1250
|
-
''
|
1251
|
-
end
|
1252
|
-
end
|
1253
|
-
[ord.strip, dir]
|
1254
|
-
end
|
1255
|
-
end
|
1256
|
-
|
1257
|
-
def views_real_column_name(table_name,column_name)
|
1258
|
-
view_definition = view_information(table_name)[:VIEW_DEFINITION]
|
1259
|
-
match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
|
1260
|
-
match_data ? match_data[1] : column_name
|
1261
|
-
end
|
1262
|
-
|
1263
|
-
def order_to_min_set(order)
|
1264
|
-
orders_dirs = orders_and_dirs_set(order)
|
1265
|
-
orders_dirs.map do |o,d|
|
1266
|
-
"MIN(#{o}) #{d}".strip
|
1267
|
-
end.join(', ')
|
1268
|
-
end
|
1269
|
-
|
1270
|
-
def sql_for_association_limiting?(sql)
|
1271
|
-
if md = sql.match(/^\s*SELECT(.*)FROM.*GROUP BY.*ORDER BY.*/im)
|
1272
|
-
select_froms = md[1].split(',')
|
1273
|
-
select_froms.size == 1 && !select_froms.first.include?('*')
|
1274
|
-
end
|
1275
|
-
end
|
1276
|
-
|
1277
|
-
def remove_sqlserver_columns_cache_for(table_name)
|
1278
|
-
cache_key = unqualify_table_name(table_name)
|
1279
|
-
@sqlserver_columns_cache[cache_key] = nil
|
1280
|
-
initialize_sqlserver_caches(false)
|
1281
|
-
end
|
1282
|
-
|
1283
|
-
def initialize_sqlserver_caches(reset_columns=true)
|
1284
|
-
@sqlserver_columns_cache = {} if reset_columns
|
1285
|
-
@sqlserver_views_cache = nil
|
1286
|
-
@sqlserver_view_information_cache = {}
|
1287
|
-
@sqlserver_quoted_column_and_table_names = {}
|
1288
|
-
end
|
1289
|
-
|
1290
|
-
def initialize_native_database_types
|
1291
|
-
{
|
1292
|
-
:primary_key => "int NOT NULL IDENTITY(1,1) PRIMARY KEY",
|
1293
|
-
:string => { :name => native_string_database_type, :limit => 255 },
|
1294
|
-
:text => { :name => native_text_database_type },
|
1295
|
-
:integer => { :name => "int", :limit => 4 },
|
1296
|
-
:float => { :name => "float", :limit => 8 },
|
1297
|
-
:decimal => { :name => "decimal" },
|
1298
|
-
:datetime => { :name => "datetime" },
|
1299
|
-
:timestamp => { :name => "datetime" },
|
1300
|
-
:time => { :name => native_time_database_type },
|
1301
|
-
:date => { :name => native_date_database_type },
|
1302
|
-
:binary => { :name => native_binary_database_type },
|
1303
|
-
:boolean => { :name => "bit"},
|
1304
|
-
# These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
|
1305
|
-
:char => { :name => 'char' },
|
1306
|
-
:varchar_max => { :name => 'varchar(max)' },
|
1307
|
-
:nchar => { :name => "nchar" },
|
1308
|
-
:nvarchar => { :name => "nvarchar", :limit => 255 },
|
1309
|
-
:nvarchar_max => { :name => "nvarchar(max)" },
|
1310
|
-
:ntext => { :name => "ntext" },
|
1311
|
-
:ss_timestamp => { :name => 'timestamp'}
|
1312
|
-
}
|
1313
|
-
end
|
1314
|
-
|
1315
|
-
def column_definitions(table_name)
|
1316
|
-
db_name = unqualify_db_name(table_name)
|
1317
|
-
db_name_with_period = "#{db_name}." if db_name
|
1318
|
-
table_name = unqualify_table_name(table_name)
|
1319
|
-
sql = %{
|
1320
|
-
SELECT
|
1321
|
-
columns.TABLE_NAME as table_name,
|
1322
|
-
columns.COLUMN_NAME as name,
|
1323
|
-
columns.DATA_TYPE as type,
|
1324
|
-
columns.COLUMN_DEFAULT as default_value,
|
1325
|
-
columns.NUMERIC_SCALE as numeric_scale,
|
1326
|
-
columns.NUMERIC_PRECISION as numeric_precision,
|
1327
|
-
CASE
|
1328
|
-
WHEN columns.DATA_TYPE IN ('nchar','nvarchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
|
1329
|
-
ELSE COL_LENGTH(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
|
1330
|
-
END as length,
|
1331
|
-
CASE
|
1332
|
-
WHEN columns.IS_NULLABLE = 'YES' THEN 1
|
1333
|
-
ELSE NULL
|
1334
|
-
END as is_nullable,
|
1335
|
-
CASE
|
1336
|
-
WHEN COLUMNPROPERTY(OBJECT_ID(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME), columns.COLUMN_NAME, 'IsIdentity') = 0 THEN NULL
|
1337
|
-
ELSE 1
|
1338
|
-
END as is_identity
|
1339
|
-
FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
|
1340
|
-
WHERE columns.TABLE_NAME = '#{table_name}'
|
1341
|
-
ORDER BY columns.ordinal_position
|
1342
|
-
}.gsub(/[ \t\r\n]+/,' ')
|
1343
|
-
results = info_schema_query { select(sql,nil) }
|
1344
|
-
results.collect do |ci|
|
1345
|
-
ci = ci.symbolize_keys
|
1346
|
-
ci[:type] = case ci[:type]
|
1347
|
-
when /^bit|image|text|ntext|datetime$/
|
1348
|
-
ci[:type]
|
1349
|
-
when /^numeric|decimal$/i
|
1350
|
-
"#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
|
1351
|
-
when /^float|real$/i
|
1352
|
-
"#{ci[:type]}(#{ci[:numeric_precision]})"
|
1353
|
-
when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
|
1354
|
-
ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
|
1355
|
-
else
|
1356
|
-
ci[:type]
|
1357
|
-
end
|
1358
|
-
if ci[:default_value].nil? && views.include?(table_name)
|
1359
|
-
real_table_name = table_name_or_views_table_name(table_name)
|
1360
|
-
real_column_name = views_real_column_name(table_name,ci[:name])
|
1361
|
-
col_default_sql = "SELECT c.COLUMN_DEFAULT FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_NAME = '#{real_table_name}' AND c.COLUMN_NAME = '#{real_column_name}'"
|
1362
|
-
ci[:default_value] = info_schema_query { select_value(col_default_sql) }
|
1363
|
-
end
|
1364
|
-
ci[:default_value] = case ci[:default_value]
|
1365
|
-
when nil, '(null)', '(NULL)'
|
1366
|
-
nil
|
1367
|
-
when /\A\((\w+\(\))\)\Z/
|
1368
|
-
ci[:default_function] = $1
|
1369
|
-
nil
|
1370
|
-
else
|
1371
|
-
match_data = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/m)
|
1372
|
-
match_data ? match_data[1] : nil
|
1373
|
-
end
|
1374
|
-
ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
|
1375
|
-
ci
|
1376
|
-
end
|
1377
|
-
end
|
1378
|
-
|
1379
|
-
def column_for(table_name, column_name)
|
1380
|
-
unless column = columns(table_name).detect { |c| c.name == column_name.to_s }
|
1381
|
-
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
1382
|
-
end
|
1383
|
-
column
|
1384
|
-
end
|
1385
|
-
|
1386
|
-
def change_order_direction(order)
|
1387
|
-
order.split(",").collect {|fragment|
|
1388
|
-
case fragment
|
1389
|
-
when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
|
1390
|
-
when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
|
1391
|
-
else String.new(fragment).split(',').join(' DESC,') + ' DESC'
|
1392
|
-
end
|
1393
|
-
}.join(",")
|
1394
|
-
end
|
1395
|
-
|
1396
|
-
def special_columns(table_name)
|
1397
|
-
columns(table_name).select(&:is_special?).map(&:name)
|
1398
|
-
end
|
1399
|
-
|
1400
|
-
def repair_special_columns(sql)
|
1401
|
-
special_cols = special_columns(get_table_name(sql))
|
1402
|
-
for col in special_cols.to_a
|
1403
|
-
sql.gsub!(/((\.|\s|\()\[?#{col.to_s}\]?)\s?=\s?/, '\1 LIKE ')
|
1404
|
-
sql.gsub!(/ORDER BY #{col.to_s}/i, '')
|
1405
|
-
end
|
1406
|
-
sql
|
415
|
+
def connection_mode
|
416
|
+
@connection_options[:mode]
|
1407
417
|
end
|
1408
418
|
|
1409
419
|
end #class SQLServerAdapter < AbstractAdapter
|