activerecord-sqlserver-adapter 3.1.7 → 3.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,20 +1,16 @@
1
1
 
2
- * 3.1.7 *
2
+ * 3.2.0 *
3
3
 
4
- * Fix exists? with offset by patching visitor. Fixes #171 and Fixes #167
4
+ * ActiveRecord explain (SHOWPLAN) support.
5
+ http://youtu.be/ckb3YYZZZ2Q
5
6
 
6
- * Set default text size to 2147483647 for TinyTDS connections. Fixes #181
7
+ * Remove our log_info_schema_queries config since we are not hooking properly into AR's 'SCHEMA' names.
7
8
 
8
- * Set @config ivar for 3rd party libs. Fixes #177
9
+ * Properly use 'SCHEMA' name arguement in DB statements to comply with ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.
9
10
 
10
- * Strengthen #query_requires_identity_insert? by looking for VALUES at the end. Fixes #178
11
+ * Make use of the new ConnectionAdapters::SchemaCache for our needs.
11
12
 
12
- * Make #sql_type_for_statement work for integers that may have empty parens or none at all. Fixes #175
13
-
14
-
15
- * 3.1.6 *
16
-
17
- * Add explicit order-by clause for windowed results. Fixes #161.
13
+ * New Sqlserver::Utils class for out helpers. Moved table name unquotes there.
18
14
 
19
15
 
20
16
  * 3.1.5 *
@@ -1,9 +1,3 @@
1
- require 'set'
2
- require 'active_record/base'
3
- require 'active_record/version'
4
- require 'active_support/concern'
5
- require 'active_support/core_ext/class/attribute'
6
-
7
1
  module ActiveRecord
8
2
  module ConnectionAdapters
9
3
  module Sqlserver
@@ -46,4 +40,3 @@ end
46
40
 
47
41
 
48
42
  ActiveRecord::Base.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ActiveRecord
49
-
@@ -0,0 +1,41 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module CoreExt
5
+ module Explain
6
+
7
+ SQLSERVER_STATEMENT_PREFIX = "EXEC sp_executesql "
8
+ SQLSERVER_PARAM_MATCHER = /@\d+ =/
9
+
10
+ def exec_explain(queries)
11
+ unprepared_queries = queries.map { |sql, bind| [unprepare_sqlserver_statement(sql), bind] }
12
+ super(unprepared_queries)
13
+ end
14
+
15
+ private
16
+
17
+ # This is somewhat hacky, but it should reliably reformat our prepared sql statment
18
+ # which uses sp_executesql to just the first argument, then unquote it. Likewise our
19
+ # do_exec_query method should substitude the @n args withe the quoted values.
20
+ def unprepare_sqlserver_statement(sql)
21
+ if sql.starts_with?(SQLSERVER_STATEMENT_PREFIX)
22
+ executesql = sql.from(SQLSERVER_STATEMENT_PREFIX.length)
23
+ executesql_args = executesql.split(', ')
24
+ executesql_args.reject! { |arg| arg =~ SQLSERVER_PARAM_MATCHER }
25
+ executesql_args.pop if executesql_args.many?
26
+ executesql = executesql_args.join(', ').strip.match(/N'(.*)'/)[1]
27
+ Utils.unquote_string(executesql)
28
+ else
29
+ sql
30
+ end
31
+ end
32
+
33
+
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ ActiveRecord::Base.extend ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::Explain
41
+ ActiveRecord::Relation.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::Explain
@@ -40,7 +40,7 @@ module ActiveRecord
40
40
  end
41
41
 
42
42
  def outside_transaction?
43
- info_schema_query { select_value("SELECT @@TRANCOUNT") == 0 }
43
+ select_value('SELECT @@TRANCOUNT', 'SCHEMA') == 0
44
44
  end
45
45
 
46
46
  def supports_statement_cache?
@@ -131,19 +131,17 @@ module ActiveRecord
131
131
 
132
132
  def user_options
133
133
  return {} if sqlserver_azure?
134
- info_schema_query do
135
- select_rows("dbcc useroptions").inject(HashWithIndifferentAccess.new) do |values,row|
136
- set_option = row[0].gsub(/\s+/,'_')
137
- user_value = row[1]
138
- values[set_option] = user_value
139
- values
140
- end
134
+ select_rows("dbcc useroptions",'SCHEMA').inject(HashWithIndifferentAccess.new) do |values,row|
135
+ set_option = row[0].gsub(/\s+/,'_')
136
+ user_value = row[1]
137
+ values[set_option] = user_value
138
+ values
141
139
  end
142
140
  end
143
141
 
144
142
  def user_options_dateformat
145
143
  if sqlserver_azure?
146
- info_schema_query { select_value "SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID" }
144
+ select_value 'SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID', 'SCHEMA'
147
145
  else
148
146
  user_options['dateformat']
149
147
  end
@@ -151,18 +149,16 @@ module ActiveRecord
151
149
 
152
150
  def user_options_isolation_level
153
151
  if sqlserver_azure?
