activerecord-sqlserver-adapter 4.1.8 → 4.2.0.pre

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.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +15 -0
  3. data/CHANGELOG.md +60 -0
  4. data/Gemfile +45 -0
  5. data/Guardfile +29 -0
  6. data/MIT-LICENSE +5 -5
  7. data/README.md +193 -0
  8. data/RUNNING_UNIT_TESTS.md +95 -0
  9. data/Rakefile +48 -0
  10. data/activerecord-sqlserver-adapter.gemspec +28 -0
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +5 -15
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +25 -0
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +6 -4
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +9 -3
  15. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +3 -1
  16. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +130 -151
  17. data/lib/active_record/connection_adapters/sqlserver/errors.rb +0 -25
  18. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +39 -78
  19. data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +71 -47
  20. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +14 -30
  21. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +112 -108
  22. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +4 -2
  23. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +1 -1
  24. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +1 -1
  25. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +52 -7
  26. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +52 -0
  27. data/lib/active_record/connection_adapters/sqlserver/type.rb +46 -0
  28. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +15 -0
  29. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +15 -0
  30. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +13 -0
  31. data/lib/active_record/connection_adapters/sqlserver/type/castable.rb +15 -0
  32. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +15 -0
  33. data/lib/active_record/connection_adapters/sqlserver/type/core_ext/value.rb +39 -0
  34. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +14 -0
  35. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +37 -0
  36. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +13 -0
  37. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +17 -0
  38. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +13 -0
  39. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +21 -0
  40. data/lib/active_record/connection_adapters/sqlserver/type/quoter.rb +32 -0
  41. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +17 -0
  42. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +13 -0
  43. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +21 -0
  44. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +24 -0
  45. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +12 -0
  46. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +15 -0
  47. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +59 -0
  48. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +15 -0
  49. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +22 -0
  50. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +15 -0
  51. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +12 -0
  52. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +15 -0
  53. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +20 -0
  54. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +20 -0
  55. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +23 -0
  56. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +20 -0
  57. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +20 -0
  58. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +20 -0
  59. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +20 -0
  60. data/lib/active_record/connection_adapters/sqlserver/utils.rb +118 -12
  61. data/lib/active_record/connection_adapters/sqlserver/version.rb +11 -0
  62. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +133 -198
  63. data/lib/active_record/connection_adapters/sqlserver_column.rb +15 -86
  64. data/lib/active_record/sqlserver_base.rb +2 -0
  65. data/lib/arel/visitors/sqlserver.rb +120 -393
  66. data/lib/{arel/arel_sqlserver.rb → arel_sqlserver.rb} +1 -3
  67. data/test/cases/adapter_test_sqlserver.rb +420 -0
  68. data/test/cases/coerced_tests.rb +642 -0
  69. data/test/cases/column_test_sqlserver.rb +703 -0
  70. data/test/cases/connection_test_sqlserver.rb +216 -0
  71. data/test/cases/database_statements_test_sqlserver.rb +57 -0
  72. data/test/cases/execute_procedure_test_sqlserver.rb +38 -0
  73. data/test/cases/helper_sqlserver.rb +36 -0
  74. data/test/cases/migration_test_sqlserver.rb +66 -0
  75. data/test/cases/order_test_sqlserver.rb +147 -0
  76. data/test/cases/pessimistic_locking_test_sqlserver.rb +90 -0
  77. data/test/cases/schema_dumper_test_sqlserver.rb +175 -0
  78. data/test/cases/schema_test_sqlserver.rb +54 -0
  79. data/test/cases/scratchpad_test_sqlserver.rb +9 -0
  80. data/test/cases/showplan_test_sqlserver.rb +65 -0
  81. data/test/cases/specific_schema_test_sqlserver.rb +118 -0
  82. data/test/cases/transaction_test_sqlserver.rb +61 -0
  83. data/test/cases/utils_test_sqlserver.rb +91 -0
  84. data/test/cases/uuid_test_sqlserver.rb +41 -0
  85. data/test/config.yml +35 -0
  86. data/test/fixtures/1px.gif +0 -0
  87. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
  88. data/test/models/sqlserver/customers_view.rb +3 -0
  89. data/test/models/sqlserver/datatype.rb +3 -0
  90. data/test/models/sqlserver/datatype_migration.rb +3 -0
  91. data/test/models/sqlserver/dollar_table_name.rb +3 -0
  92. data/test/models/sqlserver/edge_schema.rb +13 -0
  93. data/test/models/sqlserver/fk_has_fk.rb +3 -0
  94. data/test/models/sqlserver/fk_has_pk.rb +3 -0
  95. data/test/models/sqlserver/natural_pk_data.rb +4 -0
  96. data/test/models/sqlserver/natural_pk_int_data.rb +3 -0
  97. data/test/models/sqlserver/no_pk_data.rb +3 -0
  98. data/test/models/sqlserver/quoted_table.rb +7 -0
  99. data/test/models/sqlserver/quoted_view_1.rb +3 -0
  100. data/test/models/sqlserver/quoted_view_2.rb +3 -0
  101. data/test/models/sqlserver/string_default.rb +3 -0
  102. data/test/models/sqlserver/string_defaults_big_view.rb +3 -0
  103. data/test/models/sqlserver/string_defaults_view.rb +3 -0
  104. data/test/models/sqlserver/tinyint_pk.rb +3 -0
  105. data/test/models/sqlserver/upper.rb +3 -0
  106. data/test/models/sqlserver/uppered.rb +3 -0
  107. data/test/models/sqlserver/uuid.rb +3 -0
  108. data/test/schema/datatypes/2012.sql +64 -0
  109. data/test/schema/sqlserver_specific_schema.rb +181 -0
  110. data/test/support/coerceable_test_sqlserver.rb +45 -0
  111. data/test/support/load_schema_sqlserver.rb +29 -0
  112. data/test/support/minitest_sqlserver.rb +1 -0
  113. data/test/support/paths_sqlserver.rb +48 -0
  114. data/test/support/rake_helpers.rb +41 -0
  115. data/test/support/sql_counter_sqlserver.rb +32 -0
  116. metadata +271 -21
  117. data/CHANGELOG +0 -39
  118. data/VERSION +0 -1
  119. data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +0 -17
  120. data/lib/active_record/sqlserver_test_case.rb +0 -17
  121. data/lib/arel/nodes_sqlserver.rb +0 -14
  122. data/lib/arel/select_manager_sqlserver.rb +0 -62
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+ require_relative 'test/support/paths_sqlserver'
4
+ require_relative 'test/support/rake_helpers'
5
+
6
+ task test: ['test:dblib']
7
+ task default: [:test]
8
+
9
+ namespace :test do
10
+
11
+ %w(dblib odbc).each do |mode|
12
+
13
+ Rake::TestTask.new(mode) do |t|
14
+ t.libs = ARTest::SQLServer.test_load_paths
15
+ t.test_files = test_files
16
+ t.warning = !!ENV['WARNING']
17
+ t.verbose = true
18
+ end
19
+
20
+ end
21
+
22
+ task 'dblib:env' do
23
+ ENV['ARCONN'] = 'dblib'
24
+ end
25
+
26
+ task 'odbc:env' do
27
+ ENV['ARCONN'] = 'odbc'
28
+ end
29
+
30
+ end
31
+
32
+ task 'test:dblib' => 'test:dblib:env'
33
+ task 'test:odbc' => 'test:odbc:env'
34
+
35
+ namespace :profile do
36
+ ['dblib', 'odbc'].each do |mode|
37
+ namespace mode.to_sym do
38
+ Dir.glob('test/profile/*_profile_case.rb').sort.each do |test_file|
39
+ profile_case = File.basename(test_file).sub('_profile_case.rb', '')
40
+ Rake::TestTask.new(profile_case) do |t|
41
+ t.libs = ARTest::SQLServer.test_load_paths
42
+ t.test_files = [test_file]
43
+ t.verbose = true
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "active_record/connection_adapters/sqlserver/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'activerecord-sqlserver-adapter'
7
+ spec.version = ActiveRecord::ConnectionAdapters::SQLServer::Version::VERSION
8
+ spec.platform = Gem::Platform::RUBY
9
+ spec.authors = ['Ken Collins', 'Anna Carey', 'Will Bond', 'Murray Steele', 'Shawn Balestracci', 'Joe Rafaniello', 'Tom Ward']
10
+ spec.email = ['ken@metaskills.net', 'will@wbond.net']
11
+ spec.homepage = 'http://github.com/rails-sqlserver/activerecord-sqlserver-adapter'
12
+ spec.summary = 'ActiveRecord SQL Server Adapter.'
13
+ spec.description = spec.summary
14
+ spec.files = `git ls-files -z`.split("\x0")
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ['lib']
18
+ spec.add_dependency 'activerecord', '~> 4.2.0'
19
+ spec.add_development_dependency 'bundler'
20
+ spec.add_development_dependency 'guard-minitest'
21
+ spec.add_development_dependency 'minitest', '< 5.3.4' # PENDING: [Rails5.x] Remove test order constraint.
22
+ spec.add_development_dependency 'minitest-focus'
23
+ spec.add_development_dependency 'minitest-spec-rails'
24
+ spec.add_development_dependency 'mocha'
25
+ spec.add_development_dependency 'nokogiri'
26
+ spec.add_development_dependency 'pry'
27
+ spec.add_development_dependency 'rake'
28
+ end
@@ -1,17 +1,13 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- module Sqlserver
3
+ module SQLServer
4
4
  module CoreExt
