activerecord-sqlserver-adapter 3.0.19 → 3.1.0.beta1

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,32 +1,7 @@
1
1
 
2
- * 3.0.19 *
2
+ * 3.1.0.beta1 *
3
3
 
4
- * Create a #configure_connection method that can be overridden. Think "SET TEXTSIZE...".
5
-
6
- * Create a #configure_application_name method that can be overridden for unique TinyTDS app names
7
- to show up in SQL Server's activity monitor.
8
-
9
- * Fixed the #finish_statement_handle to cancel the TinyTDS connection if needed.
10
-
11
- * Fix the #indexes method to fail gracefully if sp_helptext permissions fail.
12
-
13
-
14
-
15
- * 3.0.18 *
16
-
17
- * Make #rollback_db_transaction smarter.
18
-
19
-
20
- * 3.0.17 *
21
-
22
- * Allow :limit/:offset to be used with fully qualified table and column in :select.
23
-
24
- * Fix test_count_explicit_columns.
25
-
26
-
27
- * 3.0.16 *
28
-
29
- * Allow complex order objects to not be molested by our visitor overrides. Fixes #99
4
+ It just works! Uses "EXEC sp_executesql ..." for just about everything now!
30
5
 
31
6
 
32
7
  * 3.0.15 *
@@ -1,21 +1,25 @@
1
+ require 'set'
2
+ require 'active_record/base'
1
3
  require 'active_record/version'
2
- require 'active_support/core_ext/class/inheritable_attributes'
4
+ require 'active_support/concern'
5
+ require 'active_support/core_ext/class/attribute'
3
6
 
4
7
  module ActiveRecord
5
8
  module ConnectionAdapters
6
9
  module Sqlserver
7
10
  module CoreExt
8
11
  module ActiveRecord
9
-
10
- def self.included(klass)
11
- klass.extend ClassMethods
12
- class << klass
13
- alias_method_chain :reset_column_information, :sqlserver_cache_support
14
- end
12
+
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ class_attribute :coerced_sqlserver_date_columns, :coerced_sqlserver_time_columns
17
+ self.coerced_sqlserver_date_columns = Set.new
18
+ self.coerced_sqlserver_time_columns = Set.new
15
19
  end
16
20
 
17
21
  module ClassMethods
18
-
22
+
19
23
  def execute_procedure(proc_name, *variables)
20
24
  if connection.respond_to?(:execute_procedure)
21
25
  connection.execute_procedure(proc_name,*variables)
@@ -25,24 +29,11 @@ module ActiveRecord
25
29
  end
26
30
 
27
31
  def coerce_sqlserver_date(*attributes)
28
- write_inheritable_attribute :coerced_sqlserver_date_columns, Set.new(attributes.map(&:to_s))
32
+ self.coerced_sqlserver_date_columns += attributes.map(&:to_s)
29
33
  end
30
34
 
31
35
  def coerce_sqlserver_time(*attributes)
32
- write_inheritable_attribute :coerced_sqlserver_time_columns, Set.new(attributes.map(&:to_s))
33
- end
34
-
35
- def coerced_sqlserver_date_columns
36
- read_inheritable_attribute(:coerced_sqlserver_date_columns) || []
37
- end
38
-
39
- def coerced_sqlserver_time_columns
40
- read_inheritable_attribute(:coerced_sqlserver_time_columns) || []
41
- end
42
-
43
- def reset_column_information_with_sqlserver_cache_support
44
- connection.send(:initialize_sqlserver_caches) if connection.respond_to?(:sqlserver?)
45
- reset_column_information_without_sqlserver_cache_support
36
+ self.coerced_sqlserver_time_columns += attributes.map(&:to_s)
46
37
  end
47
38
 
48
39
  end
@@ -4,22 +4,13 @@ module ActiveRecord
4
4
  module CoreExt
5
5
  module ODBC
6
6
 
7
- module TimeStamp
8
-
9
- def to_sqlserver_string
10
- date, time, nanoseconds = to_s.split(' ')
11
- "#{date} #{time}.#{sprintf("%03d",nanoseconds.to_i/1000000)}"
12
- end
13
-
14
- end
15
-
16
7
  module Statement
17
8
 
18
9
  def finished?
19
10
  begin
20
11
  connected?
21
12
  false
22
- rescue *Database.parent_modules_error_exceptions
13
+ rescue ::ODBC::Error
23
14
  true
24
15
  end
25
16
  end
@@ -28,14 +19,6 @@ module ActiveRecord
28
19
 
29
20
  module Database
30
21
 
31
- def self.parent_modules
32
- @parent_module ||= ['ODBC','ODBC_UTF8','ODBC_NONE'].map{ |odbc_ns| odbc_ns.constantize rescue nil }.compact
33
- end
34
-
35
- def self.parent_modules_error_exceptions
36
- @parent_modules_error_exceptions ||= parent_modules.map { |odbc_ns| "::#{odbc_ns}::Error".constantize }
37
- end
38
-
39
22
  def run_block(*args)
40
23
  yield sth = run(*args)