154
- info_schema_query do
155
- sql = %|SELECT CASE [transaction_isolation_level]
156
- WHEN 0 THEN NULL
157
- WHEN 1 THEN 'READ UNCOMITTED'
158
- WHEN 2 THEN 'READ COMITTED'
159
- WHEN 3 THEN 'REPEATABLE'
160
- WHEN 4 THEN 'SERIALIZABLE'
161
- WHEN 5 THEN 'SNAPSHOT' END AS [isolation_level]
162
- FROM [sys].[dm_exec_sessions]
163
- WHERE [session_id] = @@SPID|.squish
164
- select_value(sql)
165
- end
152
+ sql = %|SELECT CASE [transaction_isolation_level]
153
+ WHEN 0 THEN NULL
154
+ WHEN 1 THEN 'READ UNCOMITTED'
155
+ WHEN 2 THEN 'READ COMITTED'
156
+ WHEN 3 THEN 'REPEATABLE'
157
+ WHEN 4 THEN 'SERIALIZABLE'
158
+ WHEN 5 THEN 'SNAPSHOT' END AS [isolation_level]
159
+ FROM [sys].[dm_exec_sessions]
160
+ WHERE [session_id] = @@SPID|.squish
161
+ select_value sql, 'SCHEMA'
166
162
  else
167
163
  user_options['isolation_level']
168
164
  end
@@ -170,7 +166,7 @@ module ActiveRecord
170
166
 
171
167
  def user_options_language
172
168
  if sqlserver_azure?
173
- info_schema_query { select_value "SELECT @@LANGUAGE AS [language]" }
169
+ select_value 'SELECT @@LANGUAGE AS [language]', 'SCHEMA'
174
170
  else
175
171
  user_options['language']
176
172
  end
@@ -314,15 +310,14 @@ module ActiveRecord
314
310
 
315
311
  # === SQLServer Specific (Executing) ============================ #
316
312
 
317
- def do_execute(sql, name = nil)
318
- name ||= 'EXECUTE'
313
+ def do_execute(sql, name = 'SQL')
319
314
  log(sql, name) do
320
315
  with_sqlserver_error_handling { raw_connection_do(sql) }
321
316
  end
322
317
  end
323
318
 
324
319
  def do_exec_query(sql, name, binds)
325
- statement = quote(sql)
320
+ explaining = name == 'EXPLAIN'
326
321
  names_and_types = []
327
322
  params = []
328
323
  binds.each_with_index do |(column,value),index|
@@ -341,10 +336,17 @@ module ActiveRecord
341
336
  raise "Unknown bind columns. We can account for this."
342
337
  end
343
338
  quoted_value = ar_column ? quote(v,column) : quote(v,nil)
344
- params << "@#{index} = #{quoted_value}"
339
+ params << (explaining ? quoted_value : "@#{index} = #{quoted_value}")
340
+ end
341
+ if explaining
342
+ params.each_with_index do |param, index|
343
+ substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
344
+ sql.sub! substitute_at_finder, param
345
+ end
346
+ else
347
+ sql = "EXEC sp_executesql #{quote(sql)}"
348
+ sql << ", #{quote(names_and_types.join(', '))}, #{params.join(', ')}" unless binds.empty?
345
349
  end
346
- sql = "EXEC sp_executesql #{statement}"
347
- sql << ", #{quote(names_and_types.join(', '))}, #{params.join(', ')}" unless binds.empty?
348
350
  raw_select sql, name, binds, :ar_result => true
349
351
  end
350
352
 
@@ -361,7 +363,7 @@ module ActiveRecord
361
363
 
362
364
  # === SQLServer Specific (Selecting) ============================ #
363
365
 
364
- def raw_select(sql, name=nil, binds=[], options={})
366
+ def raw_select(sql, name='SQL', binds=[], options={})
365
367
  log(sql,name,binds) { _raw_select(sql, options) }
366
368
  end
367
369
 
@@ -42,8 +42,7 @@ module ActiveRecord
42
42
  end
43
43
 
44
44
  def quote_column_name(name)
45
- @sqlserver_quoted_column_and_table_names[name] ||=
46
- name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n.to_s.gsub(']', ']]')}]" }.join('.')
45
+ schema_cache.quote_name(name)
47
46
  end
48
47
 
49
48
  def quote_table_name(name)
@@ -0,0 +1,85 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ class SchemaCache < ActiveRecord::ConnectionAdapters::SchemaCache
5
+
6
+ attr_reader :view_information
7
+
8
+ def initialize(conn)
9
+ super
10
+ @table_names = nil
11
+ @view_names = nil
12
+ @view_information = {}
13
+ @quoted_names = {}
14
+ end
15
+
16
+ # Superclass Overrides
17
+
18
+ def table_exists?(table_name)
19
+ return false if table_name.blank?
20
+ key = table_name_key(table_name)
21
+ return @tables[key] if @tables.key? key
22
+ @tables[key] = connection.table_exists?(table_name)
23
+ end
24
+
25
+ def clear!
26
+ super
27
+ @table_names = nil
28
+ @view_names = nil
29
+ @view_information.clear
30
+ @quoted_names.clear
31
+ end
32
+
33
+ def clear_table_cache!(table_name)
34
+ key = table_name_key(table_name)
35
+ super(key)
36
+ super(table_name)
37
+ # SQL Server Specific
38
+ if @table_names
39
+ @table_names.delete key
40
+ @table_names.delete table_name
41
+ end
42
+ if @view_names
43
+ @view_names.delete key
44
+ @view_names.delete table_name
45
+ end
46
+ @view_information.delete key
47
+ end
48
+
49
+ # SQL Server Specific
50
+
51
+ def table_names
52
+ @table_names ||= connection.tables
53
+ end
54
+
55
+ def view_names
56
+ @view_names ||= connection.views
57
+ end
58
+
59
+ def view_exists?(table_name)
60
+ table_exists?(table_name)
61
+ end
62
+
63
+ def view_information(table_name)
64
+ key = table_name_key(table_name)
65
+ return @view_information[key] if @view_information.key? key
66
+ @view_information[key] = connection.send(:view_information, table_name)
67
+ end
68
+
69
+ def quote_name(name)
70
+ return @quoted_names[name] if @quoted_names.key? name
71
+ @quoted_names[name] = name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n.to_s.gsub(']', ']]')}]" }.join('.')
72
+ end
73
+
74
+
75
+ private
76
+
77
+ def table_name_key(table_name)
78
+ Utils.unqualify_table_name(table_name)
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end
85
+
@@ -7,15 +7,13 @@ module ActiveRecord
7
7
  @native_database_types ||= initialize_native_database_types.freeze
