activerecord-sqlserver-adapter 3.0.19 → 3.1.0.beta1

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