41
24
  sth.drop
@@ -49,9 +32,7 @@ module ActiveRecord
49
32
  end
50
33
  end
51
34
 
52
- ['ODBC','ODBC_UTF8','ODBC_NONE'].map{ |odbc_ns| odbc_ns.constantize rescue nil }.compact.each do |ns|
53
- ns::TimeStamp.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ODBC::TimeStamp
54
- ns::Statement.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ODBC::Statement
55
- ns::Database.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ODBC::Database
56
- end
35
+
36
+ ODBC::Statement.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ODBC::Statement
37
+ ODBC::Database.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ODBC::Database
57
38
 
@@ -3,26 +3,47 @@ module ActiveRecord
3
3
  module Sqlserver
4
4
  module DatabaseStatements
5
5
 
6
- def select_one(sql, name = nil)
7
- result = raw_select sql, name, :fetch => :one
8
- (result && result.first.present?) ? result.first : nil
9
- end
10
-
11
6
  def select_rows(sql, name = nil)
12
- raw_select sql, name, :fetch => :rows
7
+ raw_select sql, name, [], :fetch => :rows
13
8
  end
14
9
 
15
- def execute(sql, name = nil, skip_logging = false)
10
+ def execute(sql, name = nil)
16
11
  if id_insert_table_name = query_requires_identity_insert?(sql)
17
12
  with_identity_insert_enabled(id_insert_table_name) { do_execute(sql,name) }
18
13
  else
19
14
  do_execute(sql,name)
20
15
  end
21
16
  end
17
+
18
+ def exec_query(sql, name = 'SQL', binds = [], sqlserver_options = {})
19
+ if id_insert_table_name = sqlserver_options[:insert] ? query_requires_identity_insert?(sql) : nil
20
+ with_identity_insert_enabled(id_insert_table_name) { do_exec_query(sql, name, binds) }
21
+ else
22
+ do_exec_query(sql, name, binds)
23
+ end
24
+ end
25
+
26
+ def exec_insert(sql, name, binds)
27
+ exec_query sql, name, binds, :insert => true
28
+ end
29
+
30
+ def exec_delete(sql, name, binds)
31
+ sql << "; SELECT @@ROWCOUNT AS AffectedRows"
32
+ super.rows.first.first
33
+ end
34
+
35
+ def exec_update(sql, name, binds)
36
+ sql << "; SELECT @@ROWCOUNT AS AffectedRows"
37
+ super.rows.first.first
38
+ end
22
39
 
23
40
  def outside_transaction?
24
41
  info_schema_query { select_value("SELECT @@TRANCOUNT") == 0 }
25
42
  end
43
+
44
+ def supports_statement_cache?
45
+ true
46
+ end
26
47
 
27
48
  def begin_db_transaction
28
49
  do_execute "BEGIN TRANSACTION"
@@ -33,7 +54,7 @@ module ActiveRecord
33
54
  end
34
55
 
35
56
  def rollback_db_transaction
36
- do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
57
+ do_execute "ROLLBACK TRANSACTION" rescue nil
37
58
  end
38
59
 
39
60
  def create_savepoint
@@ -55,15 +76,8 @@ module ActiveRecord
55
76
  "DEFAULT VALUES"
56
77
  end
57
78
 