5
5
  module ActiveRecord
6
- extend ActiveSupport::Concern
7
6
 
8
- included do
9
- class_attribute :coerced_sqlserver_date_columns, :coerced_sqlserver_time_columns
10
- self.coerced_sqlserver_date_columns = Set.new
11
- self.coerced_sqlserver_time_columns = Set.new
12
- end
7
+ extend ActiveSupport::Concern
13
8
 
14
9
  module ClassMethods
10
+
15
11
  def execute_procedure(proc_name, *variables)
16
12
  if connection.respond_to?(:execute_procedure)
17
13
  connection.execute_procedure(proc_name, *variables)
@@ -20,18 +16,12 @@ module ActiveRecord
20
16
  end
21
17
  end
22
18
 
23
- def coerce_sqlserver_date(*attributes)
24
- self.coerced_sqlserver_date_columns += attributes.map(&:to_s)
25
- end
26
-
27
- def coerce_sqlserver_time(*attributes)
28
- self.coerced_sqlserver_time_columns += attributes.map(&:to_s)
29
- end
30
19
  end
20
+
31
21
  end
32
22
  end
33
23
  end
34
24
  end
35
25
  end
36
26
 
37
- ActiveRecord::Base.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ActiveRecord
27
+ ActiveRecord::Base.send :include, ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::ActiveRecord
@@ -0,0 +1,25 @@
1
+ require 'active_record/attribute_methods'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module SQLServer
6
+ module CoreExt
7
+ module AttributeMethods
8
+
9
+
10
+ private
11
+
12
+ def attributes_for_update(attribute_names)
13
+ super.reject do |name|
14
+ column = self.class.columns_hash[name]
15
+ column && column.respond_to?(:is_identity?) && column.is_identity?
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ ActiveRecord::Base.send :include, ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::AttributeMethods
@@ -1,8 +1,9 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- module Sqlserver
3
+ module SQLServer
4
4
  module CoreExt
