activerecord-sqlserver-adapter-2000 3.0.15
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 +400 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +176 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +58 -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 +414 -0
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +35 -0
- data/lib/active_record/connection_adapters/sqlserver/query_cache.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +55 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +403 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +470 -0
- data/lib/activerecord-sqlserver-adapter.rb +1 -0
- data/lib/arel/visitors/sqlserver.rb +330 -0
- metadata +103 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
|
3
|
+
class LostConnection < WrappedDatabaseException
|
4
|
+
end
|
5
|
+
|
6
|
+
module ConnectionAdapters
|
7
|
+
module Sqlserver
|
8
|
+
module Errors
|
9
|
+
|
10
|
+
LOST_CONNECTION_EXCEPTIONS = {
|
11
|
+
:dblib => ['TinyTds::Error'],
|
12
|
+
:odbc => ['ODBC::Error','ODBC_UTF8::Error','ODBC_NONE::Error'],
|
13
|
+
:adonet => ['TypeError','System::Data::SqlClient::SqlException']
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
LOST_CONNECTION_MESSAGES = {
|
17
|
+
:dblib => [/closed connection/, /dead or not enabled/, /server failed/i],
|
18
|
+
:odbc => [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i],
|
19
|
+
:adonet => [/current state is closed/, /network-related/]
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
|
23
|
+
def lost_connection_exceptions
|
24
|
+
exceptions = LOST_CONNECTION_EXCEPTIONS[@connection_options[:mode]]
|
25
|
+
@lost_connection_exceptions ||= exceptions ? exceptions.map{ |e| e.constantize rescue nil }.compact : []
|
26
|
+
end
|
27
|
+
|
28
|
+
def lost_connection_messages
|
29
|
+
LOST_CONNECTION_MESSAGES[@connection_options[:mode]]
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module Quoting
|
5
|
+
|
6
|
+
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
7
|
+
|
8
|
+
def quote(value, column = nil)
|
9
|
+
case value
|
10
|
+
when String, ActiveSupport::Multibyte::Chars
|
11
|
+
if column && column.type == :binary
|
12
|
+
column.class.string_to_binary(value)
|
13
|
+
elsif value.is_utf8? || (column && column.type == :string)
|
14
|
+
"N'#{quote_string(value)}'"
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def quote_string(string)
|
24
|
+
string.to_s.gsub(/\'/, "''")
|
25
|
+
end
|
26
|
+
|
27
|
+
def quote_column_name(name)
|
28
|
+
@sqlserver_quoted_column_and_table_names[name] ||=
|
29
|
+
name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n}]" }.join('.')
|
30
|
+
end
|
31
|
+
|
32
|
+
def quote_table_name(name)
|
33
|
+
quote_column_name(name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def quoted_true
|
37
|
+
QUOTED_TRUE
|
38
|
+
end
|
39
|
+
|
40
|
+
def quoted_false
|
41
|
+
QUOTED_FALSE
|
42
|
+
end
|
43
|
+
|
44
|
+
def quoted_date(value)
|
45
|
+
if value.acts_like?(:time) && value.respond_to?(:usec)
|
46
|
+
"#{super}.#{sprintf("%03d",value.usec/1000)}"
|
47
|
+
else
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,403 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module SchemaStatements
|
5
|
+
|
6
|
+
def native_database_types
|
7
|
+
@native_database_types ||= initialize_native_database_types.freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
def tables(name = nil)
|
11
|
+
info_schema_query do
|
12
|
+
select_values "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME <> 'dtproperties' AND TABLE_SCHEMA = schema_name()"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def table_exists?(table_name)
|
17
|
+
unquoted_table_name = unqualify_table_name(table_name)
|
18
|
+
super || tables.include?(unquoted_table_name) || views.include?(unquoted_table_name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def indexes(table_name, name = nil)
|
22
|
+
unquoted_table_name = unqualify_table_name(table_name)
|
23
|
+
select("EXEC sp_helpindex #{quote_table_name(unquoted_table_name)}",name).inject([]) do |indexes,index|
|
24
|
+
index = index.with_indifferent_access
|
25
|
+
if index[:index_description] =~ /primary key/
|
26
|
+
indexes
|
27
|
+
else
|
28
|
+
name = index[:index_name]
|
29
|
+
unique = index[:index_description] =~ /unique/
|
30
|
+
columns = index[:index_keys].split(',').map do |column|
|
31
|
+
column.strip!
|
32
|
+
column.gsub! '(-)', '' if column.ends_with?('(-)')
|
33
|
+
column
|
34
|
+
end
|
35
|
+
indexes << IndexDefinition.new(table_name, name, unique, columns)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def columns(table_name, name = nil)
|
41
|
+
return [] if table_name.blank?
|
42
|
+
cache_key = columns_cache_key(table_name)
|
43
|
+
@sqlserver_columns_cache[cache_key] ||= column_definitions(table_name).collect do |ci|
|
44
|
+
sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
|
45
|
+
SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_table(table_name, options = {})
|
50
|
+
super
|
51
|
+
remove_sqlserver_columns_cache_for(table_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def rename_table(table_name, new_name)
|
55
|
+
do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
|
56
|
+
end
|
57
|
+
|
58
|
+
def drop_table(table_name, options = {})
|
59
|
+
super
|
60
|
+
remove_sqlserver_columns_cache_for(table_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_column(table_name, column_name, type, options = {})
|
64
|
+
super
|
65
|
+
remove_sqlserver_columns_cache_for(table_name)
|
66
|
+
end
|
67
|
+
|
68
|
+
def remove_column(table_name, *column_names)
|
69
|
+
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
|
70
|
+
column_names.flatten.each do |column_name|
|
71
|
+
remove_check_constraints(table_name, column_name)
|
72
|
+
remove_default_constraint(table_name, column_name)
|
73
|
+
remove_indexes(table_name, column_name)
|
74
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
75
|
+
end
|
76
|
+
remove_sqlserver_columns_cache_for(table_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def change_column(table_name, column_name, type, options = {})
|
80
|
+
sql_commands = []
|
81
|
+
column_object = columns(table_name).detect { |c| c.name.to_s == column_name.to_s }
|
82
|
+
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])}"
|
83
|
+
change_column_sql << " NOT NULL" if options[:null] == false
|
84
|
+
sql_commands << change_column_sql
|
85
|
+
if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
|
86
|
+
remove_default_constraint(table_name,column_name)
|
87
|
+
end
|
88
|
+
if options_include_default?(options)
|
89
|
+
remove_sqlserver_columns_cache_for(table_name)
|
90
|
+
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)}"
|
91
|
+
end
|
92
|
+
sql_commands.each { |c| do_execute(c) }
|
93
|
+
remove_sqlserver_columns_cache_for(table_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
def change_column_default(table_name, column_name, default)
|
97
|
+
remove_default_constraint(table_name, column_name)
|
98
|
+
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)}"
|
99
|
+
remove_sqlserver_columns_cache_for(table_name)
|
100
|
+
end
|
101
|
+
|
102
|
+
def rename_column(table_name, column_name, new_column_name)
|
103
|
+
detect_column_for!(table_name,column_name)
|
104
|
+
do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
|
105
|
+
remove_sqlserver_columns_cache_for(table_name)
|
106
|
+
end
|
107
|
+
|
108
|
+
def remove_index!(table_name, index_name)
|
109
|
+
do_execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
113
|
+
type_limitable = ['string','integer','float','char','nchar','varchar','nvarchar'].include?(type.to_s)
|
114
|
+
limit = nil unless type_limitable
|
115
|
+
case type.to_s
|
116
|
+
when 'integer'
|
117
|
+
case limit
|
118
|
+
when 1..2 then 'smallint'
|
119
|
+
when 3..4, nil then 'integer'
|
120
|
+
when 5..8 then 'bigint'
|
121
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
122
|
+
end
|
123
|
+
else
|
124
|
+
super
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
129
|
+
column = detect_column_for!(table_name,column_name)
|
130
|
+
unless null || default.nil?
|
131
|
+
do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
132
|
+
end
|
133
|
+
sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
|
134
|
+
sql << " NOT NULL" unless null
|
135
|
+
do_execute sql
|
136
|
+
end
|
137
|
+
|
138
|
+
# === SQLServer Specific ======================================== #
|
139
|
+
|
140
|
+
def views(name = nil)
|
141
|
+
@sqlserver_views_cache ||=
|
142
|
+
info_schema_query { select_values("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME NOT IN ('sysconstraints','syssegments')") }
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
protected
|
147
|
+
|
148
|
+
# === SQLServer Specific ======================================== #
|
149
|
+
|
150
|
+
def initialize_native_database_types
|
151
|
+
{
|
152
|
+
:primary_key => "int NOT NULL IDENTITY(1,1) PRIMARY KEY",
|
153
|
+
:string => { :name => native_string_database_type, :limit => 255 },
|
154
|
+
:text => { :name => native_text_database_type },
|
155
|
+
:integer => { :name => "int", :limit => 4 },
|
156
|
+
:float => { :name => "float", :limit => 8 },
|
157
|
+
:decimal => { :name => "decimal" },
|
158
|
+
:datetime => { :name => "datetime" },
|
159
|
+
:timestamp => { :name => "datetime" },
|
160
|
+
:time => { :name => native_time_database_type },
|
161
|
+
:date => { :name => native_date_database_type },
|
162
|
+
:binary => { :name => native_binary_database_type },
|
163
|
+
:boolean => { :name => "bit"},
|
164
|
+
# These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
|
165
|
+
:char => { :name => 'char' },
|
166
|
+
:varchar_max => { :name => 'varchar(max)' },
|
167
|
+
:nchar => { :name => "nchar" },
|
168
|
+
:nvarchar => { :name => "nvarchar", :limit => 255 },
|
169
|
+
:nvarchar_max => { :name => "nvarchar(max)" },
|
170
|
+
:ntext => { :name => "ntext" },
|
171
|
+
:ss_timestamp => { :name => 'timestamp' }
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
def column_definitions(table_name)
|
176
|
+
db_name = unqualify_db_name(table_name)
|
177
|
+
db_name_with_period = "#{db_name}." if db_name
|
178
|
+
table_schema = unqualify_table_schema(table_name)
|
179
|
+
table_name = unqualify_table_name(table_name)
|
180
|
+
sql = %{
|
181
|
+
SELECT
|
182
|
+
columns.TABLE_NAME as table_name,
|
183
|
+
columns.COLUMN_NAME as name,
|
184
|
+
columns.DATA_TYPE as type,
|
185
|
+
columns.COLUMN_DEFAULT as default_value,
|
186
|
+
columns.NUMERIC_SCALE as numeric_scale,
|
187
|
+
columns.NUMERIC_PRECISION as numeric_precision,
|
188
|
+
CASE
|
189
|
+
WHEN columns.DATA_TYPE IN ('nchar','nvarchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
|
190
|
+
ELSE COL_LENGTH(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
|
191
|
+
END as length,
|
192
|
+
CASE
|
193
|
+
WHEN columns.IS_NULLABLE = 'YES' THEN 1
|
194
|
+
ELSE NULL
|
195
|
+
END as is_nullable,
|
196
|
+
CASE
|
197
|
+
WHEN COLUMNPROPERTY(OBJECT_ID(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME), columns.COLUMN_NAME, 'IsIdentity') = 0 THEN NULL
|
198
|
+
ELSE 1
|
199
|
+
END as is_identity
|
200
|
+
FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
|
201
|
+
WHERE columns.TABLE_NAME = '#{table_name}'
|
202
|
+
AND columns.TABLE_SCHEMA = #{table_schema.nil? ? "schema_name() " : "'#{table_schema}' "}
|
203
|
+
ORDER BY columns.ordinal_position
|
204
|
+
}.gsub(/[ \t\r\n]+/,' ')
|
205
|
+
results = info_schema_query { select(sql,nil) }
|
206
|
+
results.collect do |ci|
|
207
|
+
ci = ci.symbolize_keys
|
208
|
+
ci[:type] = case ci[:type]
|
209
|
+
when /^bit|image|text|ntext|datetime$/
|
210
|
+
ci[:type]
|
211
|
+
when /^numeric|decimal$/i
|
212
|
+
"#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
|
213
|
+
when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
|
214
|
+
ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
|
215
|
+
else
|
216
|
+
ci[:type]
|
217
|
+
end
|
218
|
+
if ci[:default_value].nil? && views.include?(table_name)
|
219
|
+
real_table_name = table_name_or_views_table_name(table_name)
|
220
|
+
real_column_name = views_real_column_name(table_name,ci[:name])
|
221
|
+
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}'"
|
222
|
+
ci[:default_value] = info_schema_query { select_value(col_default_sql) }
|
223
|
+
end
|
224
|
+
ci[:default_value] = case ci[:default_value]
|
225
|
+
when nil, '(null)', '(NULL)'
|
226
|
+
nil
|
227
|
+
when /\A\((\w+\(\))\)\Z/
|
228
|
+
ci[:default_function] = $1
|
229
|
+
nil
|
230
|
+
else
|
231
|
+
match_data = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/m)
|
232
|
+
match_data ? match_data[1] : nil
|
233
|
+
end
|
234
|
+
ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
|
235
|
+
ci
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def remove_check_constraints(table_name, column_name)
|
240
|
+
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)}'") }
|
241
|
+
constraints.each do |constraint|
|
242
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def remove_default_constraint(table_name, column_name)
|
247
|
+
# If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
|
248
|
+
select_all("EXEC sp_helpconstraint '#{quote_string(table_name)}','nomsg'").flatten.select do |row|
|
249
|
+
row['constraint_type'] == "DEFAULT on column #{column_name}"
|
250
|
+
end.each do |row|
|
251
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def remove_indexes(table_name, column_name)
|
256
|
+
indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
|
257
|
+
remove_index(table_name, {:name => index.name})
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# === SQLServer Specific (Misc Helpers) ========================= #
|
262
|
+
|
263
|
+
def info_schema_query
|
264
|
+
log_info_schema_queries ? yield : ActiveRecord::Base.silence{ yield }
|
265
|
+
end
|
266
|
+
|
267
|
+
def unqualify_table_name(table_name)
|
268
|
+
table_name.to_s.split('.').last.tr('[]','')
|
269
|
+
end
|
270
|
+
|
271
|
+
def unqualify_table_schema(table_name)
|
272
|
+
table_name.to_s.split('.')[-2].gsub(/[\[\]]/,'') rescue nil
|
273
|
+
end
|
274
|
+
|
275
|
+
def unqualify_db_name(table_name)
|
276
|
+
table_names = table_name.to_s.split('.')
|
277
|
+
table_names.length == 3 ? table_names.first.tr('[]','') : nil
|
278
|
+
end
|
279
|
+
|
280
|
+
def get_table_name(sql)
|
281
|
+
if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
282
|
+
$1 || $2
|
283
|
+
elsif sql =~ /from\s+([^\(\s]+)\s*/i
|
284
|
+
$1
|
285
|
+
else
|
286
|
+
nil
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def default_constraint_name(table_name, column_name)
|
291
|
+
"DF_#{table_name}_#{column_name}"
|
292
|
+
end
|
293
|
+
|
294
|
+
def detect_column_for!(table_name, column_name)
|
295
|
+
unless column = columns(table_name).detect { |c| c.name == column_name.to_s }
|
296
|
+
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
297
|
+
end
|
298
|
+
column
|
299
|
+
end
|
300
|
+
|
301
|
+
# === SQLServer Specific (View Reflection) ====================== #
|
302
|
+
|
303
|
+
def view_table_name(table_name)
|
304
|
+
view_info = view_information(table_name)
|
305
|
+
view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
|
306
|
+
end
|
307
|
+
|
308
|
+
def view_information(table_name)
|
309
|
+
table_name = unqualify_table_name(table_name)
|
310
|
+
@sqlserver_view_information_cache[table_name] ||= begin
|
311
|
+
view_info = info_schema_query { select_one("SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'") }
|
312
|
+
if view_info
|
313
|
+
view_info = view_info.with_indifferent_access
|
314
|
+
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
|
315
|
+
view_info[:VIEW_DEFINITION] = info_schema_query do
|
316
|
+
begin
|
317
|
+
select_values("EXEC sp_helptext #{quote_table_name(table_name)}").join
|
318
|
+
rescue
|
319
|
+
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
view_info
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def table_name_or_views_table_name(table_name)
|
329
|
+
unquoted_table_name = unqualify_table_name(table_name)
|
330
|
+
views.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
|
331
|
+
end
|
332
|
+
|
333
|
+
def views_real_column_name(table_name,column_name)
|
334
|
+
view_definition = view_information(table_name)[:VIEW_DEFINITION]
|
335
|
+
|
336
|
+
match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
|
337
|
+
match_data ? match_data[1] : column_name
|
338
|
+
end
|
339
|
+
|
340
|
+
# === SQLServer Specific (Column/View Caches) =================== #
|
341
|
+
|
342
|
+
def columns_cache_key(table_name)
|
343
|
+
table_schema = unqualify_table_schema(table_name)
|
344
|
+
table_name = unqualify_table_name(table_name)
|
345
|
+
if table_schema
|
346
|
+
"#{table_schema}.#{table_name}"
|
347
|
+
else
|
348
|
+
table_name
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def remove_sqlserver_columns_cache_for(table_name)
|
353
|
+
cache_key = unqualify_table_name(table_name)
|
354
|
+
@sqlserver_columns_cache[cache_key] = nil
|
355
|
+
initialize_sqlserver_caches(false)
|
356
|
+
end
|
357
|
+
|
358
|
+
def initialize_sqlserver_caches(reset_columns=true)
|
359
|
+
@sqlserver_columns_cache = {} if reset_columns
|
360
|
+
@sqlserver_views_cache = nil
|
361
|
+
@sqlserver_view_information_cache = {}
|
362
|
+
@sqlserver_quoted_column_and_table_names = {}
|
363
|
+
end
|
364
|
+
|
365
|
+
# === SQLServer Specific (Identity Inserts) ===================== #
|
366
|
+
|
367
|
+
def query_requires_identity_insert?(sql)
|
368
|
+
if insert_sql?(sql)
|
369
|
+
table_name = get_table_name(sql)
|
370
|
+
id_column = identity_column(table_name)
|
371
|
+
id_column && sql =~ /^\s*INSERT[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
|
372
|
+
else
|
373
|
+
false
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
def insert_sql?(sql)
|
378
|
+
!(sql =~ /^\s*INSERT/i).nil?
|
379
|
+
end
|
380
|
+
|
381
|
+
def with_identity_insert_enabled(table_name)
|
382
|
+
table_name = quote_table_name(table_name_or_views_table_name(table_name))
|
383
|
+
set_identity_insert(table_name, true)
|
384
|
+
yield
|
385
|
+
ensure
|
386
|
+
set_identity_insert(table_name, false)
|
387
|
+
end
|
388
|
+
|
389
|
+
def set_identity_insert(table_name, enable = true)
|
390
|
+
sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
391
|
+
do_execute(sql,'IDENTITY_INSERT')
|
392
|
+
rescue Exception => e
|
393
|
+
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
|
394
|
+
end
|
395
|
+
|
396
|
+
def identity_column(table_name)
|
397
|
+
columns(table_name).detect(&:is_identity?)
|
398
|
+
end
|
399
|
+
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|