58
- def case_sensitive_equality_operator
59
- cs_equality_operator
60
- end
61
-
62
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
63
- match_data = where_sql.match(/^(.*?[\]\) ])WHERE[\[\( ]/)
64
- limit = match_data[1]
65
- where_sql.sub!(limit,'')
66
- "WHERE #{quoted_primary_key} IN (SELECT #{limit} #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
79
+ def case_sensitive_modifier(node)
80
+ node.acts_like?(:string) ? Arel::Nodes::Bin.new(node) : node
67
81
  end
68
82
 
69
83
  # === SQLServer Specific ======================================== #
@@ -190,29 +204,17 @@ module ActiveRecord
190
204
 
191
205
  protected
192
206
 
193
- def select(sql, name = nil)
194
- raw_select sql, name, :fetch => :all
207
+ def select(sql, name = nil, binds = [])
208
+ exec_query(sql, name, binds).to_a
195
209
  end
196
210
 
197
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
198
- @insert_sql = true
199
- case @connection_options[:mode]
200
- when :dblib
201
- execute(sql, name) || id_value
202
- else
203
- super || select_value("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
204
- end
211
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
212
+ sql = "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"# unless binds.empty?
213
+ super
205
214
  end
206
-
207
- def update_sql(sql, name = nil)
208
- @update_sql = true
209
- case @connection_options[:mode]
210
- when :dblib
211
- execute(sql, name)
212
- else
213
- execute(sql, name)
214
- select_value('SELECT @@ROWCOUNT AS AffectedRows')
215
- end
215
+
216
+ def last_inserted_id(result)
217
+ super || select_value("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
216
218
  end
217
219
 
218
220
  # === SQLServer Specific ======================================== #
@@ -230,24 +232,50 @@ module ActiveRecord
230
232
  end
231
233
  end
232
234
 
235
+ def do_exec_query(sql, name, binds)
236
+ statement = quote(sql)
237
+ names_and_types = []
238
+ params = []
239
+ binds.each_with_index do |(column,value),index|
240
+ ar_column = column.is_a?(ActiveRecord::ConnectionAdapters::Column)
241
+ next if ar_column && column.sql_type == 'timestamp'
242
+ v = value
243
+ names_and_types << if ar_column
244
+ v = value.to_i if column.is_integer?
245
+ "@#{index} #{column.sql_type_for_statement}"
246
+ elsif column.acts_like?(:string)
247
+ "@#{index} nvarchar(max)"
248
+ elsif column.is_a?(Fixnum)
249
+ v = value.to_i
250
+ "@#{index} int"
251
+ else
252
+ raise "Unknown bind columns. We can account for this."
253
+ end
254
+ quoted_value = ar_column ? quote(v,column) : quote(v,nil)
255
+ params << "@#{index} = #{quoted_value}"
256
+ end
257
+ sql = "EXEC sp_executesql #{statement}"
258
+ sql << ", #{quote(names_and_types.join(', '))}, #{params.join(', ')}" unless binds.empty?
259
+ raw_select sql, name, binds, :ar_result => true
260
+ end
261
+
233
262
  def raw_connection_do(sql)
234
263
  case @connection_options[:mode]
235
264
  when :dblib
236
- @insert_sql ? @connection.execute(sql).insert : @connection.execute(sql).do
265
+ @connection.execute(sql).do
237
266
  when :odbc
238
267
  @connection.do(sql)
239
268
  else :adonet
240
269
  @connection.create_command.tap{ |cmd| cmd.command_text = sql }.execute_non_query
241
270
  end
242
271
  ensure
243
- @insert_sql = false
244
272
  @update_sql = false
245
273
  end
246
274
 
247
275
  # === SQLServer Specific (Selecting) ============================ #
248
276
 
249
- def raw_select(sql, name=nil, options={})
250
- log(sql,name) do
277
+ def raw_select(sql, name=nil, binds=[], options={})
278
+ log(sql,name,binds) do
251
279
  begin
252
280
  handle = raw_connection_run(sql)
253
281
  handle_to_names_and_values(handle, options)
@@ -294,56 +322,25 @@ module ActiveRecord
294
322
  def handle_to_names_and_values_dblib(handle, options={})
295
323
  query_options = {}.tap do |qo|
296
324
  qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
297
- qo[:first] = true if options[:fetch] == :one
298
- qo[:as] = options[:fetch] == :rows ? :array : :hash
325
+ qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
299
326
  end
300
- handle.each(query_options)
327
+ results = handle.each(query_options)
328
+ options[:ar_result] ? ActiveRecord::Result.new(handle.fields, results) : results
301
329
  end
302
330
 
303
331
  def handle_to_names_and_values_odbc(handle, options={})
304
- @connection.use_utc = ActiveRecord::Base.default_timezone == :utc if @connection_supports_native_types
305
- case options[:fetch]
306
- when :all, :one
307
- if @connection_supports_native_types
308
- if options[:fetch] == :all
309
- handle.each_hash || []
310
- else
311
- row = handle.fetch_hash
312
- rows = row ? [row] : [[]]
313
- end
314
- else
315
- rows = if options[:fetch] == :all
316
- handle.fetch_all || []
317
- else
318
- row = handle.fetch
319
- row ? [row] : [[]]
320
- end
321
- names = handle.columns(true).map{ |c| c.name }
322
- names_and_values = []
323
- rows.each do |row|
324
- h = {}
325
- i = 0
326
- while i < row.size
327
- v = row[i]
328
- h[names[i]] = v.respond_to?(:to_sqlserver_string) ? v.to_sqlserver_string : v
329
- i += 1
330
- end
331
- names_and_values << h
332
- end
333
- names_and_values
334
- end
335
- when :rows
332
+ @connection.use_utc = ActiveRecord::Base.default_timezone == :utc
333
+ if options[:ar_result]
334
+ columns = handle.columns(true).map { |c| c.name }
336
335
  rows = handle.fetch_all || []
337
- return rows if @connection_supports_native_types
338
- rows.each do |row|
339
- i = 0
340
- while i < row.size
341
- v = row[i]
342
- row[i] = v.to_sqlserver_string if v.respond_to?(:to_sqlserver_string)
343
- i += 1
344
- end
336
+ ActiveRecord::Result.new(columns, rows)
337
+ else
338
+ case options[:fetch]
339
+ when :all
340
+ handle.each_hash || []
341
+ when :rows
342
+ handle.fetch_all || []
345
343
  end
346
- rows
347
344
  end
348
345
  end
349
346
 
@@ -352,7 +349,6 @@ module ActiveRecord
352
349
  names = []
353
350
  rows = []
354
351
  fields_named = options[:fetch] == :rows
355
- one_row_only = options[:fetch] == :one
356
352
  while handle.read
357
353
  row = []
358
354
  handle.visible_field_count.times do |row_index|
@@ -371,7 +367,6 @@ module ActiveRecord
371
367
  end
372
368
  row << value
373
369
  names << handle.get_name(row_index).to_s unless fields_named
374
- break if one_row_only
375
370
  end
376
371
  rows << row
377
372
  fields_named = true
@@ -398,8 +393,7 @@ module ActiveRecord
398
393
 
399
394
  def finish_statement_handle(handle)
400
395
  case @connection_options[:mode]
401
- when :dblib
402
- handle.cancel if handle
396
+ when :dblib
403
397
  when :odbc
404
398
  handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
405
399
  when :adonet
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
 
10
10
  LOST_CONNECTION_EXCEPTIONS = {
11
11
  :dblib => ['TinyTds::Error'],
12
- :odbc => ['ODBC::Error','ODBC_UTF8::Error','ODBC_NONE::Error'],
12
+ :odbc => ['ODBC::Error'],
13
13
  :adonet => ['TypeError','System::Data::SqlClient::SqlException']
14
14
  }.freeze
15
15
 
@@ -15,6 +15,8 @@ module ActiveRecord
15
15
  else
16
16
  super
17
17
  end
18
+ when nil
19
+ column.respond_to?(:sql_type) && column.sql_type == 'timestamp' ? 'DEFAULT' : super
18
20
  else
19
21
  super
20
22
  end
@@ -26,12 +28,20 @@ module ActiveRecord
26
28
 
27
29
  def quote_column_name(name)
28
30
  @sqlserver_quoted_column_and_table_names[name] ||=
29
- name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n.to_s.gsub(']', ']]')}]" }.join('.')
31
+ name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n}]" }.join('.')
30
32
  end