5
5
  module Explain
6
+
6
7
  SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '
7
8
  SQLSERVER_PARAM_MATCHER = /@\d+ =/
8
9
 
@@ -15,7 +16,7 @@ module ActiveRecord
15
16
 
16
17
  # This is somewhat hacky, but it should reliably reformat our prepared sql statment
17
18
  # which uses sp_executesql to just the first argument, then unquote it. Likewise our
18
- # do_exec_query method should substitude the @n args withe the quoted values.
19
+ # `sp_executesql` method should substitude the @n args withe the quoted values.
19
20
  def unprepare_sqlserver_statement(sql)
20
21
  if sql.starts_with?(SQLSERVER_STATEMENT_PREFIX)
21
22
  executesql = sql.from(SQLSERVER_STATEMENT_PREFIX.length)
@@ -28,11 +29,12 @@ module ActiveRecord
28
29
  sql
29
30
  end
30
31
  end
32
+
31
33
  end
32
34
  end
33
35
  end
34
36
  end
35
37
  end
36
38
 
37
- ActiveRecord::Base.extend ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::Explain
38
- ActiveRecord::Relation.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::Explain
39
+ ActiveRecord::Base.extend ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::Explain
40
+ ActiveRecord::Relation.send :include, ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::Explain
@@ -1,28 +1,34 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- module Sqlserver
3
+ module SQLServer
4
4
  module CoreExt
