activerecord-jdbcsqlserver-adapter 50.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.travis.yml +27 -0
  4. data/CHANGELOG.md +124 -0
  5. data/CODE_OF_CONDUCT.md +31 -0
  6. data/Dockerfile +20 -0
  7. data/Gemfile +77 -0
  8. data/Guardfile +29 -0
  9. data/MIT-LICENSE +20 -0
  10. data/RAILS5-TODO.md +5 -0
  11. data/README.md +93 -0
  12. data/RUNNING_UNIT_TESTS.md +96 -0
  13. data/Rakefile +46 -0
  14. data/VERSION +1 -0
  15. data/activerecord-jdbcsqlserver-adapter.gemspec +21 -0
  16. data/appveyor.yml +39 -0
  17. data/docker-compose.ci.yml +11 -0
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +27 -0
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +25 -0
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/date_time.rb +58 -0
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +47 -0
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +4 -0
  23. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  24. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +362 -0
  25. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +67 -0
  26. data/lib/active_record/connection_adapters/sqlserver/errors.rb +7 -0
  27. data/lib/active_record/connection_adapters/sqlserver/jdbc_overrides.rb +192 -0
  28. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +99 -0
  29. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +34 -0
  30. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +16 -0
  31. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +517 -0
  32. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +66 -0
  33. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +66 -0
  34. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +22 -0
  35. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +20 -0
  36. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +112 -0
  37. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +64 -0
  38. data/lib/active_record/connection_adapters/sqlserver/type.rb +49 -0
  39. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +19 -0
  40. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +21 -0
  41. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +15 -0
  42. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +32 -0
  43. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +30 -0
  44. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +61 -0
  45. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +71 -0
  46. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +17 -0
  47. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +23 -0
  48. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +21 -0
  49. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +19 -0
  50. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +15 -0
  51. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +11 -0
  52. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +25 -0
  53. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +19 -0
  54. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +15 -0
  55. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +25 -0
  56. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +29 -0
  57. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +12 -0
  58. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +19 -0
  59. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +68 -0
  60. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +93 -0
  61. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +19 -0
  62. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +25 -0
  63. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +21 -0
  64. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +12 -0
  65. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +19 -0
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +26 -0
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +24 -0
  68. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +36 -0
  69. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +26 -0
  70. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +24 -0
  71. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +26 -0
  72. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +24 -0
  73. data/lib/active_record/connection_adapters/sqlserver/utils.rb +146 -0
  74. data/lib/active_record/connection_adapters/sqlserver/version.rb +11 -0
  75. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +445 -0
  76. data/lib/active_record/connection_adapters/sqlserver_column.rb +28 -0
  77. data/lib/active_record/jdbc_sqlserver_connection_methods.rb +31 -0
  78. data/lib/active_record/sqlserver_base.rb +16 -0
  79. data/lib/active_record/tasks/sqlserver_database_tasks.rb +131 -0
  80. data/lib/activerecord-jdbcsqlserver-adapter.rb +24 -0
  81. data/lib/activerecord-sqlserver-adapter.rb +1 -0
  82. data/lib/arel/visitors/sqlserver.rb +205 -0
  83. data/lib/arel_sqlserver.rb +3 -0
  84. data/test/appveyor/dbsetup.ps1 +27 -0
  85. data/test/appveyor/dbsetup.sql +11 -0
  86. data/test/bin/wait-for.sh +79 -0
  87. data/test/cases/adapter_test_sqlserver.rb +430 -0
  88. data/test/cases/coerced_tests.rb +845 -0
  89. data/test/cases/column_test_sqlserver.rb +812 -0
  90. data/test/cases/connection_test_sqlserver.rb +71 -0
  91. data/test/cases/execute_procedure_test_sqlserver.rb +45 -0
  92. data/test/cases/fetch_test_sqlserver.rb +57 -0
  93. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +76 -0
  94. data/test/cases/helper_sqlserver.rb +44 -0
  95. data/test/cases/index_test_sqlserver.rb +47 -0
  96. data/test/cases/json_test_sqlserver.rb +32 -0
  97. data/test/cases/migration_test_sqlserver.rb +61 -0
  98. data/test/cases/order_test_sqlserver.rb +147 -0
  99. data/test/cases/pessimistic_locking_test_sqlserver.rb +94 -0
  100. data/test/cases/rake_test_sqlserver.rb +169 -0
  101. data/test/cases/schema_dumper_test_sqlserver.rb +234 -0
  102. data/test/cases/schema_test_sqlserver.rb +54 -0
  103. data/test/cases/scratchpad_test_sqlserver.rb +8 -0
  104. data/test/cases/showplan_test_sqlserver.rb +65 -0
  105. data/test/cases/specific_schema_test_sqlserver.rb +180 -0
  106. data/test/cases/transaction_test_sqlserver.rb +91 -0
  107. data/test/cases/utils_test_sqlserver.rb +129 -0
  108. data/test/cases/uuid_test_sqlserver.rb +49 -0
  109. data/test/config.yml +38 -0
  110. data/test/debug.rb +14 -0
  111. data/test/fixtures/1px.gif +0 -0
  112. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
  113. data/test/models/sqlserver/booking.rb +3 -0
  114. data/test/models/sqlserver/customers_view.rb +3 -0
  115. data/test/models/sqlserver/datatype.rb +3 -0
  116. data/test/models/sqlserver/datatype_migration.rb +8 -0
  117. data/test/models/sqlserver/dollar_table_name.rb +3 -0
  118. data/test/models/sqlserver/dot_table_name.rb +3 -0
  119. data/test/models/sqlserver/edge_schema.rb +13 -0
  120. data/test/models/sqlserver/fk_has_fk.rb +3 -0
  121. data/test/models/sqlserver/fk_has_pk.rb +3 -0
  122. data/test/models/sqlserver/natural_pk_data.rb +4 -0
  123. data/test/models/sqlserver/natural_pk_int_data.rb +3 -0
  124. data/test/models/sqlserver/no_pk_data.rb +3 -0
  125. data/test/models/sqlserver/object_default.rb +3 -0
  126. data/test/models/sqlserver/quoted_table.rb +7 -0
  127. data/test/models/sqlserver/quoted_view_1.rb +3 -0
  128. data/test/models/sqlserver/quoted_view_2.rb +3 -0
  129. data/test/models/sqlserver/sst_memory.rb +3 -0
  130. data/test/models/sqlserver/string_default.rb +3 -0
  131. data/test/models/sqlserver/string_defaults_big_view.rb +3 -0
  132. data/test/models/sqlserver/string_defaults_view.rb +3 -0
  133. data/test/models/sqlserver/tinyint_pk.rb +3 -0
  134. data/test/models/sqlserver/upper.rb +3 -0
  135. data/test/models/sqlserver/uppered.rb +3 -0
  136. data/test/models/sqlserver/uuid.rb +3 -0
  137. data/test/schema/datatypes/2012.sql +55 -0
  138. data/test/schema/enable-in-memory-oltp.sql +81 -0
  139. data/test/schema/sqlserver_specific_schema.rb +238 -0
  140. data/test/support/coerceable_test_sqlserver.rb +49 -0
  141. data/test/support/connection_reflection.rb +34 -0
  142. data/test/support/load_schema_sqlserver.rb +29 -0
  143. data/test/support/minitest_sqlserver.rb +1 -0
  144. data/test/support/paths_sqlserver.rb +50 -0
  145. data/test/support/rake_helpers.rb +41 -0
  146. data/test/support/sql_counter_sqlserver.rb +28 -0
  147. data/test/support/test_in_memory_oltp.rb +15 -0
  148. metadata +310 -0
