activerecord-sqlserver-adapter-vailsys 3.0.20

Sign up to get free protection for your applications and to get access to all the features.
@@ -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