5
5
  module ODBC
6
+
6
7
  module Statement
8
+
7
9
  def finished?
8
10
  connected?
9
11
  false
10
12
  rescue ::ODBC::Error
11
13
  true
12
14
  end
15
+
13
16
  end
14
17
 
15
18
  module Database
19
+
16
20
  def run_block(*args)
17
21
  yield sth = run(*args)
18
22
  sth.drop
19
23
  end
24
+
20
25
  end
26
+
21
27
  end
22
28
  end
23
29
  end
24
30
  end
25
31
  end
26
32
 
27
- ODBC::Statement.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ODBC::Statement
28
- ODBC::Database.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ODBC::Database
33
+ ODBC::Statement.send :include, ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::ODBC::Statement
34
+ ODBC::Database.send :include, ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::ODBC::Database
@@ -1,7 +1,8 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- module Sqlserver
3
+ module SQLServer
4
4
  module DatabaseLimits
5
+
5
6
  def table_alias_length
6
7
  128
7
8
  end
@@ -41,6 +42,7 @@ module ActiveRecord
41
42
  def joins_per_query
42
43
  256
43
44
  end
45
+
44
46
  end
45
47
  end
46
48
  end
@@ -1,9 +1,10 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
- module Sqlserver
3
+ module SQLServer
4
4
  module DatabaseStatements
5
+
5
6
  def select_rows(sql, name = nil, binds = [])
6
- do_exec_query sql, name, binds, fetch: :rows
7
+ sp_executesql sql, name, binds, fetch: :rows
7
8
  end
8
9
 
9
10
  def execute(sql, name = nil)
@@ -15,19 +16,17 @@ module ActiveRecord
15
16
  end
16
17
 
17
18
  def exec_query(sql, name = 'SQL', binds = [], sqlserver_options = {})
18
- if id_insert_table_name = sqlserver_options[:insert] ? query_requires_identity_insert?(sql) : nil
19
- with_identity_insert_enabled(id_insert_table_name) { do_exec_query(sql, name, binds) }
20
- elsif update_sql?(sql)
21
- sql = strip_ident_from_update(sql)
22
- do_exec_query(sql, name, binds)
23
- else
24
- do_exec_query(sql, name, binds)
25
- end
19
+ sp_executesql(sql, name, binds)
26
20
  end
27
21
 
28
- # The abstract adapter ignores the last two parameters also
29
22
  def exec_insert(sql, name, binds, _pk = nil, _sequence_name = nil)
30
- exec_query sql, name, binds, insert: true
23
+ id_insert = binds_have_identity_column?(binds)
24
+ id_table = table_name_from_binds(binds) if id_insert
25
+ if id_insert && id_table
26
+ with_identity_insert_enabled(id_table) { exec_query(sql, name, binds) }
27
+ else
28
+ exec_query(sql, name, binds)
29
+ end
31
30
  end
32
31
 
33
32
  def exec_delete(sql, name, binds)
@@ -48,35 +47,44 @@ module ActiveRecord
48
47
  do_execute 'BEGIN TRANSACTION'
49
48
  end
50
49
 
51
- def commit_db_transaction
52
- disable_auto_reconnect { do_execute 'COMMIT TRANSACTION' }
50
+ def transaction_isolation_levels
51
+ super.merge snapshot: "SNAPSHOT"
53
52
  end