@@ -0,0 +1,96 @@
1
+
2
+ # How To Run The Test!
3
+
4
+ This process is much easier than it has been before!
5
+
6
+
7
+ ## TL;DR
8
+
9
+ * Setup two databases in SQL Server, [activerecord_unittest] and [activerecord_unittest2]
10
+ * Create a [rails] user with an empty password and give it a [db_owner] role to both DBs. Some tests require a server role of [sysadmin] too. More details below with DDL SQL examples.
11
+ * $ bundle install
12
+ * $ bundle exec rake test ACTIVERECORD_UNITTEST_HOST='my.db.net'
13
+
14
+ Focusing tests. Use the `ONLY_` env vars to run either ours or the ActiveRecord cases. Use the `TEST_FILES` env variants to focus on specific test(s), use commas for multiple cases. Note, you have to use different env vars to focus only on ours or a core ActiveRecord case. There may be failures when focusing on an ActiveRecord case since our coereced test files is not loaded in this scenerio.
15
+
16
+ ```
17
+ $ bundle exec rake test ONLY_SQLSERVER=1
18
+ $ bundle exec rake test ONLY_ACTIVERECORD=1
19
+
20
+ $ bundle exec rake test TEST_FILES="test/cases/adapter_test_sqlserver.rb"
21
+ $ bundle exec rake test TEST_FILES_AR="test/cases/finder_test.rb"
22
+ ```
23
+
24
+
25
+ ## Creating the test databases
26
+
27
+ The default names for the test databases are `activerecord_unittest` and `activerecord_unittest2`. If you want to use another database name then be sure to update the connection file that matches your connection method in test/connections/native_sqlserver_#{connection_method}/connection.rb. Define a user named 'rails' in SQL Server with all privileges granted for the test databases. Use an empty password for said user.
28
+
29
+ ```sql
30
+ CREATE DATABASE [activerecord_unittest];
31
+ CREATE DATABASE [activerecord_unittest2];
32
+ GO
33
+ CREATE LOGIN [rails] WITH PASSWORD = '', CHECK_POLICY = OFF, DEFAULT_DATABASE = [activerecord_unittest];
34
+ GO
35
+ USE [activerecord_unittest];
36
+ CREATE USER [rails] FOR LOGIN [rails];
37
+ GO
38
+ EXEC sp_addrolemember N'db_owner', N'rails';
39
+ EXEC master..sp_addsrvrolemember @loginame = N'rails', @rolename = N'sysadmin'
40
+ GO
41
+ ```
42
+
43
+ ## Cloning The Repos
44
+
45
+ The tests of this adapter depend on the existence of the Rails which are automatically cloned for you with bundler. However you can clone Rails from git://github.com/rails/rails.git and set the `RAILS_SOURCE` environment variable so bundler will use another local path instead.
46
+
47
+ ```
48
+ $ git clone git://github.com/jruby/activerecord-jdbcsqlserver-adapter.git
49
+ ```
50
+
51
+ Suggest just letting bundler do all the work and assuming there is a git tag for the Rails version, you can set `RAILS_VERSION` before bundling.
52
+
53
+ ```
54
+ $ export RAILS_VERSION='5.0.6'
55
+ $ bundle install
56
+ ```
57
+
58
+
59
+ ## Configure DB Connection
60
+
61
+ Please consult the `test/config.yml` file which is used to parse the configuration options for the DB connections when running tests. This file has overrides for any connection mode that you can set using simple environment variables. Assuming you are using FreeTDS 0.91 and above
62
+
63
+ ```
64
+ $ export ACTIVERECORD_UNITTEST_HOST='my.db.net' # Defaults to localhost
65
+ $ export ACTIVERECORD_UNITTEST_PORT='1533' # Defaults to 1433
66
+ ```
67
+
68
+ These can be passed down to rake too.
69
+
70
+ ```
71
+ $ bundle exec rake test ACTIVERECORD_UNITTEST_HOST='my.db.net'
72
+ ```
73
+
74
+
75
+ ## Bundling
76
+
77
+ Now with that out of the way you can run "bundle install" to hook everything up. Our tests use bundler to setup the load paths correctly. It is important to use bundle exec so we can wire up the ActiveRecord test libs correctly.
78
+
79
+ ```
80
+ $ bundle exec rake test
81
+ ```
82
+
83
+
84
+ ## Testing Options
85
+
86
+
87
+ By default, Bundler will download the Rails git repo and use the git tag that matches the dependency version in our gemspec. If you want to test another version of Rails, you can either temporarily change the :tag for Rails in the Gemfile. Likewise, you can clone the Rails repo your self to another directory and use the `RAILS_SOURCE` environment variable.
88
+
89
+
90
+ ## Troubleshooting
91
+
92
+ * Make sure your firewall is off or allows SQL Server traffic both ways, typically on port 1433.
93
+ * Ensure that you are running on a local admin login to create the Rails user.
94
+ * Possibly change the SQL Server TCP/IP properties in "SQL Server Configuration Manager -> SQL Server Network Configuration -> Protocols for MSSQLSERVER", and ensure that TCP/IP is enabled and the appropriate entries on the "IP Addresses" tab are enabled.
95
+
96
+
@@ -0,0 +1,46 @@
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
+ if defined? JRUBY_VERSION
7
+ task test: ['test:jdbc']
8
+ else
9
+ task test: ['test:dblib']
10
+ end
11
+ task default: [:test]
12
+
13
+ namespace :test do
14
+
15
+ %w(dblib jdbc).each do |mode|
16
+
17
+ Rake::TestTask.new(mode) do |t|
18
+ t.libs = ARTest::SQLServer.test_load_paths
19
+ t.test_files = test_files
20
+ t.warning = !!ENV['WARNING']
21
+ t.verbose = false
22
+ end
23
+
24
+ task "#{mode}:env" do
25
+ ENV['ARCONN'] = mode
26
+ end
27
+
28
+ task mode => "test:#{mode}:env"
29
+ end
30
+
31
+ end
32
+
33
+ namespace :profile do
34
+ ['dblib'].each do |mode|
35
+ namespace mode.to_sym do
36
+ Dir.glob('test/profile/*_profile_case.rb').sort.each do |test_file|
37
+ profile_case = File.basename(test_file).sub('_profile_case.rb', '')
38
+ Rake::TestTask.new(profile_case) do |t|
39
+ t.libs = ARTest::SQLServer.test_load_paths
40
+ t.test_files = [test_file]
41
+ t.verbose = true
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 50.0.0
@@ -0,0 +1,21 @@
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-jdbcsqlserver-adapter'
7
+ spec.version = ActiveRecord::ConnectionAdapters::SQLServer::Version::VERSION
8
+ spec.license = 'MIT'
9
+ spec.authors = ['Ken Collins', 'Anna Carey', 'Will Bond', 'Murray Steele', 'Shawn Balestracci', 'Joe Rafaniello', 'Tom Ward', 'Rob Widmer']
10
+ spec.email = ['ken@metaskills.net', 'will@wbond.net']
11
+ spec.homepage = 'http://github.com/jruby/activerecord-jdbcsqlserver-adapter'
12
+ spec.summary = 'ActiveRecord JDBC SQL Server Adapter.'
13
+ spec.description = 'This is a fork of ActiveRecord SQL Server Adapter for JRuby. SQL Server 2012 and upward.'
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', '~> 5.0.0', '>= 5.0.6'
19
+ spec.add_dependency 'activerecord-jdbc-adapter' , '~> 50.5'
20
+ spec.add_dependency 'jdbc-mssql', '>= 0.6.0'
21
+ end
@@ -0,0 +1,39 @@
1
+ init:
2
+ - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
3
+ - SET PATH=C:\MinGW\msys\1.0\bin;%PATH%
4
+ - SET RAKEOPT=-rdevkit
5
+ - SET TINYTDS_VERSION=1.3.0
6
+ clone_depth: 5
7
+ skip_tags: true
8
+ matrix:
9
+ fast_finish: true
10
+ install:
11
+ - ps: Update-AppveyorBuild -Version "$(Get-Content $env:appveyor_build_folder\VERSION).$env:appveyor_build_number"
12
+ - ruby --version
13
+ - gem --version
14
+ - bundle install
15
+ - gem uninstall bcrypt
16
+ - gem install bcrypt --platform=ruby
17
+ build: off
18
+ test_script:
19
+ - powershell -File "%APPVEYOR_BUILD_FOLDER%\test\appveyor\dbsetup.ps1"
20
+ - timeout /t 4 /nobreak > NUL
21
+ - ps: Start-Service 'MSSQL$SQL2014'
22
+ - timeout /t 4 /nobreak > NUL
23
+ - sqlcmd -S ".\SQL2014" -U sa -P Password12! -i %APPVEYOR_BUILD_FOLDER%\test\appveyor\dbsetup.sql
24
+ - bundle exec rake test ACTIVERECORD_UNITTEST_DATASERVER="localhost\SQL2014"
25
+ - ps: Stop-Service 'MSSQL$SQL2014'
26
+ - ps: Start-Service 'MSSQL$SQL2012SP1'
27
+ - timeout /t 4 /nobreak > NUL
28
+ - sqlcmd -S ".\SQL2012SP1" -U sa -P Password12! -i %APPVEYOR_BUILD_FOLDER%\test\appveyor\dbsetup.sql
29
+ - bundle exec rake test ACTIVERECORD_UNITTEST_DATASERVER="localhost\SQL2012SP1"
30
+ environment:
31
+ CI_AZURE_HOST:
32
+ secure: VChrioaIWkf9iuuaSs4cryiA4honrADgZqNC0++begg=
33
+ CI_AZURE_PASS:
34
+ secure: cSQp8sk4urJYvq0utpsK+r7J+snJ2wpcdp8RdXJfB+w=
35
+ matrix:
36
+ - ruby_version: "23-x64"
37
+ - ruby_version: "23"
38
+ - ruby_version: "22-x64"
39
+ - ruby_version: "22"
@@ -0,0 +1,11 @@
1
+ version: "2.2"
2
+ services:
3
+ database:
4
+ image: metaskills/mssql-server-linux-rails
5
+ ci:
6
+ environment:
7
+ - ACTIVERECORD_UNITTEST_HOST=database
8
+ build: .
9
+ command: test/bin/wait-for.sh database:1433 -- bundle exec rake test
10
+ depends_on:
11
+ - "database"
@@ -0,0 +1,27 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module CoreExt
5
+ module ActiveRecord
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+
11
+ def execute_procedure(proc_name, *variables)
12
+ if connection.respond_to?(:execute_procedure)
13
+ connection.execute_procedure(proc_name, *variables)
14
+ else
15
+ []
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
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
@@ -0,0 +1,58 @@
1
+ require 'date'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module SQLServer
6
+ module CoreExt
7
+
8
+ module DataCompat
9
+
10
+ attr_accessor :_sql_type
11
+
12
+ def quoted
13
+ _sql_type.quoted(self)
14
+ end
15
+
16
+ def to_s(*args)
17
+ return super unless args.empty?
18
+ _sql_type._formatted(self)
19
+ end
20
+
21
+ end
22
+
23
+ # Create our own DateTime class so that we can format strings properly and still have a DateTime class
24
+ # for the jdbc driver to work with
25
+ class DateTime < ::DateTime
26
+
27
+ include DataCompat
28
+
29
+ def self._jd_with_sql_type(value, type)
30
+ jd(value.jd).tap { |t| t._sql_type = type }
31
+ end
32
+
33
+ end
34
+
35
+ # Create our own Time class so that we can format strings properly and still have a Time class
36
+ # for the jdbc driver to work with
37
+ class Time < ::Time
38
+
39
+ include DataCompat
40
+
41
+ def self._at_with_sql_type(value, type)
42
+ new(
43
+ value.year,
44
+ value.month,
45
+ value.day,
46
+ value.hour,
47
+ value.min,
48
+ value.sec + (Rational(value.nsec, 1000) / 1000000),
49
+ value.gmt_offset
50
+ ).tap { |t| t._sql_type = type }
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,47 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module CoreExt
5
+ module Explain
6
+
7
+ SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '.freeze
8
+ SQLSERVER_PARAM_MATCHER = /@\d+ = (.*)/
9
+ SQLSERVER_NATIONAL_STRING_MATCHER = /N'(.*)'/m
10
+
11
+ def exec_explain(queries)
12
+ unprepared_queries = queries.map do |(sql, binds)|
13
+ [unprepare_sqlserver_statement(sql), binds]
14
+ end
15
+ super(unprepared_queries)
16
+ end
17
+
18
+ private
19
+
20
+ # This is somewhat hacky, but it should reliably reformat our prepared sql statment
21
+ # which uses sp_executesql to just the first argument, then unquote it. Likewise our
22
+ # `sp_executesql` method should substitude the @n args withe the quoted values.
23
+ def unprepare_sqlserver_statement(sql)
24
+ if sql.starts_with?(SQLSERVER_STATEMENT_PREFIX)
25
+ executesql = sql.from(SQLSERVER_STATEMENT_PREFIX.length)
26
+ args = executesql.split(', ')
27
+ unprepared_sql = args.shift.strip.match(SQLSERVER_NATIONAL_STRING_MATCHER)[1]
28
+ unprepared_sql = Utils.unquote_string(unprepared_sql)
29
+ args = args.from(args.length / 2)
30
+ args.each_with_index do |arg, index|
31
+ value = arg.match(SQLSERVER_PARAM_MATCHER)[1]
32
+ unprepared_sql.sub! "@#{index}", value
33
+ end
34
+ unprepared_sql
35
+ else
36
+ sql
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ ActiveRecord::Base.extend ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::Explain
47
+ ActiveRecord::Relation.send :include, ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::Explain
@@ -0,0 +1,4 @@
1
+ silence_warnings do
2
+ # Already defined in Rails
3
+ ActiveRecord::ExplainSubscriber::EXPLAINED_SQLS = /(select|update|delete|insert)\b/i
4
+ end
@@ -0,0 +1,49 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module DatabaseLimits
5
+
6
+ def table_alias_length
7
+ 128
8
+ end
9
+
10
+ def column_name_length
11
+ 128
12
+ end
13
+
14
+ def table_name_length
15
+ 128
16
+ end
17
+
18
+ def index_name_length
19
+ 128
20
+ end
21
+
22
+ def columns_per_table
23
+ 1024
24
+ end
25
+
26
+ def indexes_per_table
27
+ 999
28
+ end
29
+
30
+ def columns_per_multicolumn_index
31
+ 16
32
+ end
33
+
34
+ def in_clause_length
35
+ 65_536
36
+ end
37
+
38
+ def sql_query_length
39
+ 65_536 * 4_096
40
+ end
41
+
42
+ def joins_per_query
43
+ 256
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,362 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module DatabaseStatements
5
+
6
+ def select_rows(sql, name = nil, binds = [])
7
+ sp_executesql sql, name, binds, fetch: :rows
8
+ end
9
+
10
+ def execute(sql, name = nil)
11
+ if id_insert_table_name = query_requires_identity_insert?(sql)
12
+ with_identity_insert_enabled(id_insert_table_name) { do_execute(sql, name) }
13
+ else
14
+ do_execute(sql, name)
15
+ end
16
+ end
17
+
18
+ def exec_query(sql, name = 'SQL', binds = [], prepare: false)
19
+ sp_executesql(sql, name, binds, prepare: prepare)
20
+ end
21
+
22
+ def exec_insert(sql, name, binds, pk = nil, _sequence_name = nil)
23
+ if id_insert_table_name = exec_insert_requires_identity?(sql, pk, binds)
24
+ with_identity_insert_enabled(id_insert_table_name) { exec_query(sql, name, binds) }
25
+ else
26
+ exec_query(sql, name, binds)
27
+ end
28
+ end
29
+
30
+ def exec_delete(sql, name, binds)
31
+ sql << '; SELECT @@ROWCOUNT AS AffectedRows'
32
+ super.rows.first.first
33
+ end
34
+
35
+ def exec_update(sql, name, binds)
36
+ sql << '; SELECT @@ROWCOUNT AS AffectedRows'
37
+ super.rows.first.first
38
+ end
39
+
40
+ def supports_statement_cache?
41
+ true
42
+ end
43
+
44
+ def begin_db_transaction
45
+ do_execute 'BEGIN TRANSACTION'
46
+ end
47
+
48
+ def transaction_isolation_levels
49
+ super.merge snapshot: "SNAPSHOT"
50
+ end
51
+
52
+ def begin_isolated_db_transaction(isolation)
53
+ set_transaction_isolation_level transaction_isolation_levels.fetch(isolation)
54
+ begin_db_transaction
55
+ end
56
+
57
+ def set_transaction_isolation_level(isolation_level)
58
+ do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
59
+ end
60
+
61
+ def commit_db_transaction
62
+ do_execute 'COMMIT TRANSACTION'
63
+ end
64
+
65
+ def exec_rollback_db_transaction
66
+ do_execute 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION'
67
+ end
68
+
69
+ include Savepoints
70
+
71
+ def create_savepoint(name = current_savepoint_name)
72
+ do_execute "SAVE TRANSACTION #{name}"
73
+ end
74
+
75
+ def exec_rollback_to_savepoint(name = current_savepoint_name)
76
+ do_execute "ROLLBACK TRANSACTION #{name}"
77
+ end
78
+
79
+ def release_savepoint(name = current_savepoint_name)
80
+ end
81
+
82
+ def case_sensitive_comparison(table, attribute, column, value)
83
+ if value && value.acts_like?(:string)
84
+ table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
85
+ else
86
+ super
87
+ end
88
+ end
89
+
90
+ def can_perform_case_insensitive_comparison_for?(column)
91
+ column.type == :string
92
+ end
93
+ private :can_perform_case_insensitive_comparison_for?
94
+
95
+ # === SQLServer Specific ======================================== #
96
+
97
+ def execute_procedure(proc_name, *variables)
98
+ vars = if variables.any? && variables.first.is_a?(Hash)
99
+ variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
100
+ else
101
+ variables.map { |v| quote(v) }
102
+ end.join(', ')
103
+ sql = "EXEC #{proc_name} #{vars}".strip
104
+ name = 'Execute Procedure'
105
+ log(sql, name) do
106
+ case @connection_options[:mode]
107
+ when :dblib
108
+ result = @connection.execute(sql)
109
+ options = { as: :hash, cache_rows: true, timezone: ActiveRecord::Base.default_timezone || :utc }
110
+ result.each(options) do |row|
111
+ r = row.with_indifferent_access
112
+ yield(r) if block_given?
113
+ end
114
+ result.each.map { |row| row.is_a?(Hash) ? row.with_indifferent_access : row }
115
+ end
116
+ end
117
+ end
118
+
119
+ def with_identity_insert_enabled(table_name)
120
+ table_name = quote_table_name(table_name)
121
+ set_identity_insert(table_name, true)
122
+ yield
123
+ ensure
124
+ set_identity_insert(table_name, false)
125
+ end
126
+
127
+ def use_database(database = nil)
128
+ return if sqlserver_azure?
129
+ name = SQLServer::Utils.extract_identifiers(database || @connection_options[:database]).quoted
130
+ do_execute "USE #{name}" unless name.blank?
131
+ end
132
+
133
+ def user_options
134
+ return {} if sqlserver_azure?
135
+ rows = select_rows('DBCC USEROPTIONS WITH NO_INFOMSGS', 'SCHEMA')
136
+ rows = rows.first if rows.size == 2 && rows.last.empty?
137
+ rows.reduce(HashWithIndifferentAccess.new) do |values, row|
138
+ if row.instance_of? Hash
139
+ set_option = row.values[0].gsub(/\s+/, '_')
140
+ user_value = row.values[1]
141
+ elsif row.instance_of? Array
142
+ set_option = row[0].gsub(/\s+/, '_')
143
+ user_value = row[1]
144
+ end
145
+ values[set_option] = user_value
146
+ values
147
+ end
148
+ end
149
+
150
+ def user_options_dateformat
151
+ if sqlserver_azure?
152
+ select_value 'SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID', 'SCHEMA'
153
+ else
154
+ user_options['dateformat']
155
+ end
156
+ end
157
+
158
+ def user_options_isolation_level
159
+ if sqlserver_azure?
160
+ sql = %(SELECT CASE [transaction_isolation_level]
161
+ WHEN 0 THEN NULL
162
+ WHEN 1 THEN 'READ UNCOMMITTED'
163
+ WHEN 2 THEN 'READ COMMITTED'
164
+ WHEN 3 THEN 'REPEATABLE READ'
165
+ WHEN 4 THEN 'SERIALIZABLE'
166
+ WHEN 5 THEN 'SNAPSHOT' END AS [isolation_level]
167
+ FROM [sys].[dm_exec_sessions]
168
+ WHERE [session_id] = @@SPID).squish
169
+ select_value sql, 'SCHEMA'
170
+ else
171
+ user_options['isolation_level']
172
+ end
173
+ end
174
+
175
+ def user_options_language
176
+ if sqlserver_azure?
177
+ select_value 'SELECT @@LANGUAGE AS [language]', 'SCHEMA'
178
+ else
179
+ user_options['language']
180
+ end
181
+ end
182
+
183
+ def newid_function
184
+ select_value 'SELECT NEWID()'
185
+ end
186
+
187
+ def newsequentialid_function
188
+ select_value 'SELECT NEWSEQUENTIALID()'
189
+ end
190
+
191
+
192
+ protected
193
+
194
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
195
+ if pk.nil?
196
+ table_name = query_requires_identity_insert?(sql)
197
+ pk = primary_key(table_name)
198
+ end
199
+ sql = if pk && self.class.use_output_inserted && !database_prefix_remote_server?
200
+ quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
201
+ sql.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk}"
202
+ else
203
+ "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
204
+ end
205
+ super
206
+ end
207
+
208
+ # === SQLServer Specific ======================================== #
209
+
210
+ def set_identity_insert(table_name, enable = true)
211
+ do_execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
212
+ rescue Exception
213
+ raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
214
+ end
215
+
216
+ # === SQLServer Specific (Executing) ============================ #
217
+
218
+ def do_execute(sql, name = 'SQL')
219
+ log(sql, name) { raw_connection_do(sql) }
220
+ end
221
+
222
+ def sp_executesql(sql, name, binds, options = {})
223
+ options[:ar_result] = true if options[:fetch] != :rows
224
+ unless without_prepared_statement?(binds)
225
+ types, params = sp_executesql_types_and_parameters(binds)
226
+ sql = sp_executesql_sql(sql, types, params, name)
227
+ end
228
+ raw_select sql, name, binds, options
229
+ end
230
+
231
+ def sp_executesql_types_and_parameters(binds)
232
+ types, params = [], []
233
+ binds.each_with_index do |attr, index|
234
+ types << "@#{index} #{sp_executesql_sql_type(attr)}"
235
+ params << sp_executesql_sql_param(attr)
236
+ end
237
+ [types, params]
238
+ end
239
+
240
+ def sp_executesql_sql_type(attr)
241
+ return attr.type.sqlserver_type if attr.type.respond_to?(:sqlserver_type)
242
+ case value = attr.value_for_database
243
+ when Numeric
244
+ 'int'.freeze
245
+ else
246
+ 'nvarchar(max)'.freeze
247
+ end
248
+ end
249
+
250
+ def sp_executesql_sql_param(attr)
251
+ case attr.value_for_database
252
+ when Type::Binary::Data,
253
+ ActiveRecord::Type::SQLServer::Data
254
+ quote(attr.value_for_database)
255
+ else
256
+ quote(type_cast(attr.value_for_database))
257
+ end
258
+ end
259
+
260
+ def sp_executesql_sql(sql, types, params, name)
261
+ if name == 'EXPLAIN'
262
+ params.each.with_index do |param, index|
263
+ substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
264
+ sql.sub! substitute_at_finder, param.to_s
265
+ end
266
+ else
267
+ types = quote(types.join(', '))
268
+ params = params.map.with_index{ |p, i| "@#{i} = #{p}" }.join(', ') # Only p is needed, but with @i helps explain regexp.
269
+ sql = "EXEC sp_executesql #{quote(sql)}"
270
+ sql << ", #{types}, #{params}" unless params.empty?
271
+ end
272
+ sql
273
+ end
274
+
275
+ def raw_connection_do(sql)
276
+ case @connection_options[:mode]
277
+ when :dblib
278
+ @connection.execute(sql).do
279
+ end
280
+ ensure
281
+ @update_sql = false
282
+ end
283
+
284
+ # === SQLServer Specific (Identity Inserts) ===================== #
285
+
286
+ def exec_insert_requires_identity?(sql, pk, binds)
287
+ query_requires_identity_insert?(sql) if pk && binds.map(&:name).include?(pk)
288
+ end
289
+
290
+ def query_requires_identity_insert?(sql)
291
+ if insert_sql?(sql)
292
+ table_name = get_table_name(sql)
293
+ id_column = identity_columns(table_name).first
294
+ id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
295
+ else
296
+ false
297
+ end
298
+ end
299
+
300
+ def insert_sql?(sql)
301
+ !(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
302
+ end
303
+
304
+ def identity_columns(table_name)
305
+ schema_cache.columns(table_name).select(&:is_identity?)
306
+ end
307
+
308
+ # === SQLServer Specific (Selecting) ============================ #
309
+
310
+ def raw_select(sql, name = 'SQL', binds = [], options = {})
311
+ log(sql, name, binds) { _raw_select(sql, options) }
312
+ end
313
+
314
+ def _raw_select(sql, options = {})
315
+ handle = raw_connection_run(sql)
316
+ handle_to_names_and_values(handle, options)
317
+ ensure
318
+ finish_statement_handle(handle)
319
+ end
320
+
321
+ def raw_connection_run(sql)
322
+ case @connection_options[:mode]
323
+ when :dblib
324
+ @connection.execute(sql)
325
+ end
326
+ end
327
+
328
+ def handle_more_results?(handle)
329
+ case @connection_options[:mode]
330
+ when :dblib
331
+ end
332
+ end
333
+
334
+ def handle_to_names_and_values(handle, options = {})
335
+ case @connection_options[:mode]
336
+ when :dblib
337
+ handle_to_names_and_values_dblib(handle, options)
338
+ end
339
+ end
340
+
341
+ def handle_to_names_and_values_dblib(handle, options = {})
342
+ query_options = {}.tap do |qo|
343
+ qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
344
+ qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
345
+ end
346
+ results = handle.each(query_options)
347
+ columns = lowercase_schema_reflection ? handle.fields.map { |c| c.downcase } : handle.fields
348
+ options[:ar_result] ? ActiveRecord::Result.new(columns, results) : results
349
+ end
350
+
351
+ def finish_statement_handle(handle)
352
+ case @connection_options[:mode]
353
+ when :dblib
354
+ handle.cancel if handle
355
+ end
356
+ handle
357
+ end
358
+
359
+ end
360
+ end
361
+ end
362
+ end