activerecord-sqlserver-adapter 6.0.0.rc1 → 6.1.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/.github/workflows/ci.yml +26 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +29 -0
- data/CHANGELOG.md +18 -23
- data/Gemfile +11 -5
- data/Guardfile +9 -8
- data/README.md +32 -3
- data/RUNNING_UNIT_TESTS.md +1 -1
- data/Rakefile +12 -16
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +4 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +0 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +1 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +3 -13
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +8 -5
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +58 -43
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +7 -12
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +0 -3
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +15 -15
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -3
- data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +16 -10
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +131 -105
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +6 -8
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +25 -7
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +1 -5
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +6 -10
- data/lib/active_record/connection_adapters/sqlserver/type.rb +36 -35
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +2 -2
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/json.rb +0 -1
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +2 -3
- data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +6 -9
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +1 -3
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +1 -2
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +1 -3
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +1 -3
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +8 -11
- data/lib/active_record/connection_adapters/sqlserver/version.rb +0 -2
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +170 -136
- data/lib/active_record/connection_adapters/sqlserver_column.rb +16 -1
- data/lib/active_record/sqlserver_base.rb +9 -15
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +36 -39
- data/lib/activerecord-sqlserver-adapter.rb +1 -1
- data/lib/arel/visitors/sqlserver.rb +126 -50
- data/lib/arel_sqlserver.rb +2 -2
- data/test/cases/adapter_test_sqlserver.rb +203 -190
- data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
- data/test/cases/change_column_null_test_sqlserver.rb +12 -12
- data/test/cases/coerced_tests.rb +656 -318
- data/test/cases/column_test_sqlserver.rb +285 -284
- data/test/cases/connection_test_sqlserver.rb +15 -20
- data/test/cases/disconnected_test_sqlserver.rb +39 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +26 -19
- data/test/cases/fetch_test_sqlserver.rb +14 -22
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +12 -18
- data/test/cases/helper_sqlserver.rb +13 -15
- data/test/cases/in_clause_test_sqlserver.rb +36 -9
- data/test/cases/index_test_sqlserver.rb +13 -15
- data/test/cases/json_test_sqlserver.rb +23 -25
- data/test/cases/lateral_test_sqlserver.rb +35 -0
- data/test/cases/migration_test_sqlserver.rb +71 -26
- data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
- data/test/cases/order_test_sqlserver.rb +57 -53
- data/test/cases/pessimistic_locking_test_sqlserver.rb +25 -33
- data/test/cases/primary_keys_test_sqlserver.rb +103 -0
- data/test/cases/rake_test_sqlserver.rb +33 -46
- data/test/cases/schema_dumper_test_sqlserver.rb +121 -108
- data/test/cases/schema_test_sqlserver.rb +18 -26
- data/test/cases/scratchpad_test_sqlserver.rb +2 -4
- data/test/cases/showplan_test_sqlserver.rb +24 -33
- data/test/cases/specific_schema_test_sqlserver.rb +66 -65
- data/test/cases/transaction_test_sqlserver.rb +16 -19
- data/test/cases/trigger_test_sqlserver.rb +12 -12
- data/test/cases/utils_test_sqlserver.rb +68 -70
- data/test/cases/uuid_test_sqlserver.rb +11 -13
- data/test/debug.rb +6 -6
- data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
- data/test/migrations/create_clients_and_change_column_null.rb +1 -1
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +2 -4
- data/test/models/sqlserver/booking.rb +1 -1
- data/test/models/sqlserver/customers_view.rb +1 -1
- data/test/models/sqlserver/dollar_table_name.rb +1 -1
- data/test/models/sqlserver/edge_schema.rb +1 -3
- data/test/models/sqlserver/fk_has_fk.rb +1 -1
- data/test/models/sqlserver/fk_has_pk.rb +1 -1
- data/test/models/sqlserver/natural_pk_data.rb +2 -2
- data/test/models/sqlserver/natural_pk_int_data.rb +1 -1
- data/test/models/sqlserver/no_pk_data.rb +1 -1
- data/test/models/sqlserver/object_default.rb +1 -1
- data/test/models/sqlserver/quoted_table.rb +2 -2
- data/test/models/sqlserver/quoted_view_1.rb +1 -1
- data/test/models/sqlserver/quoted_view_2.rb +1 -1
- data/test/models/sqlserver/sst_memory.rb +1 -1
- data/test/models/sqlserver/sst_string_collation.rb +3 -0
- data/test/models/sqlserver/string_default.rb +1 -1
- data/test/models/sqlserver/string_defaults_big_view.rb +1 -1
- data/test/models/sqlserver/string_defaults_view.rb +1 -1
- data/test/models/sqlserver/tinyint_pk.rb +1 -1
- data/test/models/sqlserver/trigger.rb +2 -2
- data/test/models/sqlserver/trigger_history.rb +1 -1
- data/test/models/sqlserver/upper.rb +1 -1
- data/test/models/sqlserver/uppered.rb +1 -1
- data/test/models/sqlserver/uuid.rb +1 -1
- data/test/schema/sqlserver_specific_schema.rb +36 -21
- data/test/support/coerceable_test_sqlserver.rb +1 -4
- data/test/support/connection_reflection.rb +1 -2
- data/test/support/core_ext/query_cache.rb +1 -1
- data/test/support/load_schema_sqlserver.rb +3 -5
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
- data/test/support/minitest_sqlserver.rb +1 -1
- data/test/support/paths_sqlserver.rb +9 -11
- data/test/support/rake_helpers.rb +12 -10
- data/test/support/sql_counter_sqlserver.rb +14 -16
- data/test/support/test_in_memory_oltp.rb +7 -7
- metadata +31 -11
- data/.travis.yml +0 -23
|
@@ -5,8 +5,7 @@ module ActiveRecord
|
|
|
5
5
|
module SQLServer
|
|
6
6
|
module CoreExt
|
|
7
7
|
module Explain
|
|
8
|
-
|
|
9
|
-
SQLSERVER_STATEMENT_PREFIX = 'EXEC sp_executesql '
|
|
8
|
+
SQLSERVER_STATEMENT_PREFIX = "EXEC sp_executesql "
|
|
10
9
|
SQLSERVER_STATEMENT_REGEXP = /N'(.+)', N'(.+)', (.+)/
|
|
11
10
|
|
|
12
11
|
def exec_explain(queries)
|
|
@@ -22,19 +21,23 @@ module ActiveRecord
|
|
|
22
21
|
# which uses sp_executesql to just the first argument, then unquote it. Likewise our
|
|
23
22
|
# `sp_executesql` method should substitude the @n args with the quoted values.
|
|
24
23
|
def unprepare_sqlserver_statement(sql, binds)
|
|
25
|
-
return sql unless sql.
|
|
24
|
+
return sql unless sql.start_with?(SQLSERVER_STATEMENT_PREFIX)
|
|
26
25
|
|
|
27
26
|
executesql = sql.from(SQLSERVER_STATEMENT_PREFIX.length)
|
|
28
27
|
executesql = executesql.match(SQLSERVER_STATEMENT_REGEXP).to_a[1]
|
|
29
28
|
|
|
30
29
|
binds.each_with_index do |bind, index|
|
|
31
|
-
|
|
30
|
+
|
|
31
|
+
value = if bind.is_a?(::ActiveModel::Attribute) then
|
|
32
|
+
connection.quote(bind.value_for_database)
|
|
33
|
+
else
|
|
34
|
+
connection.quote(bind)
|
|
35
|
+
end
|
|
32
36
|
executesql = executesql.sub("@#{index}", value)
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
executesql
|
|
36
40
|
end
|
|
37
|
-
|
|
38
41
|
end
|
|
39
42
|
end
|
|
40
43
|
end
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "active_record/relation"
|
|
4
|
+
require "active_record/version"
|
|
5
5
|
|
|
6
6
|
module ActiveRecord
|
|
7
7
|
module ConnectionAdapters
|
|
8
8
|
module SQLServer
|
|
9
9
|
module CoreExt
|
|
10
10
|
module FinderMethods
|
|
11
|
-
|
|
12
11
|
private
|
|
13
12
|
|
|
14
13
|
# Same as original except we order by values in distinct select if present.
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "active_record/relation"
|
|
4
|
+
require "active_record/version"
|
|
5
5
|
|
|
6
6
|
module ActiveRecord
|
|
7
7
|
module ConnectionAdapters
|
|
8
8
|
module SQLServer
|
|
9
9
|
module CoreExt
|
|
10
10
|
module QueryMethods
|
|
11
|
-
|
|
12
11
|
private
|
|
13
12
|
|
|
14
13
|
# Copy of original from Rails master.
|
|
@@ -4,7 +4,7 @@ module ActiveRecord
|
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
module SQLServer
|
|
6
6
|
module DatabaseStatements
|
|
7
|
-
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :dbcc, :explain, :save, :select, :set, :rollback) # :nodoc:
|
|
7
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :dbcc, :explain, :save, :select, :set, :rollback, :waitfor) # :nodoc:
|
|
8
8
|
private_constant :READ_QUERY
|
|
9
9
|
|
|
10
10
|
def write_query?(sql) # :nodoc:
|
|
@@ -17,6 +17,7 @@ module ActiveRecord
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
materialize_transactions
|
|
20
|
+
mark_transaction_written_if_write(sql)
|
|
20
21
|
|
|
21
22
|
if id_insert_table_name = query_requires_identity_insert?(sql)
|
|
22
23
|
with_identity_insert_enabled(id_insert_table_name) { do_execute(sql, name) }
|
|
@@ -25,12 +26,13 @@ module ActiveRecord
|
|
|
25
26
|
end
|
|
26
27
|
end
|
|
27
28
|
|
|
28
|
-
def exec_query(sql, name =
|
|
29
|
+
def exec_query(sql, name = "SQL", binds = [], prepare: false)
|
|
29
30
|
if preventing_writes? && write_query?(sql)
|
|
30
31
|
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
materialize_transactions
|
|
35
|
+
mark_transaction_written_if_write(sql)
|
|
34
36
|
|
|
35
37
|
sp_executesql(sql, name, binds, prepare: prepare)
|
|
36
38
|
end
|
|
@@ -44,17 +46,17 @@ module ActiveRecord
|
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
def exec_delete(sql, name, binds)
|
|
47
|
-
sql = sql.dup <<
|
|
49
|
+
sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
|
|
48
50
|
super(sql, name, binds).rows.first.first
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
def exec_update(sql, name, binds)
|
|
52
|
-
sql = sql.dup <<
|
|
54
|
+
sql = sql.dup << "; SELECT @@ROWCOUNT AS AffectedRows"
|
|
53
55
|
super(sql, name, binds).rows.first.first
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
def begin_db_transaction
|
|
57
|
-
do_execute
|
|
59
|
+
do_execute "BEGIN TRANSACTION", "TRANSACTION"
|
|
58
60
|
end
|
|
59
61
|
|
|
60
62
|
def transaction_isolation_levels
|
|
@@ -67,25 +69,25 @@ module ActiveRecord
|
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
def set_transaction_isolation_level(isolation_level)
|
|
70
|
-
do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
|
|
72
|
+
do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}", "TRANSACTION"
|
|
71
73
|
end
|
|
72
74
|
|
|
73
75
|
def commit_db_transaction
|
|
74
|
-
do_execute
|
|
76
|
+
do_execute "COMMIT TRANSACTION", "TRANSACTION"
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
def exec_rollback_db_transaction
|
|
78
|
-
do_execute
|
|
80
|
+
do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION", "TRANSACTION"
|
|
79
81
|
end
|
|
80
82
|
|
|
81
83
|
include Savepoints
|
|
82
84
|
|
|
83
85
|
def create_savepoint(name = current_savepoint_name)
|
|
84
|
-
do_execute "SAVE TRANSACTION #{name}"
|
|
86
|
+
do_execute "SAVE TRANSACTION #{name}", "TRANSACTION"
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
def exec_rollback_to_savepoint(name = current_savepoint_name)
|
|
88
|
-
do_execute "ROLLBACK TRANSACTION #{name}"
|
|
90
|
+
do_execute "ROLLBACK TRANSACTION #{name}", "TRANSACTION"
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
def release_savepoint(name = current_savepoint_name)
|
|
@@ -143,7 +145,7 @@ module ActiveRecord
|
|
|
143
145
|
sql = +"INSERT #{insert.into}"
|
|
144
146
|
|
|
145
147
|
if returning = insert.send(:insert_all).returning
|
|
146
|
-
sql << " OUTPUT " << returning.map {|column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
|
|
148
|
+
sql << " OUTPUT " << returning.map { |column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
|
|
147
149
|
end
|
|
148
150
|
|
|
149
151
|
sql << " #{insert.values_list}"
|
|
@@ -159,13 +161,13 @@ module ActiveRecord
|
|
|
159
161
|
variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
|
|
160
162
|
else
|
|
161
163
|
variables.map { |v| quote(v) }
|
|
162
|
-
end.join(
|
|
164
|
+
end.join(", ")
|
|
163
165
|
sql = "EXEC #{proc_name} #{vars}".strip
|
|
164
|
-
name =
|
|
166
|
+
name = "Execute Procedure"
|
|
165
167
|
log(sql, name) do
|
|
166
168
|
case @connection_options[:mode]
|
|
167
169
|
when :dblib
|
|
168
|
-
result =
|
|
170
|
+
result = ensure_established_connection! { dblib_execute(sql) }
|
|
169
171
|
options = { as: :hash, cache_rows: true, timezone: ActiveRecord::Base.default_timezone || :utc }
|
|
170
172
|
result.each(options) do |row|
|
|
171
173
|
r = row.with_indifferent_access
|
|
@@ -186,20 +188,22 @@ module ActiveRecord
|
|
|
186
188
|
|
|
187
189
|
def use_database(database = nil)
|
|
188
190
|
return if sqlserver_azure?
|
|
191
|
+
|
|
189
192
|
name = SQLServer::Utils.extract_identifiers(database || @connection_options[:database]).quoted
|
|
190
193
|
do_execute "USE #{name}" unless name.blank?
|
|
191
194
|
end
|
|
192
195
|
|
|
193
196
|
def user_options
|
|
194
197
|
return {} if sqlserver_azure?
|
|
195
|
-
|
|
198
|
+
|
|
199
|
+
rows = select_rows("DBCC USEROPTIONS WITH NO_INFOMSGS", "SCHEMA")
|
|
196
200
|
rows = rows.first if rows.size == 2 && rows.last.empty?
|
|
197
201
|
rows.reduce(HashWithIndifferentAccess.new) do |values, row|
|
|
198
202
|
if row.instance_of? Hash
|
|
199
|
-
set_option = row.values[0].gsub(/\s+/,
|
|
203
|
+
set_option = row.values[0].gsub(/\s+/, "_")
|
|
200
204
|
user_value = row.values[1]
|
|
201
|
-
elsif
|
|
202
|
-
set_option = row[0].gsub(/\s+/,
|
|
205
|
+
elsif row.instance_of? Array
|
|
206
|
+
set_option = row[0].gsub(/\s+/, "_")
|
|
203
207
|
user_value = row[1]
|
|
204
208
|
end
|
|
205
209
|
values[set_option] = user_value
|
|
@@ -209,9 +213,9 @@ module ActiveRecord
|
|
|
209
213
|
|
|
210
214
|
def user_options_dateformat
|
|
211
215
|
if sqlserver_azure?
|
|
212
|
-
select_value
|
|
216
|
+
select_value "SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID", "SCHEMA"
|
|
213
217
|
else
|
|
214
|
-
user_options[
|
|
218
|
+
user_options["dateformat"]
|
|
215
219
|
end
|
|
216
220
|
end
|
|
217
221
|
|
|
@@ -226,29 +230,28 @@ module ActiveRecord
|
|
|
226
230
|
WHEN 5 THEN 'SNAPSHOT' END AS [isolation_level]
|
|
227
231
|
FROM [sys].[dm_exec_sessions]
|
|
228
232
|
WHERE [session_id] = @@SPID).squish
|
|
229
|
-
select_value sql,
|
|
233
|
+
select_value sql, "SCHEMA"
|
|
230
234
|
else
|
|
231
|
-
user_options[
|
|
235
|
+
user_options["isolation_level"]
|
|
232
236
|
end
|
|
233
237
|
end
|
|
234
238
|
|
|
235
239
|
def user_options_language
|
|
236
240
|
if sqlserver_azure?
|
|
237
|
-
select_value
|
|
241
|
+
select_value "SELECT @@LANGUAGE AS [language]", "SCHEMA"
|
|
238
242
|
else
|
|
239
|
-
user_options[
|
|
243
|
+
user_options["language"]
|
|
240
244
|
end
|
|
241
245
|
end
|
|
242
246
|
|
|
243
247
|
def newid_function
|
|
244
|
-
select_value
|
|
248
|
+
select_value "SELECT NEWID()"
|
|
245
249
|
end
|
|
246
250
|
|
|
247
251
|
def newsequentialid_function
|
|
248
|
-
select_value
|
|
252
|
+
select_value "SELECT NEWSEQUENTIALID()"
|
|
249
253
|
end
|
|
250
254
|
|
|
251
|
-
|
|
252
255
|
protected
|
|
253
256
|
|
|
254
257
|
def sql_for_insert(sql, pk, binds)
|
|
@@ -263,7 +266,7 @@ module ActiveRecord
|
|
|
263
266
|
exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
|
|
264
267
|
|
|
265
268
|
if exclude_output_inserted
|
|
266
|
-
id_sql_type = exclude_output_inserted.is_a?(TrueClass) ?
|
|
269
|
+
id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? "bigint" : exclude_output_inserted
|
|
267
270
|
<<~SQL.squish
|
|
268
271
|
DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
|
|
269
272
|
#{sql.dup.insert sql.index(/ (DEFAULT )?VALUES/), " OUTPUT INSERTED.#{quoted_pk} INTO @ssaIdInsertTable"}
|
|
@@ -288,8 +291,9 @@ module ActiveRecord
|
|
|
288
291
|
|
|
289
292
|
# === SQLServer Specific (Executing) ============================ #
|
|
290
293
|
|
|
291
|
-
def do_execute(sql, name =
|
|
294
|
+
def do_execute(sql, name = "SQL")
|
|
292
295
|
materialize_transactions
|
|
296
|
+
mark_transaction_written_if_write(sql)
|
|
293
297
|
|
|
294
298
|
log(sql, name) { raw_connection_do(sql) }
|
|
295
299
|
end
|
|
@@ -316,11 +320,12 @@ module ActiveRecord
|
|
|
316
320
|
|
|
317
321
|
def sp_executesql_sql_type(attr)
|
|
318
322
|
return attr.type.sqlserver_type if attr.type.respond_to?(:sqlserver_type)
|
|
323
|
+
|
|
319
324
|
case value = attr.value_for_database
|
|
320
325
|
when Numeric
|
|
321
|
-
value > 2_147_483_647 ?
|
|
326
|
+
value > 2_147_483_647 ? "bigint".freeze : "int".freeze
|
|
322
327
|
else
|
|
323
|
-
|
|
328
|
+
"nvarchar(max)".freeze
|
|
324
329
|
end
|
|
325
330
|
end
|
|
326
331
|
|
|
@@ -335,14 +340,14 @@ module ActiveRecord
|
|
|
335
340
|
end
|
|
336
341
|
|
|
337
342
|
def sp_executesql_sql(sql, types, params, name)
|
|
338
|
-
if name ==
|
|
343
|
+
if name == "EXPLAIN"
|
|
339
344
|
params.each.with_index do |param, index|
|
|
340
345
|
substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
|
|
341
346
|
sql = sql.sub substitute_at_finder, param.to_s
|
|
342
347
|
end
|
|
343
348
|
else
|
|
344
|
-
types = quote(types.join(
|
|
345
|
-
params = params.map.with_index{ |p, i| "@#{i} = #{p}" }.join(
|
|
349
|
+
types = quote(types.join(", "))
|
|
350
|
+
params = params.map.with_index { |p, i| "@#{i} = #{p}" }.join(", ") # Only p is needed, but with @i helps explain regexp.
|
|
346
351
|
sql = "EXEC sp_executesql #{quote(sql)}"
|
|
347
352
|
sql += ", #{types}, #{params}" unless params.empty?
|
|
348
353
|
end
|
|
@@ -352,13 +357,7 @@ module ActiveRecord
|
|
|
352
357
|
def raw_connection_do(sql)
|
|
353
358
|
case @connection_options[:mode]
|
|
354
359
|
when :dblib
|
|
355
|
-
result =
|
|
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
|
-
|
|
360
|
+
result = ensure_established_connection! { dblib_execute(sql) }
|
|
362
361
|
result.do
|
|
363
362
|
end
|
|
364
363
|
ensure
|
|
@@ -377,8 +376,10 @@ module ActiveRecord
|
|
|
377
376
|
|
|
378
377
|
def exclude_output_inserted_table_name?(table_name, sql)
|
|
379
378
|
return false unless exclude_output_inserted_table_names?
|
|
379
|
+
|
|
380
380
|
table_name ||= get_table_name(sql)
|
|
381
381
|
return false unless table_name
|
|
382
|
+
|
|
382
383
|
self.class.exclude_output_inserted_table_names[table_name]
|
|
383
384
|
end
|
|
384
385
|
|
|
@@ -407,7 +408,7 @@ module ActiveRecord
|
|
|
407
408
|
|
|
408
409
|
# === SQLServer Specific (Selecting) ============================ #
|
|
409
410
|
|
|
410
|
-
def raw_select(sql, name =
|
|
411
|
+
def raw_select(sql, name = "SQL", binds = [], options = {})
|
|
411
412
|
log(sql, name, binds) { _raw_select(sql, options) }
|
|
412
413
|
end
|
|
413
414
|
|
|
@@ -421,7 +422,7 @@ module ActiveRecord
|
|
|
421
422
|
def raw_connection_run(sql)
|
|
422
423
|
case @connection_options[:mode]
|
|
423
424
|
when :dblib
|
|
424
|
-
|
|
425
|
+
ensure_established_connection! { dblib_execute(sql) }
|
|
425
426
|
end
|
|
426
427
|
end
|
|
427
428
|
|
|
@@ -456,6 +457,20 @@ module ActiveRecord
|
|
|
456
457
|
handle
|
|
457
458
|
end
|
|
458
459
|
|
|
460
|
+
def dblib_execute(sql)
|
|
461
|
+
@connection.execute(sql).tap do |result|
|
|
462
|
+
# TinyTDS returns false instead of raising an exception if connection fails.
|
|
463
|
+
# Getting around this by raising an exception ourselves while this PR
|
|
464
|
+
# https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
|
|
465
|
+
raise TinyTds::Error, "failed to execute statement" if result.is_a?(FalseClass)
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def ensure_established_connection!
|
|
470
|
+
raise TinyTds::Error, 'SQL Server client is not connected' unless @connection
|
|
471
|
+
|
|
472
|
+
yield
|
|
473
|
+
end
|
|
459
474
|
end
|
|
460
475
|
end
|
|
461
476
|
end
|
|
@@ -4,7 +4,6 @@ module ActiveRecord
|
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
module SQLServer
|
|
6
6
|
module DatabaseTasks
|
|
7
|
-
|
|
8
7
|
def create_database(database, options = {})
|
|
9
8
|
name = SQLServer::Utils.extract_identifiers(database)
|
|
10
9
|
db_options = create_database_options(options)
|
|
@@ -19,7 +18,7 @@ module ActiveRecord
|
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def current_database
|
|
22
|
-
select_value
|
|
21
|
+
select_value "SELECT DB_NAME()"
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
def charset
|
|
@@ -32,20 +31,20 @@ module ActiveRecord
|
|
|
32
31
|
|
|
33
32
|
private
|
|
34
33
|
|
|
35
|
-
def create_database_options(options={})
|
|
34
|
+
def create_database_options(options = {})
|
|
36
35
|
keys = [:collate]
|
|
37
36
|
copts = @connection_options
|
|
38
37
|
options = {
|
|
39
38
|
collate: copts[:collation]
|
|
40
39
|
}.merge(options.symbolize_keys).select { |_, v|
|
|
41
40
|
v.present?
|
|
42
|
-
}.slice(*keys).map { |k,v|
|
|
41
|
+
}.slice(*keys).map { |k, v|
|
|
43
42
|
"#{k.to_s.upcase} #{v}"
|
|
44
|
-
}.join(
|
|
43
|
+
}.join(" ")
|
|
45
44
|
options
|
|
46
45
|
end
|
|
47
46
|
|
|
48
|
-
def create_database_edition_options(options={})
|
|
47
|
+
def create_database_edition_options(options = {})
|
|
49
48
|
keys = [:maxsize, :edition, :service_objective]
|
|
50
49
|
copts = @connection_options
|
|
51
50
|
edition_options = {
|
|
@@ -54,17 +53,13 @@ module ActiveRecord
|
|
|
54
53
|
service_objective: copts[:azure_service_objective]
|
|
55
54
|
}.merge(options.symbolize_keys).select { |_, v|
|
|
56
55
|
v.present?
|
|
57
|
-
}.slice(*keys).map { |k,v|
|
|
56
|
+
}.slice(*keys).map { |k, v|
|
|
58
57
|
"#{k.to_s.upcase} = #{v}"
|
|
59
|
-
}.join(
|
|
58
|
+
}.join(", ")
|
|
60
59
|
edition_options = "( #{edition_options} )" if edition_options.present?
|
|
61
60
|
edition_options
|
|
62
61
|
end
|
|
63
|
-
|
|
64
62
|
end
|
|
65
63
|
end
|
|
66
64
|
end
|
|
67
65
|
end
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|