54
53
 
55
- def rollback_db_transaction
56
- do_execute 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION'
54
+ def begin_isolated_db_transaction(isolation)
55
+ set_transaction_isolation_level transaction_isolation_levels.fetch(isolation)
56
+ begin_db_transaction
57
57
  end
58
58
 
59
- def create_savepoint(name = current_savepoint_name)
60
- disable_auto_reconnect { do_execute "SAVE TRANSACTION #{name}" }
59
+ def set_transaction_isolation_level(isolation_level)
60
+ do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
61
+ begin_db_transaction
61
62
  end
62
63
 
63
- def release_savepoint(name = current_savepoint_name)
64
+ def commit_db_transaction
65
+ do_execute 'COMMIT TRANSACTION'
64
66
  end
65
67
 
66
- def rollback_to_savepoint(name = current_savepoint_name)
67
- disable_auto_reconnect { do_execute "ROLLBACK TRANSACTION #{name}" }
68
+ def rollback_db_transaction
69
+ do_execute 'ROLLBACK TRANSACTION'
68
70
  end
69
71
 
70
- def add_limit_offset!(_sql, _options)
71
- raise NotImplementedError, 'This has been moved to the SQLServerCompiler in Arel.'
72
+ include Savepoints
73
+
74
+ def create_savepoint(name = current_savepoint_name)
75
+ do_execute "SAVE TRANSACTION #{name}"
72
76
  end
73
77
 
74
- def empty_insert_statement_value
75
- 'DEFAULT VALUES'
78
+ def rollback_to_savepoint(name = current_savepoint_name)
79
+ do_execute "ROLLBACK TRANSACTION #{name}"
76
80
  end
77
81
 
78
- def case_sensitive_modifier(node)
79
- node.acts_like?(:string) ? Arel::Nodes::Bin.new(node) : node
82
+ def release_savepoint(name = current_savepoint_name)
83
+ end
84
+
85
+ def case_sensitive_modifier(node, table_attribute)
86
+ node = Arel::Nodes.build_quoted node, table_attribute
87
+ Arel::Nodes::Bin.new(node)
80
88
  end
81
89
 
82
90
  # === SQLServer Specific ======================================== #
@@ -114,15 +122,23 @@ module ActiveRecord
114
122
  end
115
123
  end
116
124
 
125
+ def with_identity_insert_enabled(table_name)
126
+ table_name = quote_table_name(table_name_or_views_table_name(table_name))
127
+ set_identity_insert(table_name, true)
128
+ yield
129
+ ensure
130
+ set_identity_insert(table_name, false)
131
+ end
132
+
117
133
  def use_database(database = nil)
118
134
  return if sqlserver_azure?
119
- database ||= @connection_options[:database]
120
- do_execute "USE #{quote_database_name(database)}" unless database.blank?
135
+ name = SQLServer::Utils.extract_identifiers(database || @connection_options[:database]).quoted
136
+ do_execute "USE #{name}" unless name.blank?
121
137
  end
122
138
 
123
139
  def user_options
124
140
  return {} if sqlserver_azure?
125
- select_rows('DBCC USEROPTIONS WITH NO_INFOMSGS', 'SCHEMA').reduce(HashWithIndifferentAccess.new) do |values, row|
141
+ select_rows('dbcc useroptions', 'SCHEMA').reduce(HashWithIndifferentAccess.new) do |values, row|
126
142
  if row.instance_of? Hash
127
143
  set_option = row.values[0].gsub(/\s+/, '_')
128
144
  user_value = row.values[1]
@@ -135,7 +151,6 @@ module ActiveRecord
135
151
  end
136
152
  end
137
153
 
138
- # TODO: Rails 4 now supports isolation levels
139
154
  def user_options_dateformat
140
155
  if sqlserver_azure?
141
156
  select_value 'SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID', 'SCHEMA'
@@ -169,19 +184,6 @@ module ActiveRecord
169
184
  end
170
185
  end
171
186
 