31
33
 
32
34
  def quote_table_name(name)
33
35
  quote_column_name(name)
34
36
  end
37
+
38
+ def substitute_at(column, index)
39
+ if column.respond_to?(:sql_type) && column.sql_type == 'timestamp'
40
+ nil
41
+ else
42
+ Arel.sql "@#{index}"
43
+ end
44
+ end
35
45
 
36
46
  def quoted_true
37
47
  QUOTED_TRUE
@@ -20,8 +20,7 @@ module ActiveRecord
20
20
 
21
21
  def indexes(table_name, name = nil)
22
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|
23
+ select("EXEC sp_helpindex #{quote_table_name(unquoted_table_name)}",name).inject([]) do |indexes,index|
25
24
  index = index.with_indifferent_access
26
25
  if index[:index_description] =~ /primary key/
27
26
  indexes
@@ -40,8 +39,8 @@ module ActiveRecord
40
39
 
41
40
  def columns(table_name, name = nil)
42
41
  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|
42
+ cache_key = unqualify_table_name(table_name)
43
+ column_definitions(table_name).collect do |ci|
45
44
  sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
46
45
  SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
47
46
  end
@@ -199,11 +198,13 @@ module ActiveRecord
199
198
  ELSE 1
200
199
  END as is_identity
201
200
  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}' "}
201
+ WHERE columns.TABLE_NAME = @0
202
+ AND columns.TABLE_SCHEMA = #{table_schema.blank? ? "schema_name()" : "@1"}
204
203
  ORDER BY columns.ordinal_position
205
204
  }.gsub(/[ \t\r\n]+/,' ')
206
- results = info_schema_query { select(sql,nil) }
205
+ binds = [['table_name', table_name]]
206
+ binds << ['table_schema',table_schema] unless table_schema.blank?
207
+ results = info_schema_query { do_exec_query(sql, 'InfoSchema::ColumnDefinitions', binds) }
207
208
  results.collect do |ci|
208
209
  ci = ci.symbolize_keys
209
210
  ci[:type] = case ci[:type]
@@ -279,9 +280,9 @@ module ActiveRecord
279
280
  end
280
281
 
281
282
  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
283
+ if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)\s+INTO\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
284
+ $2 || $3
285
+ elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
285
286
  $1
286
287
  else
287
288
  nil
@@ -352,12 +353,10 @@ module ActiveRecord
352
353
 
353
354
  def remove_sqlserver_columns_cache_for(table_name)
354
355
  cache_key = unqualify_table_name(table_name)
355
- @sqlserver_columns_cache[cache_key] = nil
356
356
  initialize_sqlserver_caches(false)
357
357
  end
358
358
 
359
359
  def initialize_sqlserver_caches(reset_columns=true)
360
- @sqlserver_columns_cache = {} if reset_columns
361
360
  @sqlserver_views_cache = nil
362
361
  @sqlserver_view_information_cache = {}
363
362
  @sqlserver_quoted_column_and_table_names = {}
@@ -369,14 +368,14 @@ module ActiveRecord
369
368
  if insert_sql?(sql)
370
369
  table_name = get_table_name(sql)
371
370
  id_column = identity_column(table_name)
