activerecord-sqlserver-adapter_new 4.2.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/CHANGELOG.md +212 -0
  4. data/CODE_OF_CONDUCT.md +31 -0
  5. data/Gemfile +61 -0
  6. data/Guardfile +29 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +201 -0
  9. data/RUNNING_UNIT_TESTS.md +121 -0
  10. data/Rakefile +48 -0
  11. data/VERSION +1 -0
  12. data/activerecord-sqlserver-adapter_new.gemspec +20 -0
  13. data/appveyor.yml +39 -0
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +27 -0
  15. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +25 -0
  16. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +40 -0
  17. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +4 -0
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +34 -0
  19. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  20. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +386 -0
  21. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +68 -0
  22. data/lib/active_record/connection_adapters/sqlserver/errors.rb +7 -0
  23. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +69 -0
  24. data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +114 -0
  25. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +52 -0
  26. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +473 -0
  27. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +66 -0
  28. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +66 -0
  29. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +22 -0
  30. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +76 -0
  31. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +57 -0
  32. data/lib/active_record/connection_adapters/sqlserver/type.rb +46 -0
  33. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +15 -0
  34. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +15 -0
  35. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +12 -0
  36. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +38 -0
  37. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +21 -0
  38. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +41 -0
  39. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +17 -0
  40. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +31 -0
  41. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +12 -0
  42. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +15 -0
  43. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +12 -0
  44. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +21 -0
  45. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +15 -0
  46. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +13 -0
  47. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +21 -0
  48. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +22 -0
  49. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +12 -0
  50. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +15 -0
  51. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +40 -0
  52. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +76 -0
  53. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +15 -0
  54. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +22 -0
  55. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +15 -0
  56. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +12 -0
  57. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +15 -0
  58. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +20 -0
  59. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +20 -0
  60. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +23 -0
  61. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +20 -0
  62. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +20 -0
  63. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +20 -0
  64. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +20 -0
  65. data/lib/active_record/connection_adapters/sqlserver/utils.rb +136 -0
  66. data/lib/active_record/connection_adapters/sqlserver/version.rb +11 -0
  67. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +405 -0
  68. data/lib/active_record/connection_adapters/sqlserver_column.rb +53 -0
  69. data/lib/active_record/sqlserver_base.rb +20 -0
  70. data/lib/active_record/tasks/sqlserver_database_tasks.rb +131 -0
  71. data/lib/activerecord-sqlserver-adapter.rb +1 -0
  72. data/lib/arel/visitors/sqlserver.rb +214 -0
  73. data/lib/arel_sqlserver.rb +3 -0
  74. data/test/appveyor/dbsetup.ps1 +27 -0
  75. data/test/appveyor/dbsetup.sql +11 -0
  76. data/test/cases/adapter_test_sqlserver.rb +444 -0
  77. data/test/cases/coerced_tests.rb +713 -0
  78. data/test/cases/column_test_sqlserver.rb +780 -0
  79. data/test/cases/connection_test_sqlserver.rb +142 -0
  80. data/test/cases/execute_procedure_test_sqlserver.rb +44 -0
  81. data/test/cases/fetch_test_sqlserver.rb +57 -0
  82. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +76 -0
  83. data/test/cases/helper_sqlserver.rb +54 -0
  84. data/test/cases/migration_test_sqlserver.rb +61 -0
  85. data/test/cases/order_test_sqlserver.rb +147 -0
  86. data/test/cases/pessimistic_locking_test_sqlserver.rb +90 -0
  87. data/test/cases/rake_test_sqlserver.rb +163 -0
  88. data/test/cases/schema_dumper_test_sqlserver.rb +198 -0
  89. data/test/cases/schema_test_sqlserver.rb +54 -0
  90. data/test/cases/scratchpad_test_sqlserver.rb +9 -0
  91. data/test/cases/showplan_test_sqlserver.rb +65 -0
  92. data/test/cases/specific_schema_test_sqlserver.rb +167 -0
  93. data/test/cases/transaction_test_sqlserver.rb +66 -0
  94. data/test/cases/utils_test_sqlserver.rb +129 -0
  95. data/test/cases/uuid_test_sqlserver.rb +48 -0
  96. data/test/config.yml +41 -0
  97. data/test/debug.rb +14 -0
  98. data/test/fixtures/1px.gif +0 -0
  99. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
  100. data/test/models/sqlserver/booking.rb +3 -0
  101. data/test/models/sqlserver/customers_view.rb +3 -0
  102. data/test/models/sqlserver/datatype.rb +3 -0
  103. data/test/models/sqlserver/datatype_migration.rb +3 -0
  104. data/test/models/sqlserver/dollar_table_name.rb +3 -0
  105. data/test/models/sqlserver/dot_table_name.rb +3 -0
  106. data/test/models/sqlserver/edge_schema.rb +13 -0
  107. data/test/models/sqlserver/fk_has_fk.rb +3 -0
  108. data/test/models/sqlserver/fk_has_pk.rb +3 -0
  109. data/test/models/sqlserver/natural_pk_data.rb +4 -0
  110. data/test/models/sqlserver/natural_pk_int_data.rb +3 -0
  111. data/test/models/sqlserver/no_pk_data.rb +3 -0
  112. data/test/models/sqlserver/object_default.rb +3 -0
  113. data/test/models/sqlserver/quoted_table.rb +7 -0
  114. data/test/models/sqlserver/quoted_view_1.rb +3 -0
  115. data/test/models/sqlserver/quoted_view_2.rb +3 -0
  116. data/test/models/sqlserver/string_default.rb +3 -0
  117. data/test/models/sqlserver/string_defaults_big_view.rb +3 -0
  118. data/test/models/sqlserver/string_defaults_view.rb +3 -0
  119. data/test/models/sqlserver/tinyint_pk.rb +3 -0
  120. data/test/models/sqlserver/upper.rb +3 -0
  121. data/test/models/sqlserver/uppered.rb +3 -0
  122. data/test/models/sqlserver/uuid.rb +3 -0
  123. data/test/schema/datatypes/2012.sql +55 -0
  124. data/test/schema/sqlserver_specific_schema.rb +207 -0
  125. data/test/support/coerceable_test_sqlserver.rb +45 -0
  126. data/test/support/connection_reflection.rb +37 -0
  127. data/test/support/load_schema_sqlserver.rb +29 -0
  128. data/test/support/minitest_sqlserver.rb +1 -0
  129. data/test/support/paths_sqlserver.rb +50 -0
  130. data/test/support/rake_helpers.rb +41 -0
  131. data/test/support/sql_counter_sqlserver.rb +32 -0
  132. metadata +253 -0