172
- def run_with_isolation_level(isolation_level)
173
- unless valid_isolation_levels.include?(isolation_level.upcase)
174
- raise ArgumentError, "Invalid isolation level, #{isolation_level}. Supported levels include #{valid_isolation_levels.to_sentence}."
175
- end
176
- initial_isolation_level = user_options_isolation_level || 'READ COMMITTED'
177
- do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
178
- begin
179
- yield
180
- ensure
181
- do_execute "SET TRANSACTION ISOLATION LEVEL #{initial_isolation_level}"
182
- end if block_given?
183
- end
184
-
185
187
  def newid_function
186
188
  select_value 'SELECT NEWID()'
187
189
  end
@@ -190,51 +192,6 @@ module ActiveRecord
190
192
  select_value 'SELECT NEWSEQUENTIALID()'
191
193
  end
192
194
 
193
- def activity_stats
194
- select_all %|
195
- SELECT
196
- [session_id] = s.session_id,
197
- [user_process] = CONVERT(CHAR(1), s.is_user_process),
198
- [login] = s.login_name,
199
- [database] = ISNULL(db_name(r.database_id), N''),
200
- [task_state] = ISNULL(t.task_state, N''),
201
- [command] = ISNULL(r.command, N''),
202
- [application] = ISNULL(s.program_name, N''),
203
- [wait_time_ms] = ISNULL(w.wait_duration_ms, 0),
204
- [wait_type] = ISNULL(w.wait_type, N''),
205
- [wait_resource] = ISNULL(w.resource_description, N''),
206
- [blocked_by] = ISNULL(CONVERT (varchar, w.blocking_session_id), ''),
207
- [head_blocker] =
208
- CASE
209
- -- session has an active request, is blocked, but is blocking others
210
- WHEN r2.session_id IS NOT NULL AND r.blocking_session_id = 0 THEN '1'
211
- -- session is idle but has an open tran and is blocking others
212
- WHEN r.session_id IS NULL THEN '1'
213
- ELSE ''
214
- END,
215
- [total_cpu_ms] = s.cpu_time,
216
- [total_physical_io_mb] = (s.reads + s.writes) * 8 / 1024,
217
- [memory_use_kb] = s.memory_usage * 8192 / 1024,
218
- [open_transactions] = ISNULL(r.open_transaction_count,0),
219
- [login_time] = s.login_time,
220
- [last_request_start_time] = s.last_request_start_time,
221
- [host_name] = ISNULL(s.host_name, N''),
222
- [net_address] = ISNULL(c.client_net_address, N''),
223
- [execution_context_id] = ISNULL(t.exec_context_id, 0),
224
- [request_id] = ISNULL(r.request_id, 0),
225
- [workload_group] = N''
226
- FROM sys.dm_exec_sessions s LEFT OUTER JOIN sys.dm_exec_connections c ON (s.session_id = c.session_id)
227
- LEFT OUTER JOIN sys.dm_exec_requests r ON (s.session_id = r.session_id)
228
- LEFT OUTER JOIN sys.dm_os_tasks t ON (r.session_id = t.session_id AND r.request_id = t.request_id)
229
- LEFT OUTER JOIN
230
- (SELECT *, ROW_NUMBER() OVER (PARTITION BY waiting_task_address ORDER BY wait_duration_ms DESC) AS row_num
231
- FROM sys.dm_os_waiting_tasks
232
- ) w ON (t.task_address = w.waiting_task_address) AND w.row_num = 1
233
- LEFT OUTER JOIN sys.dm_exec_requests r2 ON (r.session_id = r2.blocking_session_id)
234
- WHERE db_name(r.database_id) = '#{current_database}'
235
- ORDER BY s.session_id|
236
- end
237
-
238
195
  # === SQLServer Specific (Rake/Test Helpers) ==================== #
239
196
 
240
197
  def recreate_database
@@ -244,21 +201,25 @@ module ActiveRecord
244
201
  end
245
202
 
246
203
  def recreate_database!(database = nil)
