activerecord-sqlserver-adapter_new 4.2.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/CHANGELOG.md +212 -0
- data/CODE_OF_CONDUCT.md +31 -0
- data/Gemfile +61 -0
- data/Guardfile +29 -0
- data/MIT-LICENSE +20 -0
- data/README.md +201 -0
- data/RUNNING_UNIT_TESTS.md +121 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/activerecord-sqlserver-adapter_new.gemspec +20 -0
- data/appveyor.yml +39 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +27 -0
- 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 +40 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +4 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +34 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +386 -0
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +68 -0
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +7 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +69 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +114 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +52 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +473 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +66 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +66 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +76 -0
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +57 -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 +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +38 -0
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +41 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +31 -0
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +15 -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 +22 -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 +40 -0
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +76 -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 +136 -0
- data/lib/active_record/connection_adapters/sqlserver/version.rb +11 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +405 -0
- data/lib/active_record/connection_adapters/sqlserver_column.rb +53 -0
- data/lib/active_record/sqlserver_base.rb +20 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +131 -0
- data/lib/activerecord-sqlserver-adapter.rb +1 -0
- data/lib/arel/visitors/sqlserver.rb +214 -0
- data/lib/arel_sqlserver.rb +3 -0
- data/test/appveyor/dbsetup.ps1 +27 -0
- data/test/appveyor/dbsetup.sql +11 -0
- data/test/cases/adapter_test_sqlserver.rb +444 -0
- data/test/cases/coerced_tests.rb +713 -0
- data/test/cases/column_test_sqlserver.rb +780 -0
- data/test/cases/connection_test_sqlserver.rb +142 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +44 -0
- data/test/cases/fetch_test_sqlserver.rb +57 -0
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +76 -0
- data/test/cases/helper_sqlserver.rb +54 -0
- data/test/cases/migration_test_sqlserver.rb +61 -0
- data/test/cases/order_test_sqlserver.rb +147 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +90 -0
- data/test/cases/rake_test_sqlserver.rb +163 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +198 -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 +167 -0
- data/test/cases/transaction_test_sqlserver.rb +66 -0
- data/test/cases/utils_test_sqlserver.rb +129 -0
- data/test/cases/uuid_test_sqlserver.rb +48 -0
- data/test/config.yml +41 -0
- data/test/debug.rb +14 -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/booking.rb +3 -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/dot_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/object_default.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 +55 -0
- data/test/schema/sqlserver_specific_schema.rb +207 -0
- data/test/support/coerceable_test_sqlserver.rb +45 -0
- data/test/support/connection_reflection.rb +37 -0
- data/test/support/load_schema_sqlserver.rb +29 -0
- data/test/support/minitest_sqlserver.rb +1 -0
- data/test/support/paths_sqlserver.rb +50 -0
- data/test/support/rake_helpers.rb +41 -0
- data/test/support/sql_counter_sqlserver.rb +32 -0
- 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.
|
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 = 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
|
data/appveyor.yml
ADDED
@@ -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,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
|