372
- id_column && sql =~ /^\s*INSERT[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
371
+ id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
373
372
  else
374
373
  false
375
374
  end
376
375
  end
377
376
 
378
377
  def insert_sql?(sql)
379
- !(sql =~ /^\s*INSERT/i).nil?
378
+ !(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
380
379
  end
381
380
 
382
381
  def with_identity_insert_enabled(table_name)
@@ -389,7 +388,7 @@ module ActiveRecord
389
388
 
390
389
  def set_identity_insert(table_name, enable = true)
391
390
  sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
392
- do_execute(sql,'IDENTITY_INSERT')
391
+ do_execute sql,'InfoSchema::SetIdentityInsert'
393
392
  rescue Exception => e
394
393
  raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
395
394
  end
@@ -5,7 +5,6 @@ require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
5
5
  require 'active_record/connection_adapters/sqlserver/database_limits'
6
6
  require 'active_record/connection_adapters/sqlserver/database_statements'
7
7
  require 'active_record/connection_adapters/sqlserver/errors'
8
- require 'active_record/connection_adapters/sqlserver/query_cache'
9
8
  require 'active_record/connection_adapters/sqlserver/schema_statements'
10
9
  require 'active_record/connection_adapters/sqlserver/quoting'
11
10
  require 'active_support/core_ext/kernel/requires'
@@ -26,16 +25,7 @@ module ActiveRecord
26
25
  warn("TinyTds v0.4.3 or higher required. Using #{TinyTds::VERSION}") unless TinyTds::Client.instance_methods.map(&:to_s).include?("active?")
27
26
  when :odbc
28
27
  raise ArgumentError, 'Missing :dsn configuration.' unless config.has_key?(:dsn)
29
- if RUBY_VERSION < '1.9'
30
- require_library_or_gem 'odbc'
31
- else
32
- begin
33
- # TODO: [ODBC] Change this to 'odbc_utf8'
34
- require_library_or_gem 'odbc'
35
- rescue LoadError
36
- require_library_or_gem 'odbc'
37
- end
38
- end unless ['::ODBC','::ODBC_UTF8','::ODBC_NONE'].any? { |odbc_ns| odbc_ns.constantize rescue nil }
28
+ require_library_or_gem 'odbc'
39
29
  require 'active_record/connection_adapters/sqlserver/core_ext/odbc'
40
30
  when :adonet
41
31
  require 'System.Data'
@@ -61,7 +51,7 @@ module ActiveRecord
61
51
  module ConnectionAdapters
62
52
 
63
53
  class SQLServerColumn < Column
64
-
54
+
65
55
  def initialize(name, default, sql_type = nil, null = true, sqlserver_options = {})
66
56
  @sqlserver_options = sqlserver_options.symbolize_keys
67
57
  super(name, default, sql_type, null)
@@ -87,6 +77,14 @@ module ActiveRecord
87
77
  @sql_type =~ /nvarchar|ntext|nchar/i
88
78
  end
89
79
 
80
+ def is_integer?
81
+ @sql_type =~ /int/i
82
+ end
83
+
84
+ def sql_type_for_statement
85
+ is_integer? ? sql_type.sub(/\(\d+\)/,'') : sql_type
86
+ end
87
+
90
88
  def default_function
91
89
  @sqlserver_options[:default_function]
92
90
  end
@@ -160,11 +158,10 @@ module ActiveRecord
160
158
  include Sqlserver::DatabaseStatements
161
159
  include Sqlserver::SchemaStatements
162
160
  include Sqlserver::DatabaseLimits
163
- include Sqlserver::QueryCache
164
161
  include Sqlserver::Errors
165
162
 
166
163
  ADAPTER_NAME = 'SQLServer'.freeze
167
- VERSION = '3.0.19'.freeze
164
+ VERSION = '3.1.0.beta1'.freeze
168
165
  DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+"?(\d{4}|\w+)"?/
169
166
  SUPPORTED_VERSIONS = [2005,2008,2010,2011].freeze
170
167
 
@@ -265,6 +262,11 @@ module ActiveRecord
265
262
  remove_database_connections_and_rollback { }
266
263
  end
267
264
 
265
+ def clear_cache!
266
+ # This requires db admin perms and I'm not even sure it is a good idea.
267
+ # raw_connection_do "DBCC FREEPROCCACHE WITH NO_INFOMSGS" rescue nil
268
+ end
269
+
268
270
  # === Abstract Adapter (Misc Support) =========================== #
269
271
 
270
272
  def pk_and_sequence_for(table_name)
@@ -331,7 +333,7 @@ module ActiveRecord
331
333
  end
332
334
 
333
335
  def cs_equality_operator
334
- @@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS ='
336
+ @@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS'
335
337
  end
336
338
 
337
339
 
@@ -358,7 +360,7 @@ module ActiveRecord
358
360
  config = @connection_options
359
361
  @connection = case @connection_options[:mode]
360
362
  when :dblib
361
- appname = config[:appname] || configure_application_name || Rails.application.class.name.split('::').first rescue nil
363
+ appname = config[:appname] || Rails.application.class.name.split('::').first rescue nil
362
364
  login_timeout = config[:login_timeout].present? ? config[:login_timeout].to_i : nil
363
365
  timeout = config[:timeout].present? ? config[:timeout].to_i/1000 : nil
364
366
  encoding = config[:encoding].present? ? config[:encoding] : nil
@@ -390,20 +392,20 @@ module ActiveRecord
390
392
  end
391
393
  end
392
394
  when :odbc
393
- odbc = ['::ODBC','::ODBC_UTF8','::ODBC_NONE'].detect{ |odbc_ns| odbc_ns.constantize rescue nil }.constantize
394
395
  if config[:dsn].include?(';')
395
- driver = odbc::Driver.new.tap do |d|
396
+ driver = ODBC::Driver.new.tap do |d|
396
397
  d.name = config[:dsn_name] || 'Driver1'
397
398
  d.attrs = config[:dsn].split(';').map{ |atr| atr.split('=') }.reject{ |kv| kv.size != 2 }.inject({}){ |h,kv| k,v = kv ; h[k] = v ; h }
398
399
  end
399
- odbc::Database.new.drvconnect(driver)
400
+ ODBC::Database.new.drvconnect(driver)
400
401
  else
401
- odbc.connect config[:dsn], config[:username], config[:password]
402
- end.tap do |c|
403
- if c.respond_to?(:use_time)
402
+ ODBC.connect config[:dsn], config[:username], config[:password]
403
+ end.tap do |c|
404
+ begin
404
405
  c.use_time = true
405
406
  c.use_utc = ActiveRecord::Base.default_timezone == :utc
406
- @connection_supports_native_types = true
407
+ rescue Exception => e
408
+ warn "Ruby ODBC v0.99992 or higher is required."
407
409
  end
408
410
  end
409
411
  when :adonet
@@ -423,24 +425,10 @@ module ActiveRecord
423
425
  connection.open
424
426
  end
425
427
  end
426
- configure_connection
427
428
  rescue
428
429
  raise unless @auto_connecting
429
430
  end
430
431
 
431
- # Override this method so every connection can be configured to your needs.
432
- # For example:
433
- # do_execute "SET TEXTSIZE #{64.megabytes}"
434
- # do_execute "SET CONCAT_NULL_YIELDS_NULL ON"
435
- def configure_connection
436
- end
437
-
438
- # Override this method so every connection can have a unique name. Max 30 characters. Used by TinyTDS only.
439
- # For example:
440
- # "myapp_#{$$}_#{Thread.current.object_id}".to(29)
441
- def configure_application_name
442
- end
443
-
444
432
  def remove_database_connections_and_rollback(database=nil)
445
433
  database ||= current_database
446
434
  do_execute "ALTER DATABASE #{quote_table_name(database)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
@@ -4,10 +4,6 @@ module Arel
4
4
 
5
5
  module Nodes
6
6
 
7
- # See the SelectManager#lock method on why this custom class is needed.
8
- class LockWithSQLServer < Arel::Nodes::Unary
9
- end
10
-
11
7
  # Extending the Ordering class to be comparrison friendly which allows us to call #uniq on a
12
8
  # collection of them. See SelectManager#order for more details.
13
9
  class Ordering < Arel::Nodes::Binary
@@ -26,8 +22,6 @@ module Arel
26
22
 
27
23
  class SelectManager < Arel::TreeManager
28
24
 
29
- alias :lock_without_sqlserver :lock
30
-
31
25
  # Getting real Ordering objects is very important for us. We need to be able to call #uniq on
32
26
  # a colleciton of them reliably as well as using their true object attributes to mutate them
33
27
  # to grouping objects for the inner sql during a select statment with an offset/rownumber. So this
@@ -41,8 +35,6 @@ module Arel
41
35
  table = Arel::Table.new(x.relation.table_alias || x.relation.name)
42
36
  expr = table[x.name]
43
37
  Arel::Nodes::Ordering.new expr
44
- when Arel::Nodes::Ordering
45
- x
46
38
  when String
47
39
  x.split(',').map do |s|
48
40
  expr, direction = s.split
@@ -60,9 +52,17 @@ module Arel
60
52
 
61
53
  # A friendly over ride that allows us to put a special lock object that can have a default or pass
62
54
  # custom string hints down. See the visit_Arel_Nodes_LockWithSQLServer delegation method.
55
+ alias :lock_without_sqlserver :lock
63
56
  def lock(locking=true)
64
57
  if Arel::Visitors::SQLServer === @visitor
65
- @ast.lock = Arel::Nodes::LockWithSQLServer.new(locking)
58
+ case locking
59
+ when true
60
+ locking = Arel.sql('WITH(HOLDLOCK, ROWLOCK)')
61
+ when Arel::Nodes::SqlLiteral
62
+ when String
63
+ locking = Arel.sql locking
64
+ end
65
+ @ast.lock = Arel::Nodes::Lock.new(locking)
66
66
  self
67
67
  else
68
68
  lock_without_sqlserver(locking)
@@ -96,22 +96,14 @@ module Arel
96
96
  "TOP (#{visit o.expr})"
97
97
  end
98
98
 
99
- def visit_Arel_Nodes_Lock o
100
- "WITH(HOLDLOCK, ROWLOCK)"
99
+ def visit_Arel_Nodes_Lock(o)
100
+ visit o.expr
101
101
  end
102
102
 
103
- def visit_Arel_Nodes_LockWithSQLServer o
104
- case o.expr
105
- when TrueClass
106
- "WITH(HOLDLOCK, ROWLOCK)"
107
- when String
108
- o.expr
109
- else
110
- ""
111
- end
103
+ def visit_Arel_Nodes_Bin(o)
104
+ "#{visit o.expr} #{@engine.connection.cs_equality_operator}"
112
105
  end
113
106
 
114
-
115
107
  # SQLServer ToSql/Visitor (Additions)
116
108
 
117
109
  def visit_Arel_Nodes_SelectStatementWithOutOffset(o, windowed=false)
@@ -129,6 +121,8 @@ module Arel
129
121
  expr = Arel.sql projection_without_expression(x.expr)
130
122
  x.descending? ? Arel::Nodes::Max.new([expr]) : Arel::Nodes::Min.new([expr])
131
123
  end
124
+ elsif top_one_everything_for_through_join?(o)
125
+ projections = projections.map { |x| projection_without_expression(x) }
132
126
  end
133
127
  [ ("SELECT" if !windowed),
134
128
  (visit(o.limit) if o.limit && !windowed),
@@ -178,9 +172,8 @@ module Arel
178
172
  # SQLServer Helpers
179
173
 
180
174
  def source_with_lock_for_select_statement(o)
181
- # TODO: [ARel 2.2] Use #from/#source vs. #froms
182
175
  core = o.cores.first
183
- source = "FROM #{visit core.froms}" if core.froms
176
+ source = visit(core.source).strip if core.source
184
177
  if source && o.lock
185
178
  lock = visit o.lock
186
179
  index = source.match(/FROM [\w\[\]\.]+/)[0].length
@@ -220,11 +213,21 @@ module Arel
220
213
  ((p1.respond_to?(:distinct) && p1.distinct) ||
221
214
  p1.respond_to?(:include?) && p1.include?('DISTINCT'))
222
215
  end
216
+
217
+ def single_distinct_select_everything_statement?(o)
218
+ single_distinct_select_statement?(o) && visit(o.cores.first.projections.first).ends_with?(".*")
219
+ end
220
+
221
+ def top_one_everything_for_through_join?(o)
222
+ single_distinct_select_everything_statement?(o) &&
223
+ (o.limit && !o.offset) &&
224
+ join_in_select_statement?(o)
225
+ end
223
226
 
224
227
  def all_projections_aliased_in_select_statement?(o)
225
228
  projections = o.cores.first.projections
226
229
  projections.all? do |x|
227
- x.split(',').all? { |y| y.include?(' AS ') }
230
+ visit(x).split(',').all? { |y| y.include?(' AS ') }
228
231
  end
229
232
  end
230
233
 
@@ -235,14 +238,15 @@ module Arel
235
238
 
236
239
  def eager_limiting_select_statement?(o)
237
240
  core = o.cores.first
238
- single_distinct_select_statement?(o) && (o.limit && !o.offset) && core.groups.empty?
241
+ single_distinct_select_statement?(o) &&
242
+ (o.limit && !o.offset) &&
243
+ core.groups.empty? &&
244
+ !single_distinct_select_everything_statement?(o)
239
245
  end
240
246
 
241
247
  def join_in_select_statement?(o)
242
248
  core = o.cores.first
243
- # TODO: [ARel 2.2] Use #from/#source vs. #froms
244
- # core.source.right.any? { |x| Arel::Nodes::Join === x }
245
- Arel::Nodes::Join === core.froms
249
+ core.source.right.any? { |x| Arel::Nodes::Join === x }
246
250
  end
247
251
 
248
252
  def complex_count_sql?(o)
@@ -291,11 +295,20 @@ module Arel
291
295
  end
292
296
  elsif join_in_select_statement?(o) && all_projections_aliased_in_select_statement?(o)
293
297
  core.projections.map do |x|
294
- Arel.sql x.split(',').map{ |y| y.split(' AS ').last.strip }.join(', ')
298
+ Arel.sql visit(x).split(',').map{ |y| y.split(' AS ').last.strip }.join(', ')
295
299
  end
300
+ elsif function_select_statement?(o)
301
+ [Arel.star]
296
302
  else
297
- # TODO: [ARel 2.2] Use Arel.star
298
- [Arel.sql('[__rnt].*')]
303
+ core.projections.map do |x|
304
+ if x.respond_to?(:relation)
305
+ x = x.dup
306
+ x.relation = x.relation.dup
307
+ x.relation.instance_variable_set :@table_alias, Arel.sql('[__rnt].*')
308
+ else
309
+ x
310
+ end
311
+ end
299
312
  end
300
313
  end
301
314
 
@@ -312,7 +325,7 @@ module Arel
312
325
 
313
326
  # TODO: We use this for grouping too, maybe make Grouping objects vs SqlLiteral.
314
327
  def projection_without_expression(projection)
315
- Arel.sql(projection.split(',').map do |x|
328
+ Arel.sql(visit(projection).split(',').map do |x|
316
329
  x.strip!
317
330
  x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/,'\3')
318
331
  x.sub!(/^DISTINCT\s*/,'')
metadata CHANGED
@@ -1,15 +1,10 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: activerecord-sqlserver-adapter
3
- version: !ruby/object:Gem::Version
4
- hash: 33
5
- prerelease:
6
- segments:
7
- - 3
8
- - 0
9
- - 19
10
- version: 3.0.19
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.1.0.beta1
5
+ prerelease: 6
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Ken Collins
14
9
  - Murray Steele
15
10
  - Shawn Balestracci
@@ -18,51 +13,38 @@ authors:
18
13
  autorequire:
19
14
  bindir: bin
20
15
  cert_chain: []
21
-
22
- date: 2011-10-23 00:00:00 -04:00
16
+ date: 2011-05-06 00:00:00.000000000 -04:00
23
17
  default_executable:
24
- dependencies:
25
- - !ruby/object:Gem::Dependency
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
26
20
  name: activerecord
27
- prerelease: false
28
- requirement: &id001 !ruby/object:Gem::Requirement
21
+ requirement: &2171145400 !ruby/object:Gem::Requirement
29
22
  none: false
30
- requirements:
23
+ requirements:
31
24
  - - ~>
32
- - !ruby/object:Gem::Version
33
- hash: 1
34
- segments:
35
- - 3
36
- - 0
37
- - 3
25
+ - !ruby/object:Gem::Version
38
26
  version: 3.0.3
39
27
  type: :runtime
40
- version_requirements: *id001
41
- - !ruby/object:Gem::Dependency
42
- name: arel
43
28
  prerelease: false
44
- requirement: &id002 !ruby/object:Gem::Requirement
29
+ version_requirements: *2171145400
30
+ - !ruby/object:Gem::Dependency
31
+ name: arel
32
+ requirement: &2171144680 !ruby/object:Gem::Requirement
45
33
  none: false
46
- requirements:
34
+ requirements:
47
35
  - - ~>
48
- - !ruby/object:Gem::Version
49
- hash: 1
50
- segments:
51
- - 2
52
- - 0
53
- - 7
36
+ - !ruby/object:Gem::Version
54
37
  version: 2.0.7
55
38
  type: :runtime
56
- version_requirements: *id002
39
+ prerelease: false
40
+ version_requirements: *2171144680
57
41
  description: SQL Server 2005 and 2008 Adapter For ActiveRecord
58
42
  email: ken@metaskills.net
59
43
  executables: []
60
-
61
44
  extensions: []
62
-
63
- extra_rdoc_files:
45
+ extra_rdoc_files:
64
46
  - README.rdoc
65
- files:
47
+ files:
66
48
  - CHANGELOG
67
49
  - MIT-LICENSE
68
50
  - README.rdoc
@@ -71,7 +53,6 @@ files:
71
53
  - lib/active_record/connection_adapters/sqlserver/database_limits.rb
72
54
  - lib/active_record/connection_adapters/sqlserver/database_statements.rb
73
55
  - lib/active_record/connection_adapters/sqlserver/errors.rb
74
- - lib/active_record/connection_adapters/sqlserver/query_cache.rb
75
56
  - lib/active_record/connection_adapters/sqlserver/quoting.rb
76
57
  - lib/active_record/connection_adapters/sqlserver/schema_statements.rb
77
58
  - lib/active_record/connection_adapters/sqlserver_adapter.rb
@@ -80,37 +61,28 @@ files:
80
61
  has_rdoc: true
81
62
  homepage: http://github.com/rails-sqlserver/activerecord-sqlserver-adapter
82
63
  licenses: []
83
-
84
64
  post_install_message:
85
- rdoc_options:
65
+ rdoc_options:
86
66
  - --main
87
67
  - README.rdoc
88
- require_paths:
68
+ require_paths:
89
69
  - lib
90
- required_ruby_version: !ruby/object:Gem::Requirement
70
+ required_ruby_version: !ruby/object:Gem::Requirement
91
71
  none: false
92
- requirements:
93
- - - ">="
94
- - !ruby/object:Gem::Version
95
- hash: 3
96
- segments:
97
- - 0
98
- version: "0"
99
- required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
77
  none: false
101
- requirements:
102
- - - ">="
103
- - !ruby/object:Gem::Version
104
- hash: 3
105
- segments:
106
- - 0
107
- version: "0"
78
+ requirements:
79
+ - - ! '>'
80
+ - !ruby/object:Gem::Version
81
+ version: 1.3.1
108
82
  requirements: []
109
-
110
83
  rubyforge_project: activerecord-sqlserver-adapter
111
84
  rubygems_version: 1.6.2
112
85
  signing_key:
113
86
  specification_version: 3
114
87
  summary: SQL Server 2005 and 2008 Adapter For ActiveRecord.
115
88
  test_files: []
116
-
@@ -1,17 +0,0 @@
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