activerecord-sqlserver-adapter 5.2.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.editorconfig +9 -0
- data/.github/issue_template.md +23 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +29 -0
- data/.travis.yml +6 -8
- data/CHANGELOG.md +38 -24
- data/{Dockerfile → Dockerfile.ci} +1 -1
- data/Gemfile +48 -41
- data/Guardfile +9 -8
- data/README.md +9 -30
- data/RUNNING_UNIT_TESTS.md +3 -0
- data/Rakefile +14 -16
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +25 -14
- data/appveyor.yml +24 -17
- data/docker-compose.ci.yml +7 -5
- data/guides/RELEASING.md +11 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +3 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +8 -7
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +36 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +6 -4
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +88 -44
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +9 -12
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +46 -8
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +16 -5
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -7
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +190 -164
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -44
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +7 -9
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +4 -3
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
- data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/type.rb +37 -35
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -11
- data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +128 -92
- data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -5
- data/lib/active_record/sqlserver_base.rb +9 -1
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +28 -32
- data/lib/activerecord-sqlserver-adapter.rb +3 -1
- data/lib/arel/visitors/sqlserver.rb +58 -24
- data/lib/arel_sqlserver.rb +4 -2
- data/test/appveyor/dbsetup.ps1 +4 -4
- data/test/cases/adapter_test_sqlserver.rb +214 -171
- data/test/cases/change_column_null_test_sqlserver.rb +14 -12
- data/test/cases/coerced_tests.rb +631 -356
- data/test/cases/column_test_sqlserver.rb +283 -284
- data/test/cases/connection_test_sqlserver.rb +17 -20
- data/test/cases/execute_procedure_test_sqlserver.rb +20 -20
- data/test/cases/fetch_test_sqlserver.rb +16 -22
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
- data/test/cases/helper_sqlserver.rb +15 -15
- data/test/cases/in_clause_test_sqlserver.rb +36 -0
- data/test/cases/index_test_sqlserver.rb +15 -15
- data/test/cases/json_test_sqlserver.rb +25 -25
- data/test/cases/migration_test_sqlserver.rb +25 -29
- data/test/cases/order_test_sqlserver.rb +53 -54
- data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
- data/test/cases/rake_test_sqlserver.rb +33 -45
- data/test/cases/schema_dumper_test_sqlserver.rb +107 -109
- data/test/cases/schema_test_sqlserver.rb +20 -26
- data/test/cases/scratchpad_test_sqlserver.rb +4 -4
- data/test/cases/showplan_test_sqlserver.rb +28 -35
- data/test/cases/specific_schema_test_sqlserver.rb +68 -65
- data/test/cases/transaction_test_sqlserver.rb +18 -20
- data/test/cases/trigger_test_sqlserver.rb +14 -13
- data/test/cases/utils_test_sqlserver.rb +70 -70
- data/test/cases/uuid_test_sqlserver.rb +13 -14
- data/test/debug.rb +8 -6
- data/test/migrations/create_clients_and_change_column_null.rb +3 -1
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
- data/test/models/sqlserver/booking.rb +3 -1
- data/test/models/sqlserver/customers_view.rb +3 -1
- data/test/models/sqlserver/datatype.rb +2 -0
- data/test/models/sqlserver/datatype_migration.rb +2 -0
- data/test/models/sqlserver/dollar_table_name.rb +3 -1
- data/test/models/sqlserver/edge_schema.rb +3 -3
- data/test/models/sqlserver/fk_has_fk.rb +3 -1
- data/test/models/sqlserver/fk_has_pk.rb +3 -1
- data/test/models/sqlserver/natural_pk_data.rb +4 -2
- data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
- data/test/models/sqlserver/no_pk_data.rb +3 -1
- data/test/models/sqlserver/object_default.rb +3 -1
- data/test/models/sqlserver/quoted_table.rb +4 -2
- data/test/models/sqlserver/quoted_view_1.rb +3 -1
- data/test/models/sqlserver/quoted_view_2.rb +3 -1
- data/test/models/sqlserver/sst_memory.rb +3 -1
- data/test/models/sqlserver/string_default.rb +3 -1
- data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
- data/test/models/sqlserver/string_defaults_view.rb +3 -1
- data/test/models/sqlserver/tinyint_pk.rb +3 -1
- data/test/models/sqlserver/trigger.rb +4 -2
- data/test/models/sqlserver/trigger_history.rb +3 -1
- data/test/models/sqlserver/upper.rb +3 -1
- data/test/models/sqlserver/uppered.rb +3 -1
- data/test/models/sqlserver/uuid.rb +3 -1
- data/test/schema/sqlserver_specific_schema.rb +22 -22
- data/test/support/coerceable_test_sqlserver.rb +15 -9
- data/test/support/connection_reflection.rb +3 -2
- data/test/support/core_ext/query_cache.rb +4 -1
- data/test/support/load_schema_sqlserver.rb +5 -5
- data/test/support/minitest_sqlserver.rb +3 -1
- data/test/support/paths_sqlserver.rb +11 -11
- data/test/support/rake_helpers.rb +13 -10
- data/test/support/sql_counter_sqlserver.rb +3 -4
- data/test/support/test_in_memory_oltp.rb +9 -7
- metadata +17 -7
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
4
6
|
module CoreExt
|
|
5
7
|
module ActiveRecord
|
|
6
|
-
|
|
7
8
|
extend ActiveSupport::Concern
|
|
8
9
|
|
|
9
10
|
module ClassMethods
|
|
10
|
-
|
|
11
11
|
def execute_procedure(proc_name, *variables)
|
|
12
12
|
if connection.respond_to?(:execute_procedure)
|
|
13
13
|
connection.execute_procedure(proc_name, *variables)
|
|
@@ -15,9 +15,7 @@ module ActiveRecord
|
|
|
15
15
|
[]
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
|
-
|
|
19
18
|
end
|
|
20
|
-
|
|
21
19
|
end
|
|
22
20
|
end
|
|
23
21
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/attribute_methods"
|
|
2
4
|
|
|
3
5
|
module ActiveRecord
|
|
4
6
|
module ConnectionAdapters
|
|
5
7
|
module SQLServer
|
|
6
8
|
module CoreExt
|
|
7
9
|
module AttributeMethods
|
|
8
|
-
|
|
9
|
-
|
|
10
10
|
private
|
|
11
11
|
|
|
12
12
|
def attributes_for_update(attribute_names)
|
|
@@ -15,7 +15,6 @@ module ActiveRecord
|
|
|
15
15
|
column && column.respond_to?(:is_identity?) && column.is_identity?
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
|
-
|
|
19
18
|
end
|
|
20
19
|
end
|
|
21
20
|
end
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/relation"
|
|
4
|
+
require "active_record/version"
|
|
3
5
|
|
|
4
6
|
module ActiveRecord
|
|
5
7
|
module ConnectionAdapters
|
|
6
8
|
module SQLServer
|
|
7
9
|
module CoreExt
|
|
8
10
|
module Calculations
|
|
9
|
-
|
|
10
11
|
# Same as original except we don't perform PostgreSQL hack that removes ordering.
|
|
11
12
|
def calculate(operation, column_name)
|
|
12
13
|
if has_include?(column_name)
|
|
@@ -15,7 +16,7 @@ module ActiveRecord
|
|
|
15
16
|
if operation.to_s.downcase == "count"
|
|
16
17
|
unless distinct_value || distinct_select?(column_name || select_for_count)
|
|
17
18
|
relation.distinct!
|
|
18
|
-
relation.select_values = [
|
|
19
|
+
relation.select_values = [klass.primary_key || table[Arel.star]]
|
|
19
20
|
end
|
|
20
21
|
end
|
|
21
22
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
4
6
|
module CoreExt
|
|
5
7
|
module Explain
|
|
6
|
-
|
|
7
|
-
SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '.freeze
|
|
8
|
+
SQLSERVER_STATEMENT_PREFIX = "EXEC sp_executesql "
|
|
8
9
|
SQLSERVER_STATEMENT_REGEXP = /N'(.+)', N'(.+)', (.+)/
|
|
9
10
|
|
|
10
11
|
def exec_explain(queries)
|
|
@@ -32,7 +33,6 @@ module ActiveRecord
|
|
|
32
33
|
|
|
33
34
|
executesql
|
|
34
35
|
end
|
|
35
|
-
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
end
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/relation"
|
|
4
|
+
require "active_record/version"
|
|
3
5
|
|
|
4
6
|
module ActiveRecord
|
|
5
7
|
module ConnectionAdapters
|
|
6
8
|
module SQLServer
|
|
7
9
|
module CoreExt
|
|
8
10
|
module FinderMethods
|
|
9
|
-
|
|
10
11
|
private
|
|
11
12
|
|
|
12
13
|
# Same as original except we order by values in distinct select if present.
|
|
13
14
|
def construct_relation_for_exists(conditions)
|
|
14
|
-
|
|
15
|
-
relation = limit!(1)
|
|
15
|
+
conditions = sanitize_forbidden_attributes(conditions)
|
|
16
16
|
|
|
17
|
+
if distinct_value && offset_value
|
|
17
18
|
if select_values.present?
|
|
18
|
-
relation =
|
|
19
|
+
relation = order(*select_values).limit!(1)
|
|
19
20
|
else
|
|
20
|
-
relation =
|
|
21
|
+
relation = except(:order).limit!(1)
|
|
21
22
|
end
|
|
22
23
|
else
|
|
23
24
|
relation = except(:select, :distinct, :order)._select!(::ActiveRecord::FinderMethods::ONE_AS_ONE).limit!(1)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/associations/preloader"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
module ConnectionAdapters
|
|
7
|
+
module SQLServer
|
|
8
|
+
module CoreExt
|
|
9
|
+
module Preloader
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def records_for(ids)
|
|
13
|
+
ids.each_slice(in_clause_length).flat_map do |slice|
|
|
14
|
+
scope.where(association_key_name => slice).load do |record|
|
|
15
|
+
# Processing only the first owner
|
|
16
|
+
# because the record is modified but not an owner
|
|
17
|
+
owner = owners_by_key[convert_key(record[association_key_name])].first
|
|
18
|
+
association = owner.association(reflection.name)
|
|
19
|
+
association.set_inverse_instance(record)
|
|
20
|
+
end.records
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def in_clause_length
|
|
25
|
+
10_000
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
ActiveSupport.on_load(:active_record) do
|
|
34
|
+
mod = ActiveRecord::ConnectionAdapters::SQLServer::CoreExt::Preloader
|
|
35
|
+
ActiveRecord::Associations::Preloader::Association.prepend(mod)
|
|
36
|
+
end
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/relation"
|
|
4
|
+
require "active_record/version"
|
|
3
5
|
|
|
4
6
|
module ActiveRecord
|
|
5
7
|
module ConnectionAdapters
|
|
6
8
|
module SQLServer
|
|
7
9
|
module CoreExt
|
|
8
10
|
module QueryMethods
|
|
9
|
-
|
|
10
11
|
private
|
|
11
12
|
|
|
12
|
-
# Copy of original from Rails master.
|
|
13
|
+
# Copy of original from Rails master.
|
|
14
|
+
# This patch can be removed when adapter supports Rails version greater than 6.0.2.2
|
|
13
15
|
def table_name_matches?(from)
|
|
14
16
|
table_name = Regexp.escape(table.name)
|
|
15
17
|
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
@@ -9,10 +11,12 @@ module ActiveRecord
|
|
|
9
11
|
def column_name_length
|
|
10
12
|
128
|
|
11
13
|
end
|
|
14
|
+
deprecate :column_name_length
|
|
12
15
|
|
|
13
16
|
def table_name_length
|
|
14
17
|
128
|
|
15
18
|
end
|
|
19
|
+
deprecate :table_name_length
|
|
16
20
|
|
|
17
21
|
def index_name_length
|
|
18
22
|
128
|
|
@@ -21,14 +25,17 @@ module ActiveRecord
|
|
|
21
25
|
def columns_per_table
|
|
22
26
|
1024
|
|
23
27
|
end
|
|
28
|
+
deprecate :columns_per_table
|
|
24
29
|
|
|
25
30
|
def indexes_per_table
|
|
26
31
|
999
|
|
27
32
|
end
|
|
33
|
+
deprecate :indexes_per_table
|
|
28
34
|
|
|
29
35
|
def columns_per_multicolumn_index
|
|
30
36
|
16
|
|
31
37
|
end
|
|
38
|
+
deprecate :columns_per_multicolumn_index
|
|
32
39
|
|
|
33
40
|
def in_clause_length
|
|
34
41
|
10_000
|
|
@@ -37,10 +44,12 @@ module ActiveRecord
|
|
|
37
44
|
def sql_query_length
|
|
38
45
|
65_536 * 4_096
|
|
39
46
|
end
|
|
47
|
+
deprecate :sql_query_length
|
|
40
48
|
|
|
41
49
|
def joins_per_query
|
|
42
50
|
256
|
|
43
51
|
end
|
|
52
|
+
deprecate :joins_per_query
|
|
44
53
|
|
|
45
54
|
private
|
|
46
55
|
|
|
@@ -1,9 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
4
6
|
module DatabaseStatements
|
|
7
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :dbcc, :explain, :save, :select, :set, :rollback) # :nodoc:
|
|
8
|
+
private_constant :READ_QUERY
|
|
9
|
+
|
|
10
|
+
def write_query?(sql) # :nodoc:
|
|
11
|
+
!READ_QUERY.match?(sql)
|
|
12
|
+
end
|
|
5
13
|
|
|
6
14
|
def execute(sql, name = nil)
|
|
15
|
+
if preventing_writes? && write_query?(sql)
|
|
16
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
materialize_transactions
|
|
20
|
+
|
|
7
21
|
if id_insert_table_name = query_requires_identity_insert?(sql)
|
|
8
22
|
with_identity_insert_enabled(id_insert_table_name) { do_execute(sql, name) }
|
|
9
23
|
else
|
|
@@ -11,7 +25,13 @@ module ActiveRecord
|
|
|
11
25
|
end
|
|
12
26
|
end
|
|
13
27
|
|
|
14
|
-
def exec_query(sql, name =
|
|
28
|
+
def exec_query(sql, name = "SQL", binds = [], prepare: false)
|
|
29
|
+
if preventing_writes? && write_query?(sql)
|
|
30
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
materialize_transactions
|
|
34
|
+
|
|
15
35
|
sp_executesql(sql, name, binds, prepare: prepare)
|
|
16
36
|
end
|
|
17
37
|
|
|
@@ -24,17 +44,17 @@ module ActiveRecord
|
|
|
24
44
|
end
|
|
25
45
|
|
|
26
46
|
def exec_delete(sql, name, binds)
|
|
27
|
-
sql = sql.dup <<
|
|
47
|
+
sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
|
|
28
48
|
super(sql, name, binds).rows.first.first
|
|
29
49
|
end
|
|
30
50
|
|
|
31
51
|
def exec_update(sql, name, binds)
|
|
32
|
-
sql = sql.dup <<
|
|
52
|
+
sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
|
|
33
53
|
super(sql, name, binds).rows.first.first
|
|
34
54
|
end
|
|
35
55
|
|
|
36
56
|
def begin_db_transaction
|
|
37
|
-
do_execute
|
|
57
|
+
do_execute "BEGIN TRANSACTION"
|
|
38
58
|
end
|
|
39
59
|
|
|
40
60
|
def transaction_isolation_levels
|
|
@@ -51,11 +71,11 @@ module ActiveRecord
|
|
|
51
71
|
end
|
|
52
72
|
|
|
53
73
|
def commit_db_transaction
|
|
54
|
-
do_execute
|
|
74
|
+
do_execute "COMMIT TRANSACTION"
|
|
55
75
|
end
|
|
56
76
|
|
|
57
77
|
def exec_rollback_db_transaction
|
|
58
|
-
do_execute
|
|
78
|
+
do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
|
|
59
79
|
end
|
|
60
80
|
|
|
61
81
|
include Savepoints
|
|
@@ -71,9 +91,11 @@ module ActiveRecord
|
|
|
71
91
|
def release_savepoint(name = current_savepoint_name)
|
|
72
92
|
end
|
|
73
93
|
|
|
74
|
-
def case_sensitive_comparison(
|
|
94
|
+
def case_sensitive_comparison(attribute, value)
|
|
95
|
+
column = column_for_attribute(attribute)
|
|
96
|
+
|
|
75
97
|
if column.collation && !column.case_sensitive?
|
|
76
|
-
|
|
98
|
+
attribute.eq(Arel::Nodes::Bin.new(value))
|
|
77
99
|
else
|
|
78
100
|
super
|
|
79
101
|
end
|
|
@@ -89,12 +111,12 @@ module ActiveRecord
|
|
|
89
111
|
end
|
|
90
112
|
end
|
|
91
113
|
|
|
92
|
-
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}"
|
|
93
|
-
|
|
114
|
+
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}" }
|
|
115
|
+
total_sqls = Array.wrap(table_deletes + fixture_inserts)
|
|
94
116
|
|
|
95
117
|
disable_referential_integrity do
|
|
96
118
|
transaction(requires_new: true) do
|
|
97
|
-
|
|
119
|
+
total_sqls.each do |sql|
|
|
98
120
|
execute sql, "Fixtures Load"
|
|
99
121
|
yield if block_given?
|
|
100
122
|
end
|
|
@@ -107,11 +129,6 @@ module ActiveRecord
|
|
|
107
129
|
end
|
|
108
130
|
private :can_perform_case_insensitive_comparison_for?
|
|
109
131
|
|
|
110
|
-
def combine_multi_statements(total_sql)
|
|
111
|
-
total_sql
|
|
112
|
-
end
|
|
113
|
-
private :combine_multi_statements
|
|
114
|
-
|
|
115
132
|
def default_insert_value(column)
|
|
116
133
|
if column.is_identity?
|
|
117
134
|
table_name = quote(quote_table_name(column.table_name))
|
|
@@ -122,16 +139,29 @@ module ActiveRecord
|
|
|
122
139
|
end
|
|
123
140
|
private :default_insert_value
|
|
124
141
|
|
|
142
|
+
def build_insert_sql(insert) # :nodoc:
|
|
143
|
+
sql = +"INSERT #{insert.into}"
|
|
144
|
+
|
|
145
|
+
if returning = insert.send(:insert_all).returning
|
|
146
|
+
sql << " OUTPUT " << returning.map { |column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
sql << " #{insert.values_list}"
|
|
150
|
+
sql
|
|
151
|
+
end
|
|
152
|
+
|
|
125
153
|
# === SQLServer Specific ======================================== #
|
|
126
154
|
|
|
127
155
|
def execute_procedure(proc_name, *variables)
|
|
156
|
+
materialize_transactions
|
|
157
|
+
|
|
128
158
|
vars = if variables.any? && variables.first.is_a?(Hash)
|
|
129
159
|
variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
|
|
130
160
|
else
|
|
131
161
|
variables.map { |v| quote(v) }
|
|
132
|
-
end.join(
|
|
162
|
+
end.join(", ")
|
|
133
163
|
sql = "EXEC #{proc_name} #{vars}".strip
|
|
134
|
-
name =
|
|
164
|
+
name = "Execute Procedure"
|
|
135
165
|
log(sql, name) do
|
|
136
166
|
case @connection_options[:mode]
|
|
137
167
|
when :dblib
|
|
@@ -156,20 +186,22 @@ module ActiveRecord
|
|
|
156
186
|
|
|
157
187
|
def use_database(database = nil)
|
|
158
188
|
return if sqlserver_azure?
|
|
189
|
+
|
|
159
190
|
name = SQLServer::Utils.extract_identifiers(database || @connection_options[:database]).quoted
|
|
160
191
|
do_execute "USE #{name}" unless name.blank?
|
|
161
192
|
end
|
|
162
193
|
|
|
163
194
|
def user_options
|
|
164
195
|
return {} if sqlserver_azure?
|
|
165
|
-
|
|
196
|
+
|
|
197
|
+
rows = select_rows("DBCC USEROPTIONS WITH NO_INFOMSGS", "SCHEMA")
|
|
166
198
|
rows = rows.first if rows.size == 2 && rows.last.empty?
|
|
167
199
|
rows.reduce(HashWithIndifferentAccess.new) do |values, row|
|
|
168
200
|
if row.instance_of? Hash
|
|
169
|
-
set_option = row.values[0].gsub(/\s+/,
|
|
201
|
+
set_option = row.values[0].gsub(/\s+/, "_")
|
|
170
202
|
user_value = row.values[1]
|
|
171
|
-
elsif
|
|
172
|
-
set_option = row[0].gsub(/\s+/,
|
|
203
|
+
elsif row.instance_of? Array
|
|
204
|
+
set_option = row[0].gsub(/\s+/, "_")
|
|
173
205
|
user_value = row[1]
|
|
174
206
|
end
|
|
175
207
|
values[set_option] = user_value
|
|
@@ -179,9 +211,9 @@ module ActiveRecord
|
|
|
179
211
|
|
|
180
212
|
def user_options_dateformat
|
|
181
213
|
if sqlserver_azure?
|
|
182
|
-
select_value
|
|
214
|
+
select_value "SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID", "SCHEMA"
|
|
183
215
|
else
|
|
184
|
-
user_options[
|
|
216
|
+
user_options["dateformat"]
|
|
185
217
|
end
|
|
186
218
|
end
|
|
187
219
|
|
|
@@ -196,43 +228,44 @@ module ActiveRecord
|
|
|
196
228
|
WHEN 5 THEN 'SNAPSHOT' END AS [isolation_level]
|
|
197
229
|
FROM [sys].[dm_exec_sessions]
|
|
198
230
|
WHERE [session_id] = @@SPID).squish
|
|
199
|
-
select_value sql,
|
|
231
|
+
select_value sql, "SCHEMA"
|
|
200
232
|
else
|
|
201
|
-
user_options[
|
|
233
|
+
user_options["isolation_level"]
|
|
202
234
|
end
|
|
203
235
|
end
|
|
204
236
|
|
|
205
237
|
def user_options_language
|
|
206
238
|
if sqlserver_azure?
|
|
207
|
-
select_value
|
|
239
|
+
select_value "SELECT @@LANGUAGE AS [language]", "SCHEMA"
|
|
208
240
|
else
|
|
209
|
-
user_options[
|
|
241
|
+
user_options["language"]
|
|
210
242
|
end
|
|
211
243
|
end
|
|
212
244
|
|
|
213
245
|
def newid_function
|
|
214
|
-
select_value
|
|
246
|
+
select_value "SELECT NEWID()"
|
|
215
247
|
end
|
|
216
248
|
|
|
217
249
|
def newsequentialid_function
|
|
218
|
-
select_value
|
|
250
|
+
select_value "SELECT NEWSEQUENTIALID()"
|
|
219
251
|
end
|
|
220
252
|
|
|
221
|
-
|
|
222
253
|
protected
|
|
223
254
|
|
|
224
|
-
def sql_for_insert(sql, pk,
|
|
255
|
+
def sql_for_insert(sql, pk, binds)
|
|
225
256
|
if pk.nil?
|
|
226
257
|
table_name = query_requires_identity_insert?(sql)
|
|
227
258
|
pk = primary_key(table_name)
|
|
228
259
|
end
|
|
260
|
+
|
|
229
261
|
sql = if pk && use_output_inserted? && !database_prefix_remote_server?
|
|
230
262
|
quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
|
|
231
263
|
table_name ||= get_table_name(sql)
|
|
232
264
|
exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
|
|
265
|
+
|
|
233
266
|
if exclude_output_inserted
|
|
234
|
-
id_sql_type = exclude_output_inserted.is_a?(TrueClass) ?
|
|
235
|
-
|
|
267
|
+
id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? "bigint" : exclude_output_inserted
|
|
268
|
+
<<~SQL.squish
|
|
236
269
|
DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
|
|
237
270
|
#{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
|
|
238
271
|
SELECT CAST(#{quoted_pk} AS #{id_sql_type}) FROM @ssaIdInsertTable
|
|
@@ -256,7 +289,9 @@ module ActiveRecord
|
|
|
256
289
|
|
|
257
290
|
# === SQLServer Specific (Executing) ============================ #
|
|
258
291
|
|
|
259
|
-
def do_execute(sql, name =
|
|
292
|
+
def do_execute(sql, name = "SQL")
|
|
293
|
+
materialize_transactions
|
|
294
|
+
|
|
260
295
|
log(sql, name) { raw_connection_do(sql) }
|
|
261
296
|
end
|
|
262
297
|
|
|
@@ -282,11 +317,12 @@ module ActiveRecord
|
|
|
282
317
|
|
|
283
318
|
def sp_executesql_sql_type(attr)
|
|
284
319
|
return attr.type.sqlserver_type if attr.type.respond_to?(:sqlserver_type)
|
|
320
|
+
|
|
285
321
|
case value = attr.value_for_database
|
|
286
322
|
when Numeric
|
|
287
|
-
value > 2_147_483_647 ?
|
|
323
|
+
value > 2_147_483_647 ? "bigint".freeze : "int".freeze
|
|
288
324
|
else
|
|
289
|
-
|
|
325
|
+
"nvarchar(max)".freeze
|
|
290
326
|
end
|
|
291
327
|
end
|
|
292
328
|
|
|
@@ -301,16 +337,16 @@ module ActiveRecord
|
|
|
301
337
|
end
|
|
302
338
|
|
|
303
339
|
def sp_executesql_sql(sql, types, params, name)
|
|
304
|
-
if name ==
|
|
340
|
+
if name == "EXPLAIN"
|
|
305
341
|
params.each.with_index do |param, index|
|
|
306
342
|
substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
|
|
307
343
|
sql = sql.sub substitute_at_finder, param.to_s
|
|
308
344
|
end
|
|
309
345
|
else
|
|
310
|
-
types = quote(types.join(
|
|
311
|
-
params = params.map.with_index{ |p, i| "@#{i} = #{p}" }.join(
|
|
346
|
+
types = quote(types.join(", "))
|
|
347
|
+
params = params.map.with_index { |p, i| "@#{i} = #{p}" }.join(", ") # Only p is needed, but with @i helps explain regexp.
|
|
312
348
|
sql = "EXEC sp_executesql #{quote(sql)}"
|
|
313
|
-
sql
|
|
349
|
+
sql += ", #{types}, #{params}" unless params.empty?
|
|
314
350
|
end
|
|
315
351
|
sql
|
|
316
352
|
end
|
|
@@ -318,7 +354,14 @@ module ActiveRecord
|
|
|
318
354
|
def raw_connection_do(sql)
|
|
319
355
|
case @connection_options[:mode]
|
|
320
356
|
when :dblib
|
|
321
|
-
@connection.execute(sql)
|
|
357
|
+
result = @connection.execute(sql)
|
|
358
|
+
|
|
359
|
+
# TinyTDS returns false instead of raising an exception if connection fails.
|
|
360
|
+
# Getting around this by raising an exception ourselves while this PR
|
|
361
|
+
# https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
|
|
362
|
+
raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
|
|
363
|
+
|
|
364
|
+
result.do
|
|
322
365
|
end
|
|
323
366
|
ensure
|
|
324
367
|
@update_sql = false
|
|
@@ -336,8 +379,10 @@ module ActiveRecord
|
|
|
336
379
|
|
|
337
380
|
def exclude_output_inserted_table_name?(table_name, sql)
|
|
338
381
|
return false unless exclude_output_inserted_table_names?
|
|
382
|
+
|
|
339
383
|
table_name ||= get_table_name(sql)
|
|
340
384
|
return false unless table_name
|
|
385
|
+
|
|
341
386
|
self.class.exclude_output_inserted_table_names[table_name]
|
|
342
387
|
end
|
|
343
388
|
|
|
@@ -366,7 +411,7 @@ module ActiveRecord
|
|
|
366
411
|
|
|
367
412
|
# === SQLServer Specific (Selecting) ============================ #
|
|
368
413
|
|
|
369
|
-
def raw_select(sql, name =
|
|
414
|
+
def raw_select(sql, name = "SQL", binds = [], options = {})
|
|
370
415
|
log(sql, name, binds) { _raw_select(sql, options) }
|
|
371
416
|
end
|
|
372
417
|
|
|
@@ -414,7 +459,6 @@ module ActiveRecord
|
|
|
414
459
|
end
|
|
415
460
|
handle
|
|
416
461
|
end
|
|
417
|
-
|
|
418
462
|
end
|
|
419
463
|
end
|
|
420
464
|
end
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
4
6
|
module DatabaseTasks
|
|
5
|
-
|
|
6
7
|
def create_database(database, options = {})
|
|
7
8
|
name = SQLServer::Utils.extract_identifiers(database)
|
|
8
9
|
db_options = create_database_options(options)
|
|
@@ -17,7 +18,7 @@ module ActiveRecord
|
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def current_database
|
|
20
|
-
select_value
|
|
21
|
+
select_value "SELECT DB_NAME()"
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def charset
|
|
@@ -30,20 +31,20 @@ module ActiveRecord
|
|
|
30
31
|
|
|
31
32
|
private
|
|
32
33
|
|
|
33
|
-
def create_database_options(options={})
|
|
34
|
+
def create_database_options(options = {})
|
|
34
35
|
keys = [:collate]
|
|
35
36
|
copts = @connection_options
|
|
36
37
|
options = {
|
|
37
38
|
collate: copts[:collation]
|
|
38
39
|
}.merge(options.symbolize_keys).select { |_, v|
|
|
39
40
|
v.present?
|
|
40
|
-
}.slice(*keys).map { |k,v|
|
|
41
|
+
}.slice(*keys).map { |k, v|
|
|
41
42
|
"#{k.to_s.upcase} #{v}"
|
|
42
|
-
}.join(
|
|
43
|
+
}.join(" ")
|
|
43
44
|
options
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
def create_database_edition_options(options={})
|
|
47
|
+
def create_database_edition_options(options = {})
|
|
47
48
|
keys = [:maxsize, :edition, :service_objective]
|
|
48
49
|
copts = @connection_options
|
|
49
50
|
edition_options = {
|
|
@@ -52,17 +53,13 @@ module ActiveRecord
|
|
|
52
53
|
service_objective: copts[:azure_service_objective]
|
|
53
54
|
}.merge(options.symbolize_keys).select { |_, v|
|
|
54
55
|
v.present?
|
|
55
|
-
}.slice(*keys).map { |k,v|
|
|
56
|
+
}.slice(*keys).map { |k, v|
|
|
56
57
|
"#{k.to_s.upcase} = #{v}"
|
|
57
|
-
}.join(
|
|
58
|
+
}.join(", ")
|
|
58
59
|
edition_options = "( #{edition_options} )" if edition_options.present?
|
|
59
60
|
edition_options
|
|
60
61
|
end
|
|
61
|
-
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|