247
- current_db = current_database
248
- database ||= current_db
249
- this_db = database.to_s == current_db
250
- do_execute 'USE master' if this_db
204
+ database ||= current_database
251
205
  drop_database(database)
252
206
  create_database(database)
253
207
  ensure
254
- use_database(current_db) if this_db
208
+ use_database(database)
255
209
  end
256
210
 
257
211
  def drop_database(database)
258
212
  retry_count = 0
259
213
  max_retries = 1
214
+ name = SQLServer::Utils.extract_identifiers(database)
260
215
  begin
261
- do_execute "DROP DATABASE #{quote_database_name(database)}"
216
+ do_execute "
217
+ USE master
218
+ IF EXISTS (
219
+ SELECT * FROM [sys].[databases]
220
+ WHERE name = #{quote(name.object)}
221
+ ) DROP DATABASE #{name}
222
+ ".squish
262
223
  rescue ActiveRecord::StatementInvalid => err
263
224
  if err.message =~ /because it is currently in use/i
264
225
  raise if retry_count >= max_retries
@@ -273,12 +234,19 @@ module ActiveRecord
273
234
  end
274
235
  end
275
236
 
276
- def create_database(database, collation = @connection_options[:collation])
277
- if collation
278
- do_execute "CREATE DATABASE #{quote_database_name(database)} COLLATE #{collation}"
279
- else
280
- do_execute "CREATE DATABASE #{quote_database_name(database)}"
237
+ def create_database(database, options = {})
238
+ name = SQLServer::Utils.extract_identifiers(database)
239
+ options = {collation: @connection_options[:collation]}.merge!(options.symbolize_keys)
240
+ options = options.select { |_, v| v.present? }
241
+ option_string = options.inject("") do |memo, (key, value)|
242
+ memo += case key
243
+ when :collation
244
+ " COLLATE #{value}"
245
+ else
246
+ ""
247
+ end
281
248
  end
249
+ do_execute "CREATE DATABASE #{name}#{option_string}"
282
250
  end
283
251
 
284
252
  def current_database
@@ -289,6 +257,7 @@ module ActiveRecord
289
257
  select_value "SELECT SERVERPROPERTY('SqlCharSetName')"
290
258
  end
291
259
 
260
+
292
261
  protected
293
262
 
294
263
  def select(sql, name = nil, binds = [])
@@ -296,71 +265,82 @@ module ActiveRecord
296
265
  end
297
266
 
298
267
  def sql_for_insert(sql, pk, id_value, sequence_name, binds)
299
- sql =
300
- if pk
301
- sql.insert(sql.index(/ (DEFAULT )?VALUES/), " OUTPUT inserted.#{pk}")
302
- else
303
- "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
304
- end
268
+ sql = if pk
269
+ quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
270
+ sql.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk}"
271
+ else
272
+ "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
273
+ end
305
274
  super
306
275
  end
307
276
 
308
277
  # === SQLServer Specific ======================================== #
309
278
 
310
- def valid_isolation_levels
311
- ['READ COMMITTED', 'READ UNCOMMITTED', 'REPEATABLE READ', 'SERIALIZABLE', 'SNAPSHOT']
279
+ def binds_have_identity_column?(binds)
280
+ binds.any? do |column_value|
281
+ column, value = column_value
282
+ SQLServerColumn === column && column.is_identity?
283
+ end
284
+ end
285
+
286
+ def table_name_from_binds(binds)
287
+ binds.detect { |column_value|
288
+ column, value = column_value
289
+ SQLServerColumn === column
290
+ }.try(:first).try(:table_name)
291
+ end
292
+
293
+ def set_identity_insert(table_name, enable = true)
294
+ do_execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
295
+ rescue Exception
296
+ raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
312
297
  end
313
298
 
314
299
  # === SQLServer Specific (Executing) ============================ #
315
300
 
316
301
  def do_execute(sql, name = 'SQL')
317
- log(sql, name) do
318
- with_sqlserver_error_handling { raw_connection_do(sql) }
319
- end
302
+ log(sql, name) { raw_connection_do(sql) }
320
303
  end
321
304
 