@@ -0,0 +1,121 @@
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
+ Default testing uses DBLIB with TinyTDS.
10
+
11
+ * Setup two databases in SQL Server, [activerecord_unittest] and [activerecord_unittest2]
12
+ * 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.
13
+ * $ bundle install
14
+ * $ bundle exec rake test ACTIVERECORD_UNITTEST_HOST='my.db.net'
15
+
16
+ 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.
17
+
18
+ ```
19
+ $ bundle exec rake test ONLY_SQLSERVER=1
20
+ $ bundle exec rake test ONLY_ACTIVERECORD=1
21
+
22
+ $ bundle exec rake test TEST_FILES="test/cases/adapter_test_sqlserver.rb"
23
+ $ bundle exec rake test TEST_FILES_AR="test/cases/finder_test.rb"
24
+ ```
25
+
26
+
27
+ ## Creating the test databases
28
+
29
+ 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.
30
+
31
+ The connection files make certain assumptions. For instance, the ODBC connection assumes you have a DSN setup that matches the name of the default database names. Remember too you have to set an environment variable for the DSN of the adapter, see the connection.rb file that matches your connection mode for details.
32
+
33
+ ```sql
34
+ CREATE DATABASE [activerecord_unittest];
35
+ CREATE DATABASE [activerecord_unittest2];
36
+ GO
37
+ CREATE LOGIN [rails] WITH PASSWORD = '', CHECK_POLICY = OFF, DEFAULT_DATABASE = [activerecord_unittest];
38
+ GO
39
+ USE [activerecord_unittest];
40
+ CREATE USER [rails] FOR LOGIN [rails];
41
+ GO
42
+ EXEC sp_addrolemember N'db_owner', N'rails';
43
+ EXEC master..sp_addsrvrolemember @loginame = N'rails', @rolename = N'sysadmin'
44
+ GO
45
+ ```
46
+
47
+ ## Cloning The Repos
48
+
49
+ 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.
50
+
51
+ ```
52
+ $ git clone git://github.com/rails-sqlserver/activerecord-sqlserver-adapter.git
53
+ ```
54
+
55
+ 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.
56
+
57
+ ```
58
+ $ export RAILS_VERSION='4.2.0'
59
+ $ bundle install
60
+ ```
61
+
62
+
63
+ ## Configure DB Connection
64
+
65
+ 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
66
+
67
+ ```
68
+ $ export ACTIVERECORD_UNITTEST_HOST='my.db.net' # Defaults to localhost
69
+ $ export ACTIVERECORD_UNITTEST_PORT='1533' # Defaults to 1433
70
+ ```
71
+
72
+ If you have FreeTDS installed and/or want to use a named dataserver in your freetds.conf file
73
+
74
+ ```
75
+ $ export ACTIVERECORD_UNITTEST_DATASERVER='mydbname'
76
+ ```
77
+
78
+ These can be passed down to rake too.
79
+
80
+ ```
81
+ $ bundle exec rake test ACTIVERECORD_UNITTEST_HOST='my.db.net'
82
+ ```
83
+
84
+
85
+ ## Bundling
86
+
87
+ 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. The default mode is DBLIB using TinyTDS. It is important to use bundle exec so we can wire up the ActiveRecord test libs correctly.
88
+
89
+ ```
90
+ $ bundle exec rake test
91
+ ```
92
+
93
+
94
+ ## Testing Options
95
+
96
+ The Gemfile contains groups for `:tinytds` and `:odbc`. By default it will install both gems which allows you to run the full test suite in either connection mode. If for some reason any one of these is problematic or of no concern, you could always opt out of bundling either gem with something like this.
97
+
98
+ ```
99
+ $ bundle install --without odbc
100
+ ```
101
+
102
+ You can run different connection modes using the following rake commands. Again, the DBLIB connection mode using TinyTDS is the default test task.
103
+
104
+ ```
105
+ $ bundle exec rake test:dblib
106
+ $ bundle exec rake test:odbc
107
+ ```
108
+
109
+ 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.
110
+
111
+
112
+ ## Troubleshooting
113
+
114
+ * Make sure your firewall is off or allows SQL Server traffic both ways, typically on port 1433.
115
+ * Ensure that you are running on a local admin login to create the Rails user.
116
+ * 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.
117
+
118
+
119
+ ## Current Expected Failures
120
+
121
+ * Misc Date/Time erros when using ODBC mode.
@@ -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 = false
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
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 4.2.15
@@ -0,0 +1,20 @@
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_new'
7
+ spec.version = ActiveRecord::ConnectionAdapters::SQLServer::Version::VERSION
8
+ spec.platform = Gem::Platform::RUBY
9
+ spec.license = 'MIT'
10
+ spec.authors = ['Ken Collins', 'Anna Carey', 'Will Bond', 'Murray Steele', 'Shawn Balestracci', 'Joe Rafaniello', 'Tom Ward']
11
+ spec.email = ['ken@metaskills.net', 'will@wbond.net']
12
+ spec.homepage = 'http://github.com/rails-sqlserver/activerecord-sqlserver-adapter'
13
+ spec.summary = 'ActiveRecord SQL Server Adapter.'
14
+ spec.description = 'ActiveRecord SQL Server Adapter. SQL Server 2012 and upward.'
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+ spec.add_dependency 'activerecord', '~> 4.1'
20
+ 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 TESTOPTS='-v'
6
+ - SET TINYTDS_VERSION=1.0.4
7
+ clone_depth: 5
8
+ skip_tags: true
9
+ matrix:
10
+ fast_finish: true
11
+ install:
12
+ - ps: Update-AppveyorBuild -Version "$(Get-Content $env:appveyor_build_folder\VERSION).$env:appveyor_build_number"
13
+ - ruby --version
14
+ - gem --version
15
+ - bundle install --without odbc guard
16
+ build: off
17
+ test_script:
18
+ - powershell -File "%APPVEYOR_BUILD_FOLDER%\test\appveyor\dbsetup.ps1"
19
+ - timeout /t 4 /nobreak > NUL
20
+ - ps: Start-Service 'MSSQL$SQL2014'
21
+ - timeout /t 4 /nobreak > NUL
22
+ - sqlcmd -S ".\SQL2014" -U sa -P Password12! -i %APPVEYOR_BUILD_FOLDER%\test\appveyor\dbsetup.sql
23
+ - bundle exec rake test ACTIVERECORD_UNITTEST_DATASERVER="localhost\SQL2014"
24
+ - ps: Stop-Service 'MSSQL$SQL2014'
25
+ - ps: Start-Service 'MSSQL$SQL2012SP1'
26
+ - timeout /t 4 /nobreak > NUL
27
+ - sqlcmd -S ".\SQL2012SP1" -U sa -P Password12! -i %APPVEYOR_BUILD_FOLDER%\test\appveyor\dbsetup.sql
28
+ - bundle exec rake test ACTIVERECORD_UNITTEST_DATASERVER="localhost\SQL2012SP1"
29
+ - timeout /t 4 /nobreak > NUL
30
+ - bundle exec rake test ACTIVERECORD_UNITTEST_HOST=%CI_AZURE_HOST% ACTIVERECORD_UNITTEST_PASS=%CI_AZURE_PASS% ACTIVERECORD_UNITTEST_AZURE=1
31
+ environment:
32
+ CI_AZURE_HOST:
33
+ secure: VChrioaIWkf9iuuaSs4cryiA4honrADgZqNC0++begg=
34
+ CI_AZURE_PASS:
35
+ secure: cSQp8sk4urJYvq0utpsK+r7J+snJ2wpcdp8RdXJfB+w=
36
+ matrix:
37
+ - ruby_version: "200"
38
+ - ruby_version: "22"
39
+ - ruby_version: "22-x64"
@@ -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,40 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module CoreExt
5
+ module Explain
6
+
7
+ SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '
8
+ SQLSERVER_PARAM_MATCHER = /@\d+ =/
9
+
10
+ def exec_explain(queries)
11
+ unprepared_queries = queries.map { |sql, bind| [unprepare_sqlserver_statement(sql), bind] }
12
+ super(unprepared_queries)
13
+ end
14
+
15
+ private
16
+
17
+ # This is somewhat hacky, but it should reliably reformat our prepared sql statment
18
+ # which uses sp_executesql to just the first argument, then unquote it. Likewise our
19
+ # `sp_executesql` method should substitude the @n args withe the quoted values.
20
+ def unprepare_sqlserver_statement(sql)
21
+ if sql.starts_with?(SQLSERVER_STATEMENT_PREFIX)
22
+ executesql = sql.from(SQLSERVER_STATEMENT_PREFIX.length)
23
+ executesql_args = executesql.split(', ')
24
+ found_args = executesql_args.reject! { |arg| arg =~ SQLSERVER_PARAM_MATCHER }
25
+ executesql_args.pop if found_args && executesql_args.many?
26
+ executesql = executesql_args.join(', ').strip.match(/N'(.*)'/m)[1]
27
+ Utils.unquote_string(executesql)
28
+ else
29
+ sql
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ ActiveRecord::Base.extend ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::Explain
40
+ 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,34 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module SQLServer
4
+ module CoreExt
5
+ module ODBC
6
+
7
+ module Statement
8
+
9
+ def finished?
10
+ connected?
11
+ false
12
+ rescue ::ODBC::Error
13
+ true
14
+ end
15
+
16
+ end
17
+
18
+ module Database
19
+
20
+ def run_block(*args)
21
+ yield sth = run(*args)
22
+ sth.drop
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ ODBC::Statement.send :include, ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::ODBC::Statement
34
+ ODBC::Database.send :include, ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::ODBC::Database
@@ -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,386 @@
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 = [], sqlserver_options = {})
19
+ sp_executesql(sql, name, binds)
20
+ end
21
+
22
+ def exec_insert(sql, name, binds, _pk = nil, _sequence_name = nil)
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
30
+ end
31
+
32
+ def exec_delete(sql, name, binds)
33
+ sql << '; SELECT @@ROWCOUNT AS AffectedRows'
34
+ super.rows.first.first
35
+ end
36
+
37
+ def exec_update(sql, name, binds)
38
+ sql << '; SELECT @@ROWCOUNT AS AffectedRows'
39
+ super.rows.first.first
40
+ end
41
+
42
+ def supports_statement_cache?
43
+ true
44
+ end
45
+
46
+ def begin_db_transaction
47
+ do_execute 'BEGIN TRANSACTION'
48
+ end
49
+
50
+ def transaction_isolation_levels
51
+ super.merge snapshot: "SNAPSHOT"
52
+ end
53
+
54
+ def begin_isolated_db_transaction(isolation)
55
+ set_transaction_isolation_level transaction_isolation_levels.fetch(isolation)
56
+ begin_db_transaction
57
+ end
58
+
59
+ def set_transaction_isolation_level(isolation_level)
60
+ do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
61
+ end
62
+
63
+ def commit_db_transaction
64
+ do_execute 'COMMIT TRANSACTION'
65
+ end
66
+
67
+ def exec_rollback_db_transaction
68
+ do_execute 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION'
69
+ end
70
+
71
+ include Savepoints
72
+
73
+ def create_savepoint(name = current_savepoint_name)
74
+ do_execute "SAVE TRANSACTION #{name}"
75
+ end
76
+
77
+ def exec_rollback_to_savepoint(name = current_savepoint_name)
78
+ do_execute "ROLLBACK TRANSACTION #{name}"
79
+ end
80
+
81
+ def release_savepoint(name = current_savepoint_name)
82
+ end
83
+
84
+ def case_sensitive_modifier(node, table_attribute)
85
+ node = Arel::Nodes.build_quoted node, table_attribute
86
+ Arel::Nodes::Bin.new(node)
87
+ end
88
+
89
+ def case_sensitive_comparison(table, attribute, column, value)
90
+ if column.case_sensitive?
91
+ table[attribute].eq(value)
92
+ else
93
+ super
94
+ end
95
+ end
96
+
97
+ def case_insensitive_comparison(table, attribute, column, value)
98
+ if column.case_sensitive?
99
+ super
100
+ else
101
+ table[attribute].eq(value)
102
+ end
103
+ end
104
+
105
+ # === SQLServer Specific ======================================== #
106
+
107
+ def execute_procedure(proc_name, *variables)
108
+ vars = if variables.any? && variables.first.is_a?(Hash)
109
+ variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
110
+ else
111
+ variables.map { |v| quote(v) }
112
+ end.join(', ')
113
+ sql = "EXEC #{proc_name} #{vars}".strip
114
+ name = 'Execute Procedure'
115
+ log(sql, name) do
116
+ case @connection_options[:mode]
117
+ when :dblib
118
+ result = @connection.execute(sql)
119
+ options = { as: :hash, cache_rows: true, timezone: ActiveRecord::Base.default_timezone || :utc }
120
+ result.each(options) do |row|
121
+ r = row.with_indifferent_access
122
+ yield(r) if block_given?
123
+ end
124
+ result.each.map { |row| row.is_a?(Hash) ? row.with_indifferent_access : row }
125
+ when :odbc
126
+ results = []
127
+ raw_connection_run(sql) do |handle|
128
+ get_rows = lambda do
129
+ rows = handle_to_names_and_values handle, fetch: :all
130
+ rows.each_with_index { |r, i| rows[i] = r.with_indifferent_access }
131
+ results << rows
132
+ end
133
+ get_rows.call
134
+ get_rows.call while handle_more_results?(handle)
135
+ end
136
+ results.many? ? results : results.first
137
+ end
138
+ end
139
+ end
140
+
141
+ def with_identity_insert_enabled(table_name)
142
+ table_name = quote_table_name(table_name_or_views_table_name(table_name))
143
+ set_identity_insert(table_name, true)
144
+ yield
145
+ ensure
146
+ set_identity_insert(table_name, false)
147
+ end
148
+
149
+ def use_database(database = nil)
150
+ return if sqlserver_azure?
151
+ name = SQLServer::Utils.extract_identifiers(database || @connection_options[:database]).quoted
152
+ do_execute "USE #{name}" unless name.blank?
153
+ end
154
+
155
+ def user_options
156
+ return {} if sqlserver_azure?
157
+ select_rows('dbcc useroptions', 'SCHEMA').reduce(HashWithIndifferentAccess.new) do |values, row|
158
+ if row.instance_of? Hash
159
+ set_option = row.values[0].gsub(/\s+/, '_')
160
+ user_value = row.values[1]
161
+ elsif row.instance_of? Array
162
+ set_option = row[0].gsub(/\s+/, '_')
163
+ user_value = row[1]
164
+ end
165
+ values[set_option] = user_value
166
+ values
167
+ end
168
+ end
169
+
170
+ def user_options_dateformat
171
+ if sqlserver_azure?
172
+ select_value 'SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID', 'SCHEMA'
173
+ else
174
+ user_options['dateformat']
175
+ end
176
+ end
177
+
178
+ def user_options_isolation_level
179
+ if sqlserver_azure?
180
+ sql = %(SELECT CASE [transaction_isolation_level]
181
+ WHEN 0 THEN NULL
182
+ WHEN 1 THEN 'READ UNCOMMITTED'
183
+ WHEN 2 THEN 'READ COMMITTED'
184
+ WHEN 3 THEN 'REPEATABLE READ'
185
+ WHEN 4 THEN 'SERIALIZABLE'
186
+ WHEN 5 THEN 'SNAPSHOT' END AS [isolation_level]
187
+ FROM [sys].[dm_exec_sessions]
188
+ WHERE [session_id] = @@SPID).squish
189
+ select_value sql, 'SCHEMA'
190
+ else
191
+ user_options['isolation_level']
192
+ end
193
+ end
194
+
195
+ def user_options_language
196
+ if sqlserver_azure?
197
+ select_value 'SELECT @@LANGUAGE AS [language]', 'SCHEMA'
198
+ else
199
+ user_options['language']
200
+ end
201
+ end
202
+
203
+ def newid_function
204
+ select_value 'SELECT NEWID()'
205
+ end
206
+
207
+ def newsequentialid_function
208
+ select_value 'SELECT NEWSEQUENTIALID()'
209
+ end
210
+
211
+
212
+ protected
213
+
214
+ def select(sql, name = nil, binds = [])
215
+ exec_query(sql, name, binds)
216
+ end
217
+
218
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
219
+ sql = if pk && self.class.use_output_inserted && !database_prefix_remote_server?
220
+ quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
221
+ sql.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk}"
222
+ else
223
+ "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
224
+ end
225
+ super
226
+ end
227
+
228
+ # === SQLServer Specific ======================================== #
229
+
230
+ def binds_have_identity_column?(binds)
231
+ binds.any? do |column_value|
232
+ column, value = column_value
233
+ SQLServerColumn === column && column.is_identity?
234
+ end
235
+ end
236
+
237
+ def table_name_from_binds(binds)
238
+ binds.detect { |column_value|
239
+ column, value = column_value
240
+ SQLServerColumn === column
241
+ }.try(:first).try(:table_name)
242
+ end
243
+
244
+ def set_identity_insert(table_name, enable = true)
245
+ do_execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
246
+ rescue Exception
247
+ raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
248
+ end
249
+
250
+ # === SQLServer Specific (Executing) ============================ #
251
+
252
+ def do_execute(sql, name = 'SQL')
253
+ log(sql, name) { raw_connection_do(sql) }
254
+ end
255
+
256
+ def sp_executesql(sql, name, binds, options = {})
257
+ options[:ar_result] = true if options[:fetch] != :rows
258
+ types, params = sp_executesql_types_and_parameters(binds)
259
+ sql = sp_executesql_sql(sql, types, params, name)
260
+ raw_select sql, name, binds, options
261
+ end
262
+
263
+ def sp_executesql_types_and_parameters(binds)
264
+ types, params = [], []
265
+ binds.each_with_index do |(column, value), index|
266
+ types << "@#{index} #{sp_executesql_sql_type(column, value)}"
267
+ params << quote(value, column)
268
+ end
269
+ [types, params]
270
+ end
271
+
272
+ def sp_executesql_sql_type(column, value)
273
+ return column.sql_type_for_statement if SQLServerColumn === column
274
+ if value.is_a?(Numeric)
275
+ 'int'
276
+ # We can do more here later.
277
+ else
278
+ 'nvarchar(max)'
279
+ end
280
+ end
281
+
282
+ def sp_executesql_sql(sql, types, params, name)
283
+ if name == 'EXPLAIN'
284
+ params.each.with_index do |param, index|
285
+ substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
286
+ sql.sub! substitute_at_finder, param
287
+ end
288
+ else
289
+ types = quote(types.join(', '))
290
+ params = params.map.with_index{ |p, i| "@#{i} = #{p}" }.join(', ') # Only p is needed, but with @i helps explain regexp.
291
+ sql = "EXEC sp_executesql #{quote(sql)}"
292
+ sql << ", #{types}, #{params}" unless params.empty?
293
+ end
294
+ sql
295
+ end
296
+
297
+ def raw_connection_do(sql)
298
+ case @connection_options[:mode]
299
+ when :dblib
300
+ @connection.execute(sql).do
301
+ when :odbc
302
+ @connection.do(sql)
303
+ end
304
+ ensure
305
+ @update_sql = false
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
+ when :odbc
326
+ block_given? ? @connection.run_block(sql) { |handle| yield(handle) } : @connection.run(sql)
327
+ end
328
+ end
329
+
330
+ def handle_more_results?(handle)
331
+ case @connection_options[:mode]
332
+ when :dblib
333
+ when :odbc
334
+ handle.more_results
335
+ end
336
+ end
337
+
338
+ def handle_to_names_and_values(handle, options = {})
339
+ case @connection_options[:mode]
340
+ when :dblib
341
+ handle_to_names_and_values_dblib(handle, options)
342
+ when :odbc
343
+ handle_to_names_and_values_odbc(handle, options)
344
+ end
345
+ end
346
+
347
+ def handle_to_names_and_values_dblib(handle, options = {})
348
+ query_options = {}.tap do |qo|
349
+ qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
350
+ qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
351
+ end
352
+ results = handle.each(query_options)
353
+ columns = lowercase_schema_reflection ? handle.fields.map { |c| c.downcase } : handle.fields
354
+ options[:ar_result] ? ActiveRecord::Result.new(columns, results) : results
355
+ end
356
+
357
+ def handle_to_names_and_values_odbc(handle, options = {})
358
+ @connection.use_utc = ActiveRecord::Base.default_timezone == :utc
359
+ if options[:ar_result]
360
+ columns = lowercase_schema_reflection ? handle.columns(true).map { |c| c.name.downcase } : handle.columns(true).map { |c| c.name }
361
+ rows = handle.fetch_all || []
362
+ ActiveRecord::Result.new(columns, rows)
363
+ else
364
+ case options[:fetch]
365
+ when :all
366
+ handle.each_hash || []
367
+ when :rows
368
+ handle.fetch_all || []
369
+ end
370
+ end
371
+ end
372
+
373
+ def finish_statement_handle(handle)
374
+ case @connection_options[:mode]
375
+ when :dblib
376
+ handle.cancel if handle
377
+ when :odbc
378
+ handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
379
+ end
380
+ handle
381
+ end
382
+
383
+ end
384
+ end
385
+ end
386
+ end