activerecord-sqlserver-adapter 4.1.8 → 4.2.0.pre

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