activerecord-sqlserver-adapter 3.1.7 → 3.2.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG 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
+