activerecord-sqlserver-adapter 5.2.1 → 6.0.0.rc1
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/.travis.yml +6 -8
- data/CHANGELOG.md +22 -32
- data/{Dockerfile → Dockerfile.ci} +1 -1
- data/Gemfile +42 -41
- data/README.md +9 -30
- data/RUNNING_UNIT_TESTS.md +3 -0
- data/Rakefile +2 -0
- 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 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +3 -1
- 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 +6 -4
- 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 +4 -1
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +55 -14
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +38 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +16 -3
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +93 -70
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +42 -40
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -2
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +5 -2
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +7 -6
- data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -2
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +5 -4
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -3
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -3
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +44 -10
- data/lib/active_record/connection_adapters/sqlserver_column.rb +9 -3
- data/lib/active_record/sqlserver_base.rb +8 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +2 -0
- data/lib/activerecord-sqlserver-adapter.rb +2 -0
- data/lib/arel/visitors/sqlserver.rb +40 -10
- data/lib/arel_sqlserver.rb +2 -0
- data/test/appveyor/dbsetup.ps1 +4 -4
- data/test/cases/adapter_test_sqlserver.rb +65 -1
- data/test/cases/change_column_null_test_sqlserver.rb +2 -0
- data/test/cases/coerced_tests.rb +644 -187
- data/test/cases/column_test_sqlserver.rb +2 -1
- data/test/cases/connection_test_sqlserver.rb +2 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +2 -0
- data/test/cases/fetch_test_sqlserver.rb +2 -0
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +4 -2
- data/test/cases/helper_sqlserver.rb +2 -0
- data/test/cases/in_clause_test_sqlserver.rb +36 -0
- data/test/cases/index_test_sqlserver.rb +2 -0
- data/test/cases/json_test_sqlserver.rb +2 -0
- data/test/cases/migration_test_sqlserver.rb +4 -2
- data/test/cases/order_test_sqlserver.rb +2 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +2 -0
- data/test/cases/rake_test_sqlserver.rb +2 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +3 -1
- data/test/cases/schema_test_sqlserver.rb +2 -0
- data/test/cases/scratchpad_test_sqlserver.rb +2 -0
- data/test/cases/showplan_test_sqlserver.rb +4 -2
- data/test/cases/specific_schema_test_sqlserver.rb +2 -0
- data/test/cases/transaction_test_sqlserver.rb +2 -1
- data/test/cases/trigger_test_sqlserver.rb +2 -1
- data/test/cases/utils_test_sqlserver.rb +2 -0
- data/test/cases/uuid_test_sqlserver.rb +2 -1
- data/test/debug.rb +2 -0
- data/test/migrations/create_clients_and_change_column_null.rb +2 -0
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +2 -0
- data/test/models/sqlserver/booking.rb +2 -0
- data/test/models/sqlserver/customers_view.rb +2 -0
- 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 +2 -0
- data/test/models/sqlserver/edge_schema.rb +2 -0
- data/test/models/sqlserver/fk_has_fk.rb +2 -0
- data/test/models/sqlserver/fk_has_pk.rb +2 -0
- data/test/models/sqlserver/natural_pk_data.rb +2 -0
- data/test/models/sqlserver/natural_pk_int_data.rb +2 -0
- data/test/models/sqlserver/no_pk_data.rb +2 -0
- data/test/models/sqlserver/object_default.rb +2 -0
- data/test/models/sqlserver/quoted_table.rb +2 -0
- data/test/models/sqlserver/quoted_view_1.rb +2 -0
- data/test/models/sqlserver/quoted_view_2.rb +2 -0
- data/test/models/sqlserver/sst_memory.rb +2 -0
- data/test/models/sqlserver/string_default.rb +2 -0
- data/test/models/sqlserver/string_defaults_big_view.rb +2 -0
- data/test/models/sqlserver/string_defaults_view.rb +2 -0
- data/test/models/sqlserver/tinyint_pk.rb +2 -0
- data/test/models/sqlserver/trigger.rb +2 -0
- data/test/models/sqlserver/trigger_history.rb +2 -0
- data/test/models/sqlserver/upper.rb +2 -0
- data/test/models/sqlserver/uppered.rb +2 -0
- data/test/models/sqlserver/uuid.rb +2 -0
- data/test/schema/sqlserver_specific_schema.rb +2 -0
- data/test/support/coerceable_test_sqlserver.rb +14 -5
- data/test/support/connection_reflection.rb +2 -0
- data/test/support/core_ext/query_cache.rb +3 -0
- data/test/support/load_schema_sqlserver.rb +2 -0
- data/test/support/minitest_sqlserver.rb +2 -0
- data/test/support/paths_sqlserver.rb +2 -0
- data/test/support/rake_helpers.rb +1 -0
- data/test/support/sql_counter_sqlserver.rb +3 -0
- data/test/support/test_in_memory_oltp.rb +2 -0
- metadata +18 -9
data/docker-compose.ci.yml
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
version: "2.2"
|
|
2
2
|
services:
|
|
3
|
-
|
|
3
|
+
sqlserver:
|
|
4
4
|
image: metaskills/mssql-server-linux-rails
|
|
5
5
|
ci:
|
|
6
6
|
environment:
|
|
7
|
-
- ACTIVERECORD_UNITTEST_HOST=
|
|
8
|
-
build:
|
|
9
|
-
|
|
7
|
+
- ACTIVERECORD_UNITTEST_HOST=sqlserver
|
|
8
|
+
build:
|
|
9
|
+
context: .
|
|
10
|
+
dockerfile: Dockerfile.ci
|
|
11
|
+
command: wait-for sqlserver:1433 -- bundle exec rake test
|
|
10
12
|
depends_on:
|
|
11
|
-
- "
|
|
13
|
+
- "sqlserver"
|
data/guides/RELEASING.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
## Building locally
|
|
4
|
+
|
|
5
|
+
If you want to build the gem to test it locally run `bundle exec rake build`.
|
|
6
|
+
|
|
7
|
+
This command will build the gem in `pkg/activerecord-sqlserver-adapter-A.B.C.gem`, where `A.B.C` is the version in `VERSION` file.
|
|
8
|
+
|
|
9
|
+
## Releasing to RubyGems
|
|
10
|
+
|
|
11
|
+
Run `bundle exec rake release` to build the gem locally and push the `gem` file to RubyGems.
|
|
@@ -1,10 +1,12 @@
|
|
|
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
8
|
|
|
7
|
-
SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '
|
|
9
|
+
SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '
|
|
8
10
|
SQLSERVER_STATEMENT_REGEXP = /N'(.+)', N'(.+)', (.+)/
|
|
9
11
|
|
|
10
12
|
def exec_explain(queries)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_record/relation'
|
|
2
4
|
require 'active_record/version'
|
|
3
5
|
|
|
@@ -11,13 +13,13 @@ module ActiveRecord
|
|
|
11
13
|
|
|
12
14
|
# Same as original except we order by values in distinct select if present.
|
|
13
15
|
def construct_relation_for_exists(conditions)
|
|
14
|
-
|
|
15
|
-
relation = limit!(1)
|
|
16
|
+
conditions = sanitize_forbidden_attributes(conditions)
|
|
16
17
|
|
|
18
|
+
if distinct_value && offset_value
|
|
17
19
|
if select_values.present?
|
|
18
|
-
relation =
|
|
20
|
+
relation = order(*select_values).limit!(1)
|
|
19
21
|
else
|
|
20
|
-
relation =
|
|
22
|
+
relation = except(:order).limit!(1)
|
|
21
23
|
end
|
|
22
24
|
else
|
|
23
25
|
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,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_record/relation'
|
|
2
4
|
require 'active_record/version'
|
|
3
5
|
|
|
@@ -9,7 +11,8 @@ module ActiveRecord
|
|
|
9
11
|
|
|
10
12
|
private
|
|
11
13
|
|
|
12
|
-
# Copy of original from Rails master.
|
|
14
|
+
# Copy of original from Rails master.
|
|
15
|
+
# This patch can be removed when adapter supports Rails version greater than 6.0.2.2
|
|
13
16
|
def table_name_matches?(from)
|
|
14
17
|
table_name = Regexp.escape(table.name)
|
|
15
18
|
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
|
|
@@ -12,6 +26,12 @@ module ActiveRecord
|
|
|
12
26
|
end
|
|
13
27
|
|
|
14
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
|
|
|
@@ -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,9 +139,22 @@ 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
|
|
@@ -221,18 +251,20 @@ module ActiveRecord
|
|
|
221
251
|
|
|
222
252
|
protected
|
|
223
253
|
|
|
224
|
-
def sql_for_insert(sql, pk,
|
|
254
|
+
def sql_for_insert(sql, pk, binds)
|
|
225
255
|
if pk.nil?
|
|
226
256
|
table_name = query_requires_identity_insert?(sql)
|
|
227
257
|
pk = primary_key(table_name)
|
|
228
258
|
end
|
|
259
|
+
|
|
229
260
|
sql = if pk && use_output_inserted? && !database_prefix_remote_server?
|
|
230
261
|
quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
|
|
231
262
|
table_name ||= get_table_name(sql)
|
|
232
263
|
exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
|
|
264
|
+
|
|
233
265
|
if exclude_output_inserted
|
|
234
266
|
id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? 'bigint' : exclude_output_inserted
|
|
235
|
-
|
|
267
|
+
<<~SQL.squish
|
|
236
268
|
DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
|
|
237
269
|
#{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
|
|
238
270
|
SELECT CAST(#{quoted_pk} AS #{id_sql_type}) FROM @ssaIdInsertTable
|
|
@@ -257,6 +289,8 @@ module ActiveRecord
|
|
|
257
289
|
# === SQLServer Specific (Executing) ============================ #
|
|
258
290
|
|
|
259
291
|
def do_execute(sql, name = 'SQL')
|
|
292
|
+
materialize_transactions
|
|
293
|
+
|
|
260
294
|
log(sql, name) { raw_connection_do(sql) }
|
|
261
295
|
end
|
|
262
296
|
|
|
@@ -310,7 +344,7 @@ module ActiveRecord
|
|
|
310
344
|
types = quote(types.join(', '))
|
|
311
345
|
params = params.map.with_index{ |p, i| "@#{i} = #{p}" }.join(', ') # Only p is needed, but with @i helps explain regexp.
|
|
312
346
|
sql = "EXEC sp_executesql #{quote(sql)}"
|
|
313
|
-
sql
|
|
347
|
+
sql += ", #{types}, #{params}" unless params.empty?
|
|
314
348
|
end
|
|
315
349
|
sql
|
|
316
350
|
end
|
|
@@ -318,7 +352,14 @@ module ActiveRecord
|
|
|
318
352
|
def raw_connection_do(sql)
|
|
319
353
|
case @connection_options[:mode]
|
|
320
354
|
when :dblib
|
|
321
|
-
@connection.execute(sql)
|
|
355
|
+
result = @connection.execute(sql)
|
|
356
|
+
|
|
357
|
+
# TinyTDS returns false instead of raising an exception if connection fails.
|
|
358
|
+
# Getting around this by raising an exception ourselves while this PR
|
|
359
|
+
# https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
|
|
360
|
+
raise TinyTds::Error, 'failed to execute statement' if result.is_a?(FalseClass)
|
|
361
|
+
|
|
362
|
+
result.do
|
|
322
363
|
end
|
|
323
364
|
ensure
|
|
324
365
|
@update_sql = false
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
@@ -68,6 +70,42 @@ module ActiveRecord
|
|
|
68
70
|
end
|
|
69
71
|
end
|
|
70
72
|
|
|
73
|
+
def column_name_matcher
|
|
74
|
+
COLUMN_NAME
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def column_name_with_order_matcher
|
|
78
|
+
COLUMN_NAME_WITH_ORDER
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
COLUMN_NAME = /
|
|
82
|
+
\A
|
|
83
|
+
(
|
|
84
|
+
(?:
|
|
85
|
+
# [table_name].[column_name] | function(one or no argument)
|
|
86
|
+
((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
|
|
87
|
+
)
|
|
88
|
+
(?:\s+AS\s+(?:\w+|\[\w+\]))?
|
|
89
|
+
)
|
|
90
|
+
(?:\s*,\s*\g<1>)*
|
|
91
|
+
\z
|
|
92
|
+
/ix
|
|
93
|
+
|
|
94
|
+
COLUMN_NAME_WITH_ORDER = /
|
|
95
|
+
\A
|
|
96
|
+
(
|
|
97
|
+
(?:
|
|
98
|
+
# [table_name].[column_name] | function(one or no argument)
|
|
99
|
+
((?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])) | \w+\((?:|\g<2>)\)
|
|
100
|
+
)
|
|
101
|
+
(?:\s+ASC|\s+DESC)?
|
|
102
|
+
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
|
103
|
+
)
|
|
104
|
+
(?:\s*,\s*\g<1>)*
|
|
105
|
+
\z
|
|
106
|
+
/ix
|
|
107
|
+
|
|
108
|
+
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
|
71
109
|
|
|
72
110
|
private
|
|
73
111
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ActiveRecord
|
|
2
4
|
module ConnectionAdapters
|
|
3
5
|
module SQLServer
|
|
@@ -6,15 +8,26 @@ module ActiveRecord
|
|
|
6
8
|
private
|
|
7
9
|
|
|
8
10
|
def visit_TableDefinition(o)
|
|
11
|
+
if_not_exists = o.if_not_exists
|
|
12
|
+
|
|
9
13
|
if o.as
|
|
10
14
|
table_name = quote_table_name(o.temporary ? "##{o.name}" : o.name)
|
|
11
15
|
query = o.as.respond_to?(:to_sql) ? o.as.to_sql : o.as
|
|
12
16
|
projections, source = query.match(%r{SELECT\s+(.*)?\s+FROM\s+(.*)?}).captures
|
|
13
|
-
|
|
17
|
+
sql = "SELECT #{projections} INTO #{table_name} FROM #{source}"
|
|
14
18
|
else
|
|
15
19
|
o.instance_variable_set :@as, nil
|
|
16
|
-
|
|
20
|
+
o.instance_variable_set :@if_not_exists, false
|
|
21
|
+
sql = super
|
|
17
22
|
end
|
|
23
|
+
|
|
24
|
+
if if_not_exists
|
|
25
|
+
o.instance_variable_set :@if_not_exists, true
|
|
26
|
+
table_name = o.temporary ? "##{o.name}" : o.name
|
|
27
|
+
sql = "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='#{table_name}' and xtype='U') #{sql}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sql
|
|
18
31
|
end
|
|
19
32
|
|
|
20
33
|
def add_column_options!(sql, options)
|
|
@@ -34,7 +47,7 @@ module ActiveRecord
|
|
|
34
47
|
def action_sql(action, dependency)
|
|
35
48
|
case dependency
|
|
36
49
|
when :restrict
|
|
37
|
-
raise ArgumentError,
|
|
50
|
+
raise ArgumentError, <<~MSG.squish
|
|
38
51
|
'#{dependency}' is not supported for :on_update or :on_delete.
|
|
39
52
|
Supported values are: :nullify, :cascade
|
|
40
53
|
MSG
|