8
8
  end
9
9
 
10
- def tables(name = nil, table_type = 'BASE TABLE')
11
- info_schema_query do
12
- select_values "SELECT #{lowercase_schema_reflection_sql('TABLE_NAME')} FROM INFORMATION_SCHEMA.TABLES #{"WHERE TABLE_TYPE = '#{table_type}'" if table_type} ORDER BY TABLE_NAME"
13
- end
10
+ def tables(table_type = 'BASE TABLE')
11
+ select_values "SELECT #{lowercase_schema_reflection_sql('TABLE_NAME')} FROM INFORMATION_SCHEMA.TABLES #{"WHERE TABLE_TYPE = '#{table_type}'" if table_type} ORDER BY TABLE_NAME", 'SCHEMA'
14
12
  end
15
13
 
16
14
  def table_exists?(table_name)
17
15
  return false if table_name.blank?
18
- unquoted_table_name = unqualify_table_name(table_name)
16
+ unquoted_table_name = Utils.unqualify_table_name(table_name)
19
17
  super || tables.include?(unquoted_table_name) || views.include?(unquoted_table_name)
20
18
  end
21
19
 
@@ -40,31 +38,16 @@ module ActiveRecord
40
38
 
41
39
  def columns(table_name, name = nil)
42
40
  return [] if table_name.blank?
43
- @sqlserver_columns_cache[table_name] ||= column_definitions(table_name).collect do |ci|
41
+ column_definitions(table_name).collect do |ci|
44
42
  sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
45
43
  SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
46
44
  end
47
45
  end
48
-
49
- def create_table(table_name, options = {})
50
- super
51
- clear_cache!
52
- end
53
46
 
54
47
  def rename_table(table_name, new_name)
55
48
  do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
56
49
  end
57
50
 
58
- def drop_table(table_name, options = {})
59
- super
60
- clear_cache!
61
- end
62
-
63
- def add_column(table_name, column_name, type, options = {})
64
- super
65
- clear_cache!
66
- end
67
-
68
51
  def remove_column(table_name, *column_names)
69
52
  raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
70
53
  column_names.flatten.each do |column_name|
@@ -73,12 +56,11 @@ module ActiveRecord
73
56
  remove_indexes(table_name, column_name)
74
57
  do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
75
58
  end
76
- clear_cache!
77
59
  end
78
60
 
79
61
  def change_column(table_name, column_name, type, options = {})
80
62
  sql_commands = []
81
- column_object = columns(table_name).detect { |c| c.name.to_s == column_name.to_s }
63
+ column_object = schema_cache.columns[table_name].detect { |c| c.name.to_s == column_name.to_s }
82
64
  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
65
  change_column_sql << " NOT NULL" if options[:null] == false
84
66
  sql_commands << change_column_sql
@@ -89,19 +71,16 @@ module ActiveRecord
89
71
  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)}"
90
72
  end
91
73
  sql_commands.each { |c| do_execute(c) }
92
- clear_cache!
93
74
  end
94
75
 
95
76
  def change_column_default(table_name, column_name, default)
96
77
  remove_default_constraint(table_name, column_name)
97
78
  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)}"
98
- clear_cache!
99
79
  end
100
80
 
101
81
  def rename_column(table_name, column_name, new_column_name)
102
- detect_column_for!(table_name,column_name)
82
+ detect_column_for! table_name, column_name
103
83
  do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
104
- clear_cache!
105
84
  end
106
85
 
107
86
  def remove_index!(table_name, index_name)
@@ -125,7 +104,7 @@ module ActiveRecord
125
104
  end
126
105
 
127
106
  def change_column_null(table_name, column_name, null, default = nil)
128
- column = detect_column_for!(table_name,column_name)
107
+ column = detect_column_for! table_name, column_name
129
108
  unless null || default.nil?
130
109
  do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
131
110
  end
@@ -136,8 +115,8 @@ module ActiveRecord
136
115
 
137
116
  # === SQLServer Specific ======================================== #
138
117
 
139
- def views(name = nil)
140
- @sqlserver_views_cache ||= tables(name,'VIEW')
118
+ def views
119
+ tables('VIEW')
141
120
  end
142
121
 
143
122
 
@@ -171,10 +150,10 @@ module ActiveRecord
171
150
  end
172
151
 
173
152
  def column_definitions(table_name)
