activerecord-sqlserver-adapter 7.0.7 → 7.1.0.beta1
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 +3 -2
- data/CHANGELOG.md +2 -94
- data/Gemfile +3 -0
- data/README.md +16 -11
- data/Rakefile +2 -6
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +1 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +42 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +4 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +10 -2
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +15 -3
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -31
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +87 -131
- data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +5 -5
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +3 -2
- data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +24 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +71 -58
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +3 -3
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +6 -0
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +4 -6
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +10 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +81 -118
- data/lib/active_record/connection_adapters/sqlserver_column.rb +1 -0
- data/lib/active_record/sqlserver_base.rb +1 -10
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -2
- data/lib/arel/visitors/sqlserver.rb +0 -33
- data/test/cases/adapter_test_sqlserver.rb +8 -7
- data/test/cases/coerced_tests.rb +558 -248
- data/test/cases/column_test_sqlserver.rb +6 -6
- data/test/cases/connection_test_sqlserver.rb +3 -6
- data/test/cases/disconnected_test_sqlserver.rb +5 -8
- data/test/cases/execute_procedure_test_sqlserver.rb +1 -1
- data/test/cases/rake_test_sqlserver.rb +1 -1
- data/test/cases/schema_dumper_test_sqlserver.rb +2 -2
- data/test/cases/view_test_sqlserver.rb +6 -10
- data/test/config.yml +1 -2
- data/test/support/connection_reflection.rb +2 -8
- data/test/support/core_ext/query_cache.rb +7 -1
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
- metadata +15 -9
@@ -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, :waitfor) # :nodoc:
|
7
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :dbcc, :explain, :save, :select, :set, :rollback, :waitfor, :use) # :nodoc:
|
8
8
|
private_constant :READ_QUERY
|
9
9
|
|
10
10
|
def write_query?(sql) # :nodoc:
|
@@ -13,40 +13,56 @@ module ActiveRecord
|
|
13
13
|
!READ_QUERY.match?(sql.b)
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
|
18
|
-
if preventing_writes? && write_query?(sql)
|
19
|
-
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
20
|
-
end
|
16
|
+
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
17
|
+
result = nil
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
19
|
+
log(sql, name, async: async) do
|
20
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
21
|
+
result = if id_insert_table_name = query_requires_identity_insert?(sql)
|
22
|
+
with_identity_insert_enabled(id_insert_table_name, conn) { internal_raw_execute(sql, conn, perform_do: true) }
|
23
|
+
else
|
24
|
+
internal_raw_execute(sql, conn, perform_do: true)
|
25
|
+
end
|
26
|
+
end
|
29
27
|
end
|
28
|
+
|
29
|
+
result
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
32
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false)
|
33
|
+
result = nil
|
33
34
|
sql = transform_query(sql)
|
34
|
-
if preventing_writes? && write_query?(sql)
|
35
|
-
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
36
|
-
end
|
37
35
|
|
38
|
-
|
36
|
+
check_if_write_query(sql)
|
39
37
|
mark_transaction_written_if_write(sql)
|
40
38
|
|
41
|
-
|
42
|
-
|
39
|
+
unless without_prepared_statement?(binds)
|
40
|
+
types, params = sp_executesql_types_and_parameters(binds)
|
41
|
+
sql = sp_executesql_sql(sql, types, params, name)
|
42
|
+
end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
log(sql, name, binds, async: async) do
|
45
|
+
with_raw_connection do |conn|
|
46
|
+
begin
|
47
|
+
options = { ar_result: true }
|
48
|
+
|
49
|
+
# TODO: Look into refactoring this.
|
50
|
+
if id_insert_table_name = query_requires_identity_insert?(sql)
|
51
|
+
with_identity_insert_enabled(id_insert_table_name, conn) do
|
52
|
+
handle = internal_raw_execute(sql, conn)
|
53
|
+
result = handle_to_names_and_values(handle, options)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
handle = internal_raw_execute(sql, conn)
|
57
|
+
result = handle_to_names_and_values(handle, options)
|
58
|
+
end
|
59
|
+
ensure
|
60
|
+
finish_statement_handle(handle)
|
61
|
+
end
|
62
|
+
end
|
49
63
|
end
|
64
|
+
|
65
|
+
result
|
50
66
|
end
|
51
67
|
|
52
68
|
def exec_delete(sql, name, binds)
|
@@ -60,7 +76,7 @@ module ActiveRecord
|
|
60
76
|
end
|
61
77
|
|
62
78
|
def begin_db_transaction
|
63
|
-
|
79
|
+
internal_execute("BEGIN TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
64
80
|
end
|
65
81
|
|
66
82
|
def transaction_isolation_levels
|
@@ -68,33 +84,20 @@ module ActiveRecord
|
|
68
84
|
end
|
69
85
|
|
70
86
|
def begin_isolated_db_transaction(isolation)
|
71
|
-
set_transaction_isolation_level
|
87
|
+
set_transaction_isolation_level(transaction_isolation_levels.fetch(isolation))
|
72
88
|
begin_db_transaction
|
73
89
|
end
|
74
90
|
|
75
91
|
def set_transaction_isolation_level(isolation_level)
|
76
|
-
|
92
|
+
internal_execute("SET TRANSACTION ISOLATION LEVEL #{isolation_level}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
77
93
|
end
|
78
94
|
|
79
95
|
def commit_db_transaction
|
80
|
-
|
96
|
+
internal_execute("COMMIT TRANSACTION", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
81
97
|
end
|
82
98
|
|
83
99
|
def exec_rollback_db_transaction
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
|
-
include Savepoints
|
88
|
-
|
89
|
-
def create_savepoint(name = current_savepoint_name)
|
90
|
-
do_execute "SAVE TRANSACTION #{name}", "TRANSACTION"
|
91
|
-
end
|
92
|
-
|
93
|
-
def exec_rollback_to_savepoint(name = current_savepoint_name)
|
94
|
-
do_execute "ROLLBACK TRANSACTION #{name}", "TRANSACTION"
|
95
|
-
end
|
96
|
-
|
97
|
-
def release_savepoint(name = current_savepoint_name)
|
100
|
+
internal_execute("IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
98
101
|
end
|
99
102
|
|
100
103
|
def case_sensitive_comparison(attribute, value)
|
@@ -164,42 +167,42 @@ module ActiveRecord
|
|
164
167
|
# === SQLServer Specific ======================================== #
|
165
168
|
|
166
169
|
def execute_procedure(proc_name, *variables)
|
167
|
-
materialize_transactions
|
168
|
-
|
169
170
|
vars = if variables.any? && variables.first.is_a?(Hash)
|
170
171
|
variables.first.map { |k, v| "@#{k} = #{quote(v)}" }
|
171
172
|
else
|
172
173
|
variables.map { |v| quote(v) }
|
173
174
|
end.join(", ")
|
174
175
|
sql = "EXEC #{proc_name} #{vars}".strip
|
175
|
-
|
176
|
-
log(sql,
|
177
|
-
|
178
|
-
|
179
|
-
result = ensure_established_connection! { dblib_execute(sql) }
|
176
|
+
|
177
|
+
log(sql, "Execute Procedure") do
|
178
|
+
with_raw_connection do |conn|
|
179
|
+
result = internal_raw_execute(sql, conn)
|
180
180
|
options = { as: :hash, cache_rows: true, timezone: ActiveRecord.default_timezone || :utc }
|
181
|
+
|
181
182
|
result.each(options) do |row|
|
182
183
|
r = row.with_indifferent_access
|
183
184
|
yield(r) if block_given?
|
184
185
|
end
|
186
|
+
|
185
187
|
result.each.map { |row| row.is_a?(Hash) ? row.with_indifferent_access : row }
|
186
188
|
end
|
187
189
|
end
|
190
|
+
|
188
191
|
end
|
189
192
|
|
190
|
-
def with_identity_insert_enabled(table_name)
|
193
|
+
def with_identity_insert_enabled(table_name, conn)
|
191
194
|
table_name = quote_table_name(table_name)
|
192
|
-
set_identity_insert(table_name, true)
|
195
|
+
set_identity_insert(table_name, conn, true)
|
193
196
|
yield
|
194
197
|
ensure
|
195
|
-
set_identity_insert(table_name, false)
|
198
|
+
set_identity_insert(table_name, conn, false)
|
196
199
|
end
|
197
200
|
|
198
201
|
def use_database(database = nil)
|
199
202
|
return if sqlserver_azure?
|
200
203
|
|
201
|
-
name = SQLServer::Utils.extract_identifiers(database || @
|
202
|
-
|
204
|
+
name = SQLServer::Utils.extract_identifiers(database || @connection_parameters[:database]).quoted
|
205
|
+
execute("USE #{name}", "SCHEMA") unless name.blank?
|
203
206
|
end
|
204
207
|
|
205
208
|
def user_options
|
@@ -263,18 +266,19 @@ module ActiveRecord
|
|
263
266
|
|
264
267
|
protected
|
265
268
|
|
266
|
-
def sql_for_insert(sql, pk, binds)
|
269
|
+
def sql_for_insert(sql, pk, binds, returning)
|
267
270
|
if pk.nil?
|
268
271
|
table_name = query_requires_identity_insert?(sql)
|
269
272
|
pk = primary_key(table_name)
|
270
273
|
end
|
271
274
|
|
272
275
|
sql = if pk && use_output_inserted? && !database_prefix_remote_server?
|
273
|
-
quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
|
274
276
|
table_name ||= get_table_name(sql)
|
275
277
|
exclude_output_inserted = exclude_output_inserted_table_name?(table_name, sql)
|
276
278
|
|
277
279
|
if exclude_output_inserted
|
280
|
+
quoted_pk = SQLServer::Utils.extract_identifiers(pk).quoted
|
281
|
+
|
278
282
|
id_sql_type = exclude_output_inserted.is_a?(TrueClass) ? "bigint" : exclude_output_inserted
|
279
283
|
<<~SQL.squish
|
280
284
|
DECLARE @ssaIdInsertTable table (#{quoted_pk} #{id_sql_type});
|
@@ -282,40 +286,32 @@ module ActiveRecord
|
|
282
286
|
SELECT CAST(#{quoted_pk} AS #{id_sql_type}) FROM @ssaIdInsertTable
|
283
287
|
SQL
|
284
288
|
else
|
285
|
-
|
289
|
+
returning_columns = returning || Array(pk)
|
290
|
+
|
291
|
+
if returning_columns.any?
|
292
|
+
returning_columns_statements = returning_columns.map { |c| " INSERTED.#{SQLServer::Utils.extract_identifiers(c).quoted}" }
|
293
|
+
sql.dup.insert sql.index(/ (DEFAULT )?VALUES/i), " OUTPUT" + returning_columns_statements.join(",")
|
294
|
+
else
|
295
|
+
sql
|
296
|
+
end
|
286
297
|
end
|
287
298
|
else
|
288
299
|
"#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
|
289
300
|
end
|
290
|
-
|
301
|
+
|
302
|
+
[sql, binds]
|
291
303
|
end
|
292
304
|
|
293
305
|
# === SQLServer Specific ======================================== #
|
294
306
|
|
295
|
-
def set_identity_insert(table_name, enable
|
296
|
-
|
307
|
+
def set_identity_insert(table_name, conn, enable)
|
308
|
+
internal_raw_execute("SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}", conn , perform_do: true)
|
297
309
|
rescue Exception
|
298
310
|
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
|
299
311
|
end
|
300
312
|
|
301
313
|
# === SQLServer Specific (Executing) ============================ #
|
302
314
|
|
303
|
-
def do_execute(sql, name = "SQL")
|
304
|
-
materialize_transactions
|
305
|
-
mark_transaction_written_if_write(sql)
|
306
|
-
|
307
|
-
log(sql, name) { raw_connection_do(sql) }
|
308
|
-
end
|
309
|
-
|
310
|
-
def sp_executesql(sql, name, binds, options = {})
|
311
|
-
options[:ar_result] = true if options[:fetch] != :rows
|
312
|
-
unless without_prepared_statement?(binds)
|
313
|
-
types, params = sp_executesql_types_and_parameters(binds)
|
314
|
-
sql = sp_executesql_sql(sql, types, params, name)
|
315
|
-
end
|
316
|
-
raw_select sql, name, binds, options
|
317
|
-
end
|
318
|
-
|
319
315
|
def sp_executesql_types_and_parameters(binds)
|
320
316
|
types, params = [], []
|
321
317
|
binds.each_with_index do |attr, index|
|
@@ -328,6 +324,7 @@ module ActiveRecord
|
|
328
324
|
end
|
329
325
|
|
330
326
|
def sp_executesql_sql_type(attr)
|
327
|
+
return "nvarchar(max)".freeze if attr.is_a?(Symbol)
|
331
328
|
return attr.type.sqlserver_type if attr.type.respond_to?(:sqlserver_type)
|
332
329
|
|
333
330
|
case value = attr.value_for_database
|
@@ -339,6 +336,8 @@ module ActiveRecord
|
|
339
336
|
end
|
340
337
|
|
341
338
|
def sp_executesql_sql_param(attr)
|
339
|
+
return quote(attr) if attr.is_a?(Symbol)
|
340
|
+
|
342
341
|
case value = attr.value_for_database
|
343
342
|
when Type::Binary::Data,
|
344
343
|
ActiveRecord::Type::SQLServer::Data
|
@@ -363,16 +362,6 @@ module ActiveRecord
|
|
363
362
|
sql.freeze
|
364
363
|
end
|
365
364
|
|
366
|
-
def raw_connection_do(sql)
|
367
|
-
case @connection_options[:mode]
|
368
|
-
when :dblib
|
369
|
-
result = ensure_established_connection! { dblib_execute(sql) }
|
370
|
-
result.do
|
371
|
-
end
|
372
|
-
ensure
|
373
|
-
@update_sql = false
|
374
|
-
end
|
375
|
-
|
376
365
|
# === SQLServer Specific (Identity Inserts) ===================== #
|
377
366
|
|
378
367
|
def use_output_inserted?
|
@@ -392,10 +381,6 @@ module ActiveRecord
|
|
392
381
|
self.class.exclude_output_inserted_table_names[table_name]
|
393
382
|
end
|
394
383
|
|
395
|
-
def exec_insert_requires_identity?(sql, _pk, _binds)
|
396
|
-
query_requires_identity_insert?(sql)
|
397
|
-
end
|
398
|
-
|
399
384
|
def query_requires_identity_insert?(sql)
|
400
385
|
return false unless insert_sql?(sql)
|
401
386
|
|
@@ -415,68 +400,39 @@ module ActiveRecord
|
|
415
400
|
|
416
401
|
# === SQLServer Specific (Selecting) ============================ #
|
417
402
|
|
418
|
-
def
|
419
|
-
|
420
|
-
end
|
403
|
+
def _raw_select(sql, conn, options = {})
|
404
|
+
handle = internal_raw_execute(sql, conn)
|
421
405
|
|
422
|
-
def _raw_select(sql, options = {})
|
423
|
-
handle = raw_connection_run(sql)
|
424
406
|
handle_to_names_and_values(handle, options)
|
425
407
|
ensure
|
426
408
|
finish_statement_handle(handle)
|
427
409
|
end
|
428
410
|
|
429
|
-
def raw_connection_run(sql)
|
430
|
-
case @connection_options[:mode]
|
431
|
-
when :dblib
|
432
|
-
ensure_established_connection! { dblib_execute(sql) }
|
433
|
-
end
|
434
|
-
end
|
435
|
-
|
436
|
-
def handle_more_results?(handle)
|
437
|
-
case @connection_options[:mode]
|
438
|
-
when :dblib
|
439
|
-
end
|
440
|
-
end
|
441
|
-
|
442
411
|
def handle_to_names_and_values(handle, options = {})
|
443
|
-
case @connection_options[:mode]
|
444
|
-
when :dblib
|
445
|
-
handle_to_names_and_values_dblib(handle, options)
|
446
|
-
end
|
447
|
-
end
|
448
|
-
|
449
|
-
def handle_to_names_and_values_dblib(handle, options = {})
|
450
412
|
query_options = {}.tap do |qo|
|
451
413
|
qo[:timezone] = ActiveRecord.default_timezone || :utc
|
452
414
|
qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
|
453
415
|
end
|
454
416
|
results = handle.each(query_options)
|
455
417
|
columns = lowercase_schema_reflection ? handle.fields.map { |c| c.downcase } : handle.fields
|
418
|
+
|
456
419
|
options[:ar_result] ? ActiveRecord::Result.new(columns, results) : results
|
457
420
|
end
|
458
421
|
|
459
422
|
def finish_statement_handle(handle)
|
460
|
-
|
461
|
-
when :dblib
|
462
|
-
handle.cancel if handle
|
463
|
-
end
|
423
|
+
handle.cancel if handle
|
464
424
|
handle
|
465
425
|
end
|
466
426
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
raise TinyTds::Error, "failed to execute statement" if
|
427
|
+
# TinyTDS returns false instead of raising an exception if connection fails.
|
428
|
+
# Getting around this by raising an exception ourselves while PR
|
429
|
+
# https://github.com/rails-sqlserver/tiny_tds/pull/469 is not released.
|
430
|
+
def internal_raw_execute(sql, conn, perform_do: false)
|
431
|
+
result = conn.execute(sql).tap do |_result|
|
432
|
+
raise TinyTds::Error, "failed to execute statement" if _result.is_a?(FalseClass)
|
473
433
|
end
|
474
|
-
end
|
475
434
|
|
476
|
-
|
477
|
-
raise TinyTds::Error, 'SQL Server client is not connected' unless @connection
|
478
|
-
|
479
|
-
yield
|
435
|
+
perform_do ? result.do : result
|
480
436
|
end
|
481
437
|
end
|
482
438
|
end
|
@@ -8,13 +8,13 @@ module ActiveRecord
|
|
8
8
|
name = SQLServer::Utils.extract_identifiers(database)
|
9
9
|
db_options = create_database_options(options)
|
10
10
|
edition_options = create_database_edition_options(options)
|
11
|
-
|
11
|
+
execute "CREATE DATABASE #{name} #{db_options} #{edition_options}"
|
12
12
|
end
|
13
13
|
|
14
14
|
def drop_database(database)
|
15
15
|
name = SQLServer::Utils.extract_identifiers(database)
|
16
|
-
|
17
|
-
|
16
|
+
execute "ALTER DATABASE #{name} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
|
17
|
+
execute "DROP DATABASE #{name}"
|
18
18
|
end
|
19
19
|
|
20
20
|
def current_database
|
@@ -33,7 +33,7 @@ module ActiveRecord
|
|
33
33
|
|
34
34
|
def create_database_options(options = {})
|
35
35
|
keys = [:collate]
|
36
|
-
copts = @
|
36
|
+
copts = @connection_parameters
|
37
37
|
options = {
|
38
38
|
collate: copts[:collation]
|
39
39
|
}.merge(options.symbolize_keys).select { |_, v|
|
@@ -46,7 +46,7 @@ module ActiveRecord
|
|
46
46
|
|
47
47
|
def create_database_edition_options(options = {})
|
48
48
|
keys = [:maxsize, :edition, :service_objective]
|
49
|
-
copts = @
|
49
|
+
copts = @connection_parameters
|
50
50
|
edition_options = {
|
51
51
|
maxsize: copts[:azure_maxsize],
|
52
52
|
edition: copts[:azure_edition],
|
@@ -85,7 +85,7 @@ module ActiveRecord
|
|
85
85
|
(
|
86
86
|
(?:
|
87
87
|
# [database_name].[database_owner].[table_name].[column_name] | function(one or no argument)
|
88
|
-
((?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])
|
88
|
+
((?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\]) | \w+\((?:|\g<2>)\))
|
89
89
|
)
|
90
90
|
(?:\s+AS\s+(?:\w+|\[\w+\]))?
|
91
91
|
)
|
@@ -98,8 +98,9 @@ module ActiveRecord
|
|
98
98
|
(
|
99
99
|
(?:
|
100
100
|
# [database_name].[database_owner].[table_name].[column_name] | function(one or no argument)
|
101
|
-
((?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\])
|
101
|
+
((?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+\.|\[\w+\]\.)?(?:\w+|\[\w+\]) | \w+\((?:|\g<2>)\))
|
102
102
|
)
|
103
|
+
(?:\s+COLLATE\s+\w+)?
|
103
104
|
(?:\s+ASC|\s+DESC)?
|
104
105
|
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
105
106
|
)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module SQLServer
|
6
|
+
module Savepoints
|
7
|
+
def current_savepoint_name
|
8
|
+
current_transaction.savepoint_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_savepoint(name = current_savepoint_name)
|
12
|
+
internal_execute("SAVE TRANSACTION #{name}", "TRANSACTION")
|
13
|
+
end
|
14
|
+
|
15
|
+
def exec_rollback_to_savepoint(name = current_savepoint_name)
|
16
|
+
internal_execute("ROLLBACK TRANSACTION #{name}", "TRANSACTION")
|
17
|
+
end
|
18
|
+
|
19
|
+
def release_savepoint(_name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|