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.
@@ -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,17 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module QueryCache
5
+
6
+ def select_one(*args)
7
+ if @query_cache_enabled
8
+ cache_sql(args.first) { super }
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ 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