174
- db_name = unqualify_db_name(table_name)
153
+ db_name = Utils.unqualify_db_name(table_name)
175
154
  db_name_with_period = "#{db_name}." if db_name
176
- table_schema = unqualify_table_schema(table_name)
177
- table_name = unqualify_table_name(table_name)
155
+ table_schema = Utils.unqualify_table_schema(table_name)
156
+ table_name = Utils.unqualify_table_name(table_name)
178
157
  sql = %{
179
158
  SELECT DISTINCT
180
159
  #{lowercase_schema_reflection_sql('columns.TABLE_NAME')} AS table_name,
@@ -215,7 +194,7 @@ module ActiveRecord
215
194
  }.gsub(/[ \t\r\n]+/,' ')
216
195
  binds = [['table_name', table_name]]
217
196
  binds << ['table_schema',table_schema] unless table_schema.blank?
218
- results = info_schema_query { do_exec_query(sql, 'InfoSchema::ColumnDefinitions', binds) }
197
+ results = do_exec_query(sql, 'SCHEMA', binds)
219
198
  results.collect do |ci|
220
199
  ci = ci.symbolize_keys
221
200
  ci[:type] = case ci[:type]
@@ -230,11 +209,11 @@ module ActiveRecord
230
209
  else
231
210
  ci[:type]
232
211
  end
233
- if ci[:default_value].nil? && views.include?(table_name)
212
+ if ci[:default_value].nil? && schema_cache.view_names.include?(table_name)
234
213
  real_table_name = table_name_or_views_table_name(table_name)
235
214
  real_column_name = views_real_column_name(table_name,ci[:name])
236
215
  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}'"
237
- ci[:default_value] = info_schema_query { select_value(col_default_sql) }
216
+ ci[:default_value] = select_value col_default_sql, 'SCHEMA'
238
217
  end
239
218
  ci[:default_value] = case ci[:default_value]
240
219
  when nil, '(null)', '(NULL)'
@@ -254,7 +233,7 @@ module ActiveRecord
254
233
  end
255
234
 
256
235
  def remove_check_constraints(table_name, column_name)
257
- 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)}'") }
236
+ constraints = 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)}'", 'SCHEMA'
258
237
  constraints.each do |constraint|
259
238
  do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
260
239
  end
@@ -277,23 +256,6 @@ module ActiveRecord
277
256
 
278
257
  # === SQLServer Specific (Misc Helpers) ========================= #
279
258
 
280
- def info_schema_query
281
- log_info_schema_queries ? yield : ActiveRecord::Base.silence{ yield }
282
- end
283
-
284
- def unqualify_table_name(table_name)
285
- table_name.to_s.split('.').last.tr('[]','')
286
- end
287
-
288
- def unqualify_table_schema(table_name)
289
- table_name.to_s.split('.')[-2].gsub(/[\[\]]/,'') rescue nil
290
- end
291
-
292
- def unqualify_db_name(table_name)
293
- table_names = table_name.to_s.split('.')
294
- table_names.length == 3 ? table_names.first.tr('[]','') : nil
295
- end
296
-
297
259
  def get_table_name(sql)
298
260
  if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)\s+INTO\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
299
261
  $2 || $3
@@ -309,7 +271,7 @@ module ActiveRecord
309
271
  end
310
272
 
311
273
  def detect_column_for!(table_name, column_name)
312
- unless column = columns(table_name).detect { |c| c.name == column_name.to_s }
274
+ unless column = schema_cache.columns[table_name].detect { |c| c.name == column_name.to_s }
313
275
  raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
314
276
  end
315
277
  column
@@ -322,57 +284,45 @@ module ActiveRecord
322
284
  # === SQLServer Specific (View Reflection) ====================== #
323
285
 
324
286
  def view_table_name(table_name)
325
- view_info = view_information(table_name)
287
+ view_info = schema_cache.view_information(table_name)
326
288
  view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
327
289
  end
328
290
 
329
291
  def view_information(table_name)
330
- table_name = unqualify_table_name(table_name)
331
- @sqlserver_view_information_cache[table_name] ||= begin
332
- view_info = info_schema_query { select_one("SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'") }
333
- if view_info
334
- view_info = view_info.with_indifferent_access
335
- if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
336
- view_info[:VIEW_DEFINITION] = info_schema_query do
337
- begin
338
- select_values("EXEC sp_helptext #{quote_table_name(table_name)}").join
339
- rescue
340
- warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
341
- end
342
- end
343
- end
292
+ table_name = Utils.unqualify_table_name(table_name)
293
+ view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'", 'SCHEMA'
294
+ if view_info
295
+ view_info = view_info.with_indifferent_access
296
+ if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
297
+ view_info[:VIEW_DEFINITION] = begin
298
+ select_values("EXEC sp_helptext #{quote_table_name(table_name)}", 'SCHEMA').join
299
+ rescue
300
+ warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
301
+ nil
302
+ end
344
303
  end
345
- view_info
346
304
  end
305
+ view_info
347
306
  end
348
307
 
349
308
  def table_name_or_views_table_name(table_name)
350
- unquoted_table_name = unqualify_table_name(table_name)
351
- views.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
309
+ unquoted_table_name = Utils.unqualify_table_name(table_name)
310
+ schema_cache.view_names.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
352
311
  end
353
312
 
354
313
  def views_real_column_name(table_name,column_name)