322
- def do_exec_query(sql, name, binds, options = {})
323
- # This allows non-AR code to utilize the binds
324
- # handling code, e.g. select_rows()
325
- if options[:fetch] != :rows
326
- options[:ar_result] = true
327
- end
305
+ def sp_executesql(sql, name, binds, options = {})
306
+ options[:ar_result] = true if options[:fetch] != :rows
307
+ types, params = sp_executesql_types_and_parameters(binds)
308
+ sql = sp_executesql_sql(sql, types, params, name)
309
+ raw_select sql, name, binds, options
310
+ end
328
311
 
329
- explaining = name == 'EXPLAIN'
330
- names_and_types = []
331
- params = []
312
+ def sp_executesql_types_and_parameters(binds)
313
+ types, params = [], []
332
314
  binds.each_with_index do |(column, value), index|
333
- ar_column = column.is_a?(ActiveRecord::ConnectionAdapters::Column)
334
- next if ar_column && column.sql_type == 'timestamp'
335
- v = value
336
- names_and_types << if ar_column
337
- if column.is_integer? && value.present?
338
- v = value.to_i
339
- # Reset the casted value to the bind as required by Rails 4.1
340
- binds[index] = [column, v]
341
- end
342
- "@#{index} #{column.sql_type_for_statement}"
343
- elsif column.acts_like?(:string)
344
- "@#{index} nvarchar(max)"
345
- elsif column.is_a?(Fixnum)
346
- v = value.to_i
347
- "@#{index} int"
348
- else
349
- raise 'Unknown bind columns. We can account for this.'
350
- end
351
- quoted_value = ar_column ? quote(v, column) : quote(v, nil)
352
- params << (explaining ? quoted_value : "@#{index} = #{quoted_value}")
315
+ types << "@#{index} #{sp_executesql_sql_type(column, value)}"
316
+ params << quote(value, column)
353
317
  end
354
- if explaining
355
- params.each_with_index do |param, index|
318
+ [types, params]
319
+ end
320
+
321
+ def sp_executesql_sql_type(column, value)
322
+ return column.sql_type_for_statement if SQLServerColumn === column
323
+ if value.is_a?(Numeric)
324
+ 'int'
325
+ # We can do more here later.
326
+ else
327
+ 'nvarchar(max)'
328
+ end
329
+ end
330
+
331
+ def sp_executesql_sql(sql, types, params, name)
332
+ if name == 'EXPLAIN'
333
+ params.each.with_index do |param, index|
356
334
  substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
357
335
  sql.sub! substitute_at_finder, param
358
336
  end
359
337
  else
338
+ types = quote(types.join(', '))
339
+ params = params.map.with_index{ |p, i| "@#{i} = #{p}" }.join(', ') # Only p is needed, but with @i helps explain regexp.
360
340
  sql = "EXEC sp_executesql #{quote(sql)}"
361
- sql << ", #{quote(names_and_types.join(', '))}, #{params.join(', ')}" unless binds.empty?
341
+ sql << ", #{types}, #{params}" unless params.empty?
362
342
  end
363
- raw_select sql, name, binds, options
343
+ sql
364
344
  end
365
345
 
366
346
  def raw_connection_do(sql)
@@ -388,13 +368,11 @@ module ActiveRecord
388
368
  end
389
369
 
390
370
  def raw_connection_run(sql)
391
- with_sqlserver_error_handling do
392
- case @connection_options[:mode]
393
- when :dblib
394
- @connection.execute(sql)
395
- when :odbc
396
- block_given? ? @connection.run_block(sql) { |handle| yield(handle) } : @connection.run(sql)
397
- end
371
+ case @connection_options[:mode]
372
+ when :dblib
373
+ @connection.execute(sql)
374
+ when :odbc
375
+ block_given? ? @connection.run_block(sql) { |handle| yield(handle) } : @connection.run(sql)
398
376
  end
399
377
  end
400
378
 
@@ -450,6 +428,7 @@ module ActiveRecord
450
428
  end
451
429
  handle
452
430
  end
431
+
453
432
  end
454
433
  end
455
434
  end