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.
- checksums.yaml +4 -4
- data/.gitignore +15 -0
- data/CHANGELOG.md +60 -0
- data/Gemfile +45 -0
- data/Guardfile +29 -0
- data/MIT-LICENSE +5 -5
- data/README.md +193 -0
- data/RUNNING_UNIT_TESTS.md +95 -0
- data/Rakefile +48 -0
- data/activerecord-sqlserver-adapter.gemspec +28 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +5 -15
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +25 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +6 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +9 -3
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +130 -151
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +0 -25
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +39 -78
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +71 -47
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +14 -30
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +112 -108
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +52 -7
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +52 -0
- data/lib/active_record/connection_adapters/sqlserver/type.rb +46 -0
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/castable.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/core_ext/value.rb +39 -0
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +14 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +37 -0
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/quoter.rb +32 -0
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +24 -0
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +59 -0
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +23 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +118 -12
- data/lib/active_record/connection_adapters/sqlserver/version.rb +11 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +133 -198
- data/lib/active_record/connection_adapters/sqlserver_column.rb +15 -86
- data/lib/active_record/sqlserver_base.rb +2 -0
- data/lib/arel/visitors/sqlserver.rb +120 -393
- data/lib/{arel/arel_sqlserver.rb → arel_sqlserver.rb} +1 -3
- data/test/cases/adapter_test_sqlserver.rb +420 -0
- data/test/cases/coerced_tests.rb +642 -0
- data/test/cases/column_test_sqlserver.rb +703 -0
- data/test/cases/connection_test_sqlserver.rb +216 -0
- data/test/cases/database_statements_test_sqlserver.rb +57 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +38 -0
- data/test/cases/helper_sqlserver.rb +36 -0
- data/test/cases/migration_test_sqlserver.rb +66 -0
- data/test/cases/order_test_sqlserver.rb +147 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +90 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +175 -0
- data/test/cases/schema_test_sqlserver.rb +54 -0
- data/test/cases/scratchpad_test_sqlserver.rb +9 -0
- data/test/cases/showplan_test_sqlserver.rb +65 -0
- data/test/cases/specific_schema_test_sqlserver.rb +118 -0
- data/test/cases/transaction_test_sqlserver.rb +61 -0
- data/test/cases/utils_test_sqlserver.rb +91 -0
- data/test/cases/uuid_test_sqlserver.rb +41 -0
- data/test/config.yml +35 -0
- data/test/fixtures/1px.gif +0 -0
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
- data/test/models/sqlserver/customers_view.rb +3 -0
- data/test/models/sqlserver/datatype.rb +3 -0
- data/test/models/sqlserver/datatype_migration.rb +3 -0
- data/test/models/sqlserver/dollar_table_name.rb +3 -0
- data/test/models/sqlserver/edge_schema.rb +13 -0
- data/test/models/sqlserver/fk_has_fk.rb +3 -0
- data/test/models/sqlserver/fk_has_pk.rb +3 -0
- data/test/models/sqlserver/natural_pk_data.rb +4 -0
- data/test/models/sqlserver/natural_pk_int_data.rb +3 -0
- data/test/models/sqlserver/no_pk_data.rb +3 -0
- data/test/models/sqlserver/quoted_table.rb +7 -0
- data/test/models/sqlserver/quoted_view_1.rb +3 -0
- data/test/models/sqlserver/quoted_view_2.rb +3 -0
- data/test/models/sqlserver/string_default.rb +3 -0
- data/test/models/sqlserver/string_defaults_big_view.rb +3 -0
- data/test/models/sqlserver/string_defaults_view.rb +3 -0
- data/test/models/sqlserver/tinyint_pk.rb +3 -0
- data/test/models/sqlserver/upper.rb +3 -0
- data/test/models/sqlserver/uppered.rb +3 -0
- data/test/models/sqlserver/uuid.rb +3 -0
- data/test/schema/datatypes/2012.sql +64 -0
- data/test/schema/sqlserver_specific_schema.rb +181 -0
- data/test/support/coerceable_test_sqlserver.rb +45 -0
- data/test/support/load_schema_sqlserver.rb +29 -0
- data/test/support/minitest_sqlserver.rb +1 -0
- data/test/support/paths_sqlserver.rb +48 -0
- data/test/support/rake_helpers.rb +41 -0
- data/test/support/sql_counter_sqlserver.rb +32 -0
- metadata +271 -21
- data/CHANGELOG +0 -39
- data/VERSION +0 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +0 -17
- data/lib/active_record/sqlserver_test_case.rb +0 -17
- data/lib/arel/nodes_sqlserver.rb +0 -14
- 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
|
|
3
|
+
module SQLServer
|
|
4
4
|
module CoreExt
|
|
5
5
|
module ActiveRecord
|
|
6
|
-
extend ActiveSupport::Concern
|
|
7
6
|
|
|
8
|
-
|
|
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::
|
|
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
|
|
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
|
-
#
|
|
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::
|
|
38
|
-
ActiveRecord::Relation.send :include, ActiveRecord::ConnectionAdapters::
|
|
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
|
|
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::
|
|
28
|
-
ODBC::Database.send :include, ActiveRecord::ConnectionAdapters::
|
|
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
|
|
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
|
|
3
|
+
module SQLServer
|
|
4
4
|
module DatabaseStatements
|
|
5
|
+
|
|
5
6
|
def select_rows(sql, name = nil, binds = [])
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
52
|
-
|
|
50
|
+
def transaction_isolation_levels
|
|
51
|
+
super.merge snapshot: "SNAPSHOT"
|
|
53
52
|
end
|
|
54
53
|
|
|
55
|
-
def
|
|
56
|
-
|
|
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
|
|
60
|
-
|
|
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
|
|
64
|
+
def commit_db_transaction
|
|
65
|
+
do_execute 'COMMIT TRANSACTION'
|
|
64
66
|
end
|
|
65
67
|
|
|
66
|
-
def
|
|
67
|
-
|
|
68
|
+
def rollback_db_transaction
|
|
69
|
+
do_execute 'ROLLBACK TRANSACTION'
|
|
68
70
|
end
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
75
|
-
|
|
78
|
+
def rollback_to_savepoint(name = current_savepoint_name)
|
|
79
|
+
do_execute "ROLLBACK TRANSACTION #{name}"
|
|
76
80
|
end
|
|
77
81
|
|
|
78
|
-
def
|
|
79
|
-
|
|
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
|
|
120
|
-
do_execute "USE #{
|
|
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('
|
|
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
|
-
|
|
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(
|
|
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 "
|
|
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,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
|
311
|
-
|
|
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)
|
|
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
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
params = []
|
|
312
|
+
def sp_executesql_types_and_parameters(binds)
|
|
313
|
+
types, params = [], []
|
|
332
314
|
binds.each_with_index do |(column, value), index|
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
355
|
-
|
|
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 << ", #{
|
|
341
|
+
sql << ", #{types}, #{params}" unless params.empty?
|
|
362
342
|
end
|
|
363
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|