activerecord-sqlserver-adapter-vailsys 3.0.20

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