355
- view_definition = view_information(table_name)[:VIEW_DEFINITION]
314
+ view_definition = schema_cache.view_information(table_name)[:VIEW_DEFINITION]
356
315
  match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
357
316
  match_data ? match_data[1] : column_name
358
317
  end
359
318
 
360
- # === SQLServer Specific (Column/View Caches) =================== #
361
-
362
- def initialize_sqlserver_caches
363
- @sqlserver_columns_cache = {}
364
- @sqlserver_views_cache = nil
365
- @sqlserver_view_information_cache = {}
366
- @sqlserver_quoted_column_and_table_names = {}
367
- end
368
-
369
319
  # === SQLServer Specific (Identity Inserts) ===================== #
370
320
 
371
321
  def query_requires_identity_insert?(sql)
372
322
  if insert_sql?(sql)
373
323
  table_name = get_table_name(sql)
374
324
  id_column = identity_column(table_name)
375
- id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\).*?VALUES/i ? quote_table_name(table_name) : false
325
+ id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
376
326
  else
377
327
  false
378
328
  end
@@ -392,13 +342,13 @@ module ActiveRecord
392
342
 
393
343
  def set_identity_insert(table_name, enable = true)
394
344
  sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
395
- do_execute sql,'InfoSchema::SetIdentityInsert'
345
+ do_execute sql, 'SCHEMA'
396
346
  rescue Exception => e
397
347
  raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
398
348
  end
399
349
 
400
350
  def identity_column(table_name)
401
- columns(table_name).detect(&:is_identity?)
351
+ schema_cache.columns[table_name].detect(&:is_identity?)
402
352
  end
403
353
 
404
354
  end
