activerecord-sqlserver-adapter 7.0.7 → 7.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|