@@ -0,0 +1,67 @@
1
+ require 'active_record/connection_adapters/sqlserver/showplan/printer_table'
2
+ require 'active_record/connection_adapters/sqlserver/showplan/printer_xml'
3
+
4
+ module ActiveRecord
5
+ module ConnectionAdapters
6
+ module Sqlserver
7
+ module Showplan
8
+
9
+ OPTION_ALL = 'SHOWPLAN_ALL'
10
+ OPTION_TEXT = 'SHOWPLAN_TEXT'
11
+ OPTION_XML = 'SHOWPLAN_XML'
12
+ OPTIONS = [OPTION_ALL, OPTION_TEXT, OPTION_XML]
13
+
14
+ def explain(arel, binds = [])
15
+ sql = to_sql(arel)
16
+ result = with_showplan_on { do_exec_query(sql, 'EXPLAIN', binds) }
17
+ printer = showplan_printer.new(result)
18
+ printer.pp
19
+ end
20
+
21
+
22
+ protected
23
+
24
+ def with_showplan_on
25
+ set_showplan_option(true)
26
+ yield
27
+ ensure
28
+ set_showplan_option(false)
29
+ end
30
+
31
+ def set_showplan_option(enable = true)
32
+ sql = "SET #{option} #{enable ? 'ON' : 'OFF'}"
33
+ raw_connection_do(sql)
34
+ rescue Exception => e
35
+ raise ActiveRecordError, "#{option} could not be turned #{enable ? 'ON' : 'OFF'}, perhaps you do not have SHOWPLAN permissions?"
36
+ end
37
+
38
+ def option
39
+ (SQLServerAdapter.showplan_option || OPTION_ALL).tap do |opt|
40
+ raise(ArgumentError, "Unknown SHOWPLAN option #{opt.inspect} found.") if OPTIONS.exclude?(opt)
41
+ end
42
+ end
43
+
44
+ def showplan_all?
45
+ option == OPTION_ALL
46
+ end
47
+
48
+ def showplan_text?
49
+ option == OPTION_TEXT
50
+ end
51
+
52
+ def showplan_xml?
53
+ option == OPTION_XML
54
+ end
55
+
56
+ def showplan_printer
57
+ case option
58
+ when OPTION_XML then PrinterXml
59
+ when OPTION_ALL, OPTION_TEXT then PrinterTable
60
+ else PrinterTable
61
+ end
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,69 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module Showplan
5
+ class PrinterTable
6
+
7
+ cattr_accessor :max_column_width, :cell_padding
8
+ self.max_column_width = 50
9
+ self.cell_padding = 1
10
+
11
+ attr_reader :result
12
+
13
+ def initialize(result)
14
+ @result = result
15
+ end
16
+
17
+ def pp
18
+ @widths = compute_column_widths
19
+ @separator = build_separator
20
+ pp = []
21
+ pp << @separator
22
+ pp << build_cells(result.columns)
23
+ pp << @separator
24
+ result.rows.each do |row|
25
+ pp << build_cells(row)
26
+ end
27
+ pp << @separator
28
+ pp.join("\n") + "\n"
29
+ end
30
+
31
+ private
32
+
33
+ def compute_column_widths
34
+ [].tap do |computed_widths|
35
+ result.columns.each_with_index do |column, i|
36
+ cells_in_column = [column] + result.rows.map { |r| cast_item(r[i]) }
37
+ computed_width = cells_in_column.map(&:length).max
38
+ final_width = computed_width > max_column_width ? max_column_width : computed_width
39
+ computed_widths << final_width
40
+ end
41
+ end
42
+ end
43
+
44
+ def build_separator
45
+ '+' + @widths.map {|w| '-' * (w + (cell_padding*2))}.join('+') + '+'
46
+ end
47
+
48
+ def build_cells(items)
49
+ cells = []
50
+ items.each_with_index do |item, i|
51
+ cells << cast_item(item).ljust(@widths[i])
52
+ end
53
+ "| #{cells.join(' | ')} |"
54
+ end
55
+
56
+ def cast_item(item)
57
+ case item
58
+ when NilClass then 'NULL'
59
+ when Float then item.to_s.to(9)
60
+ else item.to_s.truncate(max_column_width)
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module Showplan
5
+ class PrinterXml
6
+
7
+ def initialize(result)
8
+ @result = result
9
+ end
10
+
11
+ def pp
12
+ xml = @result.rows.first.first
13
+ if defined?(Nokogiri)
14
+ Nokogiri::XML(xml).to_xml :indent => 2, :encoding => 'UTF-8'
15
+ else
16
+ xml
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ class Utils
5
+
6
+ class << self
7
+
8
+ def unquote_string(string)
9
+ string.to_s.gsub(/\'\'/, "'")
10
+ end
11
+
12
+ def unqualify_table_name(table_name)
13
+ table_name.to_s.split('.').last.tr('[]','')
14
+ end
15
+
16
+ def unqualify_table_schema(table_name)
17
+ table_name.to_s.split('.')[-2].gsub(/[\[\]]/,'') rescue nil
18
+ end
19
+
20
+ def unqualify_db_name(table_name)
21
+ table_names = table_name.to_s.split('.')
22
+ table_names.length == 3 ? table_names.first.tr('[]','') : nil
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+
@@ -3,7 +3,7 @@ module ActiveRecord
3
3
  module Sqlserver
4
4
  module Version
5
5
 
6
- VERSION = '3.1.7'
6
+ VERSION = '3.2.0.rc1'
7
7
 
8
8
  end
9
9
  end
@@ -1,17 +1,22 @@
1
+ require 'base64'
1
2
  require 'arel/visitors/sqlserver'
2
3
  require 'active_record'
4
+ require 'active_record/base'
5
+ require 'active_support/concern'
6
+ require 'active_support/core_ext/string'
3
7
  require 'active_record/connection_adapters/abstract_adapter'
4
8
  require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
5
9
  require 'active_record/connection_adapters/sqlserver/core_ext/database_statements'
10
+ require 'active_record/connection_adapters/sqlserver/core_ext/explain'
6
11
  require 'active_record/connection_adapters/sqlserver/database_limits'
7
12
  require 'active_record/connection_adapters/sqlserver/database_statements'
8
13
  require 'active_record/connection_adapters/sqlserver/errors'
14
+ require 'active_record/connection_adapters/sqlserver/schema_cache'
9
15
  require 'active_record/connection_adapters/sqlserver/schema_statements'
16
+ require 'active_record/connection_adapters/sqlserver/showplan'
10
17
  require 'active_record/connection_adapters/sqlserver/quoting'
18
+ require 'active_record/connection_adapters/sqlserver/utils'
11
19
  require 'active_record/connection_adapters/sqlserver/version'
12
- require 'active_support/core_ext/kernel/requires'
13
- require 'active_support/core_ext/string'
14
- require 'base64'
15
20
 
16
21
  module ActiveRecord
17
22
 
@@ -32,7 +37,7 @@ module ActiveRecord
32
37
  else
33
38
  raise ArgumentError, "Unknown connection mode in #{config.inspect}."
34
39
  end
35
- ConnectionAdapters::SQLServerAdapter.new(logger,config.merge(:mode=>mode))
40
+ ConnectionAdapters::SQLServerAdapter.new(nil, logger, nil, config.merge(:mode=>mode))
36
41
  end
37
42
 
38
43
  protected
@@ -91,7 +96,7 @@ module ActiveRecord
91
96
 
92
97
  def sql_type_for_statement
93
98
  if is_integer? || is_real?
94
- sql_type.sub(/\((\d+)?\)/,'')
99
+ sql_type.sub(/\(\d+\)/,'')
95
100
  else
96
101
  sql_type
97
102
  end
@@ -168,6 +173,7 @@ module ActiveRecord
168
173
 
169
174
  include Sqlserver::Quoting
170
175
  include Sqlserver::DatabaseStatements
176
+ include Sqlserver::Showplan
171
177
  include Sqlserver::SchemaStatements
172
178
  include Sqlserver::DatabaseLimits
173
179
  include Sqlserver::Errors
@@ -180,25 +186,22 @@ module ActiveRecord
180
186
  attr_reader :database_version, :database_year, :spid, :product_level, :product_version, :edition
181
187
 
182
188
  cattr_accessor :native_text_database_type, :native_binary_database_type, :native_string_database_type,
183
- :log_info_schema_queries, :enable_default_unicode_types, :auto_connect, :retry_deadlock_victim,
184
- :cs_equality_operator, :lowercase_schema_reflection, :auto_connect_duration
189
+ :enable_default_unicode_types, :auto_connect, :retry_deadlock_victim,
190
+ :cs_equality_operator, :lowercase_schema_reflection, :auto_connect_duration,
191
+ :showplan_option
185
192
 
186
193
  self.enable_default_unicode_types = true
187
194
 
188
- class << self
189
-
190
- def visitor_for(pool)
191
- Arel::Visitors::SQLServer.new(pool)
192
- end
193
-
194
- end
195
195
 
196
- def initialize(logger,config)
196
+ def initialize(connection, logger, pool, config)
197
+ super(connection, logger, pool)
198
+ # AbstractAdapter Responsibility
199
+ @schema_cache = Sqlserver::SchemaCache.new self
200
+ @visitor = Arel::Visitors::SQLServer.new self
201
+ # Our Responsibility
197
202
  @connection_options = config
198
203
  connect
199
- super(@connection, logger)
200
- @config = config
201
- @database_version = info_schema_query { select_value('SELECT @@version') }
204
+ @database_version = select_value 'SELECT @@version', 'SCHEMA'
202
205
  @database_year = begin
203
206
  if @database_version =~ /Microsoft SQL Azure/i
204
207
  @sqlserver_azure = true
@@ -210,11 +213,10 @@ module ActiveRecord
210
213
  rescue
211
214
  0
212
215
  end
213
- @product_level = info_schema_query { select_value("SELECT CAST(SERVERPROPERTY('productlevel') AS VARCHAR(128))") }
214
- @product_version = info_schema_query { select_value("SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(128))") }
215
- @edition = info_schema_query { select_value("SELECT CAST(SERVERPROPERTY('edition') AS VARCHAR(128))") }
216
+ @product_level = select_value "SELECT CAST(SERVERPROPERTY('productlevel') AS VARCHAR(128))", 'SCHEMA'
217
+ @product_version = select_value "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(128))", 'SCHEMA'
218
+ @edition = select_value "SELECT CAST(SERVERPROPERTY('edition') AS VARCHAR(128))", 'SCHEMA'
216
219
  initialize_dateformatter
217
- initialize_sqlserver_caches
218
220
  use_database
219
221
  unless SUPPORTED_VERSIONS.include?(@database_year)
220
222
  raise NotImplementedError, "Currently, only #{SUPPORTED_VERSIONS.to_sentence} are supported. We got back #{@database_version}."
@@ -243,10 +245,22 @@ module ActiveRecord
243
245
  true
244
246
  end
245
247
 
248
+ def supports_bulk_alter?
249
+ false
250
+ end
251
+
246
252
  def supports_savepoints?
247
253
  true
248
254
  end
249
255
 
256
+ def supports_index_sort_order?
257
+ true
258
+ end
259
+
260
+ def supports_explain?
261
+ true
262
+ end
263
+
250
264
  def disable_referential_integrity
251
265
  do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'"
252
266
  yield
@@ -287,10 +301,6 @@ module ActiveRecord
287
301
  remove_database_connections_and_rollback { }
288
302
  end
289
303
 
290
- def clear_cache!
291
- initialize_sqlserver_caches
292
- end
293
-
294
304
  # === Abstract Adapter (Misc Support) =========================== #
295
305
 
296
306
  def pk_and_sequence_for(table_name)
@@ -299,7 +309,7 @@ module ActiveRecord
299
309
  end
300
310
 
301
311
  def primary_key(table_name)
302
- identity_column(table_name).try(:name) || columns(table_name).detect(&:is_primary?).try(:name)
312
+ identity_column(table_name).try(:name) || schema_cache.columns[table_name].detect(&:is_primary?).try(:name)
303
313
  end
304
314
 
305
315
  # === SQLServer Specific (DB Reflection) ======================== #
@@ -392,7 +402,7 @@ module ActiveRecord
392
402
 
393
403
  def connect
394
404
  config = @connection_options
395
- @connection = case @connection_options[:mode]
405
+ @connection = case config[:mode]
396
406
  when :dblib
397
407
  appname = config[:appname] || configure_application_name || Rails.application.class.name.split('::').first rescue nil
398
408
  login_timeout = config[:login_timeout].present? ? config[:login_timeout].to_i : nil
@@ -424,7 +434,6 @@ module ActiveRecord
424
434
  client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
425
435
  client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
426
436
  end
427
- client.execute("SET TEXTSIZE 2147483647").do
428
437
  end
429
438
  when :odbc
430
439
  if config[:dsn].include?(';')
@@ -444,7 +453,7 @@ module ActiveRecord
444
453
  end
445
454
  end
446
455
  end
447
- @spid = _raw_select("SELECT @@SPID", :fetch => :rows).first.first
456
+ @spid = _raw_select("SELECT @@SPID", :fetch => :rows).first.first
448
457
  configure_connection
449
458
  rescue
450
459
  raise unless @auto_connecting
@@ -154,8 +154,9 @@ module Arel
154
154
  projections = projections.map { |x| projection_without_expression(x) }
155
155
  end
156
156
  [ ("SELECT" if !windowed),
157
+ (visit(core.set_quantifier) if core.set_quantifier),
157
158
  (visit(o.limit) if o.limit && !windowed),
158
- (projections.map{ |x| v = visit(x); v == "1" ? "1 AS [__wrp]" : v }.join(', ')),
159
+ (projections.map{ |x| visit(x) }.join(', ')),
159
160
  (source_with_lock_for_select_statement(o)),
160
161
  ("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
161
162
  ("GROUP BY #{groups.map { |x| visit x }.join ', ' }" unless groups.empty?),
@@ -165,7 +166,6 @@ module Arel
165
166
  end
166
167
 
167
168
  def visit_Arel_Nodes_SelectStatementWithOffset(o)
168
- o.limit ||= Arel::Nodes::Limit.new(9223372036854775807)
169
169
  orders = rowtable_orders(o)
170
170
  [ "SELECT",
171
171
  (visit(o.limit) if o.limit && !windowed_single_distinct_select_statement?(o)),
@@ -175,7 +175,6 @@ module Arel
175
175
  visit_Arel_Nodes_SelectStatementWithOutOffset(o,true),
176
176
  ") AS [__rnt]",
177
177
  (visit(o.offset) if o.offset),
178
- "ORDER BY [__rnt].[__rn] ASC"
179
178
  ].compact.join ' '
180
179
  end
181
180
 
metadata CHANGED
@@ -1,10 +1,17 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: activerecord-sqlserver-adapter
3
- version: !ruby/object:Gem::Version
4
- version: 3.1.7
5
- prerelease:
3
+ version: !ruby/object:Gem::Version
4
+ hash: 15424087
5
+ prerelease: 6
6
+ segments:
7
+ - 3
8
+ - 2
9
+ - 0
10
+ - rc
11
+ - 1
12
+ version: 3.2.0.rc1
6
13
  platform: ruby
7
- authors:
14
+ authors:
8
15
  - Ken Collins
9
16
  - Murray Steele
10
17
  - Shawn Balestracci
@@ -13,61 +20,90 @@ authors:
13
20
  autorequire:
14
21
  bindir: bin
15
22
  cert_chain: []
16
- date: 2012-04-13 00:00:00.000000000 Z
17
- dependencies:
18
- - !ruby/object:Gem::Dependency
23
+
24
+ date: 2012-01-02 00:00:00 Z
25
+ dependencies:
26
+ - !ruby/object:Gem::Dependency
19
27
  name: activerecord
20
- requirement: &70306769696760 !ruby/object:Gem::Requirement
28
+ prerelease: false
29
+ requirement: &id001 !ruby/object:Gem::Requirement
21
30
  none: false
22
- requirements:
31
+ requirements:
23
32
  - - ~>
24
- - !ruby/object:Gem::Version
25
- version: 3.1.0
33
+ - !ruby/object:Gem::Version
34
+ hash: 15424087
35
+ segments:
36
+ - 3
37
+ - 2
38
+ - 0
39
+ - rc
40
+ - 1
41
+ version: 3.2.0.rc1
26
42
  type: :runtime
27
- prerelease: false
28
- version_requirements: *70306769696760
43
+ version_requirements: *id001
29
44
  description: SQL Server 2005 and 2008 Adapter For ActiveRecord
30
45
  email: ken@metaskills.net
31
46
  executables: []
47
+
32
48
  extensions: []
49
+
33
50
  extra_rdoc_files: []
34
- files:
51
+
52
+ files:
35
53
  - CHANGELOG
36
54
  - MIT-LICENSE
37
55
  - lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb
38
56
  - lib/active_record/connection_adapters/sqlserver/core_ext/database_statements.rb
57
+ - lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb
39
58
  - lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb
40
59
  - lib/active_record/connection_adapters/sqlserver/database_limits.rb
41
60
  - lib/active_record/connection_adapters/sqlserver/database_statements.rb
42
61
  - lib/active_record/connection_adapters/sqlserver/errors.rb
43
62
  - lib/active_record/connection_adapters/sqlserver/quoting.rb
63
+ - lib/active_record/connection_adapters/sqlserver/schema_cache.rb
44
64
  - lib/active_record/connection_adapters/sqlserver/schema_statements.rb
65
+ - lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb
66
+ - lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb
67
+ - lib/active_record/connection_adapters/sqlserver/showplan.rb
68
+ - lib/active_record/connection_adapters/sqlserver/utils.rb
45
69
  - lib/active_record/connection_adapters/sqlserver/version.rb
46
70
  - lib/active_record/connection_adapters/sqlserver_adapter.rb
47
71
  - lib/activerecord-sqlserver-adapter.rb
48
72
  - lib/arel/visitors/sqlserver.rb
49
73
  homepage: http://github.com/rails-sqlserver/activerecord-sqlserver-adapter
50
74
  licenses: []
75
+
51
76
  post_install_message:
52
77
  rdoc_options: []
53
- require_paths:
78
+
79
+ require_paths:
54
80
  - lib
55
- required_ruby_version: !ruby/object:Gem::Requirement
81
+ required_ruby_version: !ruby/object:Gem::Requirement
56
82
  none: false
57
- requirements:
58
- - - ! '>='
59
- - !ruby/object:Gem::Version
60
- version: '0'
61
- required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 3
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
91
  none: false
63
- requirements:
64
- - - ! '>='
65
- - !ruby/object:Gem::Version
66
- version: '0'
92
+ requirements:
93
+ - - ">"
94
+ - !ruby/object:Gem::Version
95
+ hash: 25
96
+ segments:
97
+ - 1
98
+ - 3
99
+ - 1
100
+ version: 1.3.1
67
101
  requirements: []
102
+
68
103
  rubyforge_project: activerecord-sqlserver-adapter
69
- rubygems_version: 1.8.17
104
+ rubygems_version: 1.8.12
70
105
  signing_key:
71
106
  specification_version: 3
72
107
  summary: SQL Server 2005 and 2008 Adapter For ActiveRecord.
73
108
  test_files: []
109
+