activerecord-sqlserver-adapter 6.1.0.0 → 7.0.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 +4 -1
- data/CHANGELOG.md +12 -23
- data/Gemfile +1 -0
- data/MIT-LICENSE +1 -1
- data/README.md +31 -16
- data/VERSION +1 -1
- data/activerecord-sqlserver-adapter.gemspec +2 -2
- data/appveyor.yml +4 -6
- 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 +5 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +7 -13
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +15 -6
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +4 -5
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +28 -9
- data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +14 -5
- data/lib/active_record/connection_adapters/sqlserver/type/data.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +3 -2
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +16 -1
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +99 -76
- data/lib/active_record/connection_adapters/sqlserver_column.rb +74 -35
- data/lib/arel/visitors/sqlserver.rb +17 -2
- data/test/cases/adapter_test_sqlserver.rb +10 -2
- data/test/cases/coerced_tests.rb +314 -85
- data/test/cases/column_test_sqlserver.rb +62 -58
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
- data/test/cases/fetch_test_sqlserver.rb +18 -0
- data/test/cases/rake_test_sqlserver.rb +36 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +2 -2
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +1 -1
- data/test/models/sqlserver/composite_pk.rb +9 -0
- data/test/schema/sqlserver_specific_schema.rb +18 -0
- data/test/support/coerceable_test_sqlserver.rb +4 -4
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
- data/test/support/rake_helpers.rb +3 -1
- metadata +18 -15
- data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -28
- 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
@@ -10,7 +10,6 @@ require "active_record/connection_adapters/sqlserver/core_ext/explain"
|
|
10
10
|
require "active_record/connection_adapters/sqlserver/core_ext/explain_subscriber"
|
11
11
|
require "active_record/connection_adapters/sqlserver/core_ext/attribute_methods"
|
12
12
|
require "active_record/connection_adapters/sqlserver/core_ext/finder_methods"
|
13
|
-
require "active_record/connection_adapters/sqlserver/core_ext/query_methods"
|
14
13
|
require "active_record/connection_adapters/sqlserver/core_ext/preloader"
|
15
14
|
require "active_record/connection_adapters/sqlserver/version"
|
16
15
|
require "active_record/connection_adapters/sqlserver/type"
|
@@ -106,7 +105,23 @@ module ActiveRecord
|
|
106
105
|
end
|
107
106
|
|
108
107
|
def config_appname(config)
|
109
|
-
|
108
|
+
if instance_methods.include?(:configure_application_name)
|
109
|
+
ActiveSupport::Deprecation.warn <<~MSG.squish
|
110
|
+
Configuring the application name used by TinyTDS by overriding the
|
111
|
+
`ActiveRecord::ConnectionAdapters::SQLServerAdapter#configure_application_name`
|
112
|
+
instance method is no longer supported. The application name should configured
|
113
|
+
using the `appname` setting in the `database.yml` file instead. Consult the
|
114
|
+
README for further information."
|
115
|
+
MSG
|
116
|
+
end
|
117
|
+
|
118
|
+
config[:appname] || rails_application_name
|
119
|
+
end
|
120
|
+
|
121
|
+
def rails_application_name
|
122
|
+
Rails.application.class.name.split("::").first
|
123
|
+
rescue
|
124
|
+
nil # Might not be in a Rails context so we fallback to `nil`.
|
110
125
|
end
|
111
126
|
|
112
127
|
def config_login_timeout(config)
|
@@ -362,97 +377,107 @@ module ActiveRecord
|
|
362
377
|
version_year
|
363
378
|
end
|
364
379
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
380
|
+
class << self
|
381
|
+
protected
|
382
|
+
|
383
|
+
def initialize_type_map(m)
|
384
|
+
m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
|
385
|
+
|
386
|
+
# Exact Numerics
|
387
|
+
register_class_with_limit m, "bigint(8)", SQLServer::Type::BigInteger
|
388
|
+
m.alias_type "bigint", "bigint(8)"
|
389
|
+
register_class_with_limit m, "int(4)", SQLServer::Type::Integer
|
390
|
+
m.alias_type "integer", "int(4)"
|
391
|
+
m.alias_type "int", "int(4)"
|
392
|
+
register_class_with_limit m, "smallint(2)", SQLServer::Type::SmallInteger
|
393
|
+
m.alias_type "smallint", "smallint(2)"
|
394
|
+
register_class_with_limit m, "tinyint(1)", SQLServer::Type::TinyInteger
|
395
|
+
m.alias_type "tinyint", "tinyint(1)"
|
396
|
+
m.register_type "bit", SQLServer::Type::Boolean.new
|
397
|
+
m.register_type %r{\Adecimal}i do |sql_type|
|
398
|
+
scale = extract_scale(sql_type)
|
399
|
+
precision = extract_precision(sql_type)
|
400
|
+
if scale == 0
|
401
|
+
SQLServer::Type::DecimalWithoutScale.new(precision: precision)
|
402
|
+
else
|
403
|
+
SQLServer::Type::Decimal.new(precision: precision, scale: scale)
|
404
|
+
end
|
390
405
|
end
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
406
|
+
m.alias_type %r{\Anumeric}i, "decimal"
|
407
|
+
m.register_type "money", SQLServer::Type::Money.new
|
408
|
+
m.register_type "smallmoney", SQLServer::Type::SmallMoney.new
|
409
|
+
|
410
|
+
# Approximate Numerics
|
411
|
+
m.register_type "float", SQLServer::Type::Float.new
|
412
|
+
m.register_type "real", SQLServer::Type::Real.new
|
413
|
+
|
414
|
+
# Date and Time
|
415
|
+
m.register_type "date", SQLServer::Type::Date.new
|
416
|
+
m.register_type %r{\Adatetime} do |sql_type|
|
417
|
+
precision = extract_precision(sql_type)
|
418
|
+
if precision
|
419
|
+
SQLServer::Type::DateTime2.new precision: precision
|
420
|
+
else
|
421
|
+
SQLServer::Type::DateTime.new
|
422
|
+
end
|
408
423
|
end
|
424
|
+
m.register_type %r{\Adatetimeoffset}i do |sql_type|
|
425
|
+
precision = extract_precision(sql_type)
|
426
|
+
SQLServer::Type::DateTimeOffset.new precision: precision
|
427
|
+
end
|
428
|
+
m.register_type "smalldatetime", SQLServer::Type::SmallDateTime.new
|
429
|
+
m.register_type %r{\Atime}i do |sql_type|
|
430
|
+
precision = extract_precision(sql_type) || DEFAULT_TIME_PRECISION
|
431
|
+
SQLServer::Type::Time.new precision: precision
|
432
|
+
end
|
433
|
+
|
434
|
+
# Character Strings
|
435
|
+
register_class_with_limit m, %r{\Achar}i, SQLServer::Type::Char
|
436
|
+
register_class_with_limit m, %r{\Avarchar}i, SQLServer::Type::Varchar
|
437
|
+
m.register_type "varchar(max)", SQLServer::Type::VarcharMax.new
|
438
|
+
m.register_type "text", SQLServer::Type::Text.new
|
439
|
+
|
440
|
+
# Unicode Character Strings
|
441
|
+
register_class_with_limit m, %r{\Anchar}i, SQLServer::Type::UnicodeChar
|
442
|
+
register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
|
443
|
+
m.alias_type "string", "nvarchar(4000)"
|
444
|
+
m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
|
445
|
+
m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
|
446
|
+
m.register_type "ntext", SQLServer::Type::UnicodeText.new
|
447
|
+
|
448
|
+
# Binary Strings
|
449
|
+
register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary
|
450
|
+
register_class_with_limit m, %r{\Avarbinary}i, SQLServer::Type::Varbinary
|
451
|
+
m.register_type "varbinary(max)", SQLServer::Type::VarbinaryMax.new
|
452
|
+
|
453
|
+
# Other Data Types
|
454
|
+
m.register_type "uniqueidentifier", SQLServer::Type::Uuid.new
|
455
|
+
m.register_type "timestamp", SQLServer::Type::Timestamp.new
|
409
456
|
end
|
410
|
-
|
411
|
-
precision = extract_precision(sql_type)
|
412
|
-
SQLServer::Type::DateTimeOffset.new precision: precision
|
413
|
-
end
|
414
|
-
m.register_type "smalldatetime", SQLServer::Type::SmallDateTime.new
|
415
|
-
m.register_type %r{\Atime}i do |sql_type|
|
416
|
-
precision = extract_precision(sql_type) || DEFAULT_TIME_PRECISION
|
417
|
-
SQLServer::Type::Time.new precision: precision
|
418
|
-
end
|
457
|
+
end
|
419
458
|
|
420
|
-
|
421
|
-
register_class_with_limit m, %r{\Achar}i, SQLServer::Type::Char
|
422
|
-
register_class_with_limit m, %r{\Avarchar}i, SQLServer::Type::Varchar
|
423
|
-
m.register_type "varchar(max)", SQLServer::Type::VarcharMax.new
|
424
|
-
m.register_type "text", SQLServer::Type::Text.new
|
459
|
+
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
|
425
460
|
|
426
|
-
|
427
|
-
register_class_with_limit m, %r{\Anchar}i, SQLServer::Type::UnicodeChar
|
428
|
-
register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
|
429
|
-
m.alias_type "string", "nvarchar(4000)"
|
430
|
-
m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
|
431
|
-
m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
|
432
|
-
m.register_type "ntext", SQLServer::Type::UnicodeText.new
|
461
|
+
protected
|
433
462
|
|
434
|
-
|
435
|
-
register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary
|
436
|
-
register_class_with_limit m, %r{\Avarbinary}i, SQLServer::Type::Varbinary
|
437
|
-
m.register_type "varbinary(max)", SQLServer::Type::VarbinaryMax.new
|
463
|
+
# === Abstract Adapter (Misc Support) =========================== #
|
438
464
|
|
439
|
-
|
440
|
-
|
441
|
-
m.register_type "timestamp", SQLServer::Type::Timestamp.new
|
465
|
+
def type_map
|
466
|
+
TYPE_MAP
|
442
467
|
end
|
443
468
|
|
444
469
|
def translate_exception(e, message:, sql:, binds:)
|
445
470
|
case message
|
446
471
|
when /(SQL Server client is not connected)|(failed to execute statement)/i
|
447
472
|
ConnectionNotEstablished.new(message)
|
448
|
-
when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
|
473
|
+
when /(cannot insert duplicate key .* with unique index) | (violation of (unique|primary) key constraint)/i
|
449
474
|
RecordNotUnique.new(message, sql: sql, binds: binds)
|
450
475
|
when /(conflicted with the foreign key constraint) | (The DELETE statement conflicted with the REFERENCE constraint)/i
|
451
476
|
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
452
477
|
when /has been chosen as the deadlock victim/i
|
453
478
|
DeadlockVictim.new(message, sql: sql, binds: binds)
|
454
479
|
when /database .* does not exist/i
|
455
|
-
NoDatabaseError.new(message
|
480
|
+
NoDatabaseError.new(message)
|
456
481
|
when /data would be truncated/
|
457
482
|
ValueTooLong.new(message, sql: sql, binds: binds)
|
458
483
|
when /connection timed out/
|
@@ -484,8 +509,6 @@ module ActiveRecord
|
|
484
509
|
end
|
485
510
|
end
|
486
511
|
|
487
|
-
def configure_application_name; end
|
488
|
-
|
489
512
|
def initialize_dateformatter
|
490
513
|
@database_dateformat = user_options_dateformat
|
491
514
|
a, b, c = @database_dateformat.each_char.to_a
|
@@ -2,48 +2,87 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module ConnectionAdapters
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
super
|
9
|
-
end
|
5
|
+
module SQLServer
|
6
|
+
class Column < ConnectionAdapters::Column
|
7
|
+
delegate :is_identity, :is_primary, :table_name, :ordinal_position, to: :sql_type_metadata
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
def initialize(*, is_identity: nil, is_primary: nil, table_name: nil, ordinal_position: nil, **)
|
10
|
+
super
|
11
|
+
@is_identity = is_identity
|
12
|
+
@is_primary = is_primary
|
13
|
+
@table_name = table_name
|
14
|
+
@ordinal_position = ordinal_position
|
15
|
+
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
def is_identity?
|
18
|
+
is_identity
|
19
|
+
end
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
def is_primary?
|
22
|
+
is_primary
|
23
|
+
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
|
25
|
+
def is_utf8?
|
26
|
+
sql_type =~ /nvarchar|ntext|nchar/i
|
27
|
+
end
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
|
29
|
+
def case_sensitive?
|
30
|
+
collation && collation.match(/_CS/)
|
31
|
+
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
33
|
+
def init_with(coder)
|
34
|
+
@is_identity = coder["is_identity"]
|
35
|
+
@is_primary = coder["is_primary"]
|
36
|
+
@table_name = coder["table_name"]
|
37
|
+
@ordinal_position = coder["ordinal_position"]
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def encode_with(coder)
|
42
|
+
coder["is_identity"] = @is_identity
|
43
|
+
coder["is_primary"] = @is_primary
|
44
|
+
coder["table_name"] = @table_name
|
45
|
+
coder["ordinal_position"] = @ordinal_position
|
46
|
+
super
|
47
|
+
end
|
48
|
+
|
49
|
+
def ==(other)
|
50
|
+
other.is_a?(Column) &&
|
51
|
+
super &&
|
52
|
+
is_identity? == other.is_identity? &&
|
53
|
+
is_primary? == other.is_primary? &&
|
54
|
+
table_name == other.table_name &&
|
55
|
+
ordinal_position == other.ordinal_position
|
56
|
+
end
|
57
|
+
alias :eql? :==
|
58
|
+
|
59
|
+
def hash
|
60
|
+
Column.hash ^
|
61
|
+
super.hash ^
|
62
|
+
is_identity?.hash ^
|
63
|
+
is_primary?.hash ^
|
64
|
+
table_name.hash ^
|
65
|
+
ordinal_position.hash
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# In the Rails version of this method there is an assumption that the `default` value will always be a
|
71
|
+
# `String` class, which must be true for the MySQL/PostgreSQL/SQLite adapters. However, in the SQL Server
|
72
|
+
# adapter the `default` value can also be Boolean/Date/Time/etc. Changed the implementation of this method
|
73
|
+
# to handle non-String `default` objects.
|
74
|
+
def deduplicated
|
75
|
+
@name = -name
|
76
|
+
@sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
|
77
|
+
@default = (default.is_a?(String) ? -default : default.dup.freeze) if default
|
78
|
+
@default_function = -default_function if default_function
|
79
|
+
@collation = -collation if collation
|
80
|
+
@comment = -comment if comment
|
81
|
+
freeze
|
82
|
+
end
|
46
83
|
end
|
84
|
+
|
85
|
+
SQLServerColumn = SQLServer::Column
|
47
86
|
end
|
48
87
|
end
|
49
88
|
end
|
@@ -83,6 +83,8 @@ module Arel
|
|
83
83
|
# Monkey-patch start. Add query attribute bindings rather than just values.
|
84
84
|
column_name = o.column_name
|
85
85
|
column_type = o.attribute.relation.type_for_attribute(o.column_name)
|
86
|
+
# Use cast_type on encrypted attributes. Don't encrypt them again
|
87
|
+
column_type = column_type.cast_type if column_type.is_a?(ActiveRecord::Encryption::EncryptedAttributeType)
|
86
88
|
attrs = values.map { |value| ActiveRecord::Relation::QueryAttribute.new(column_name, value, column_type) }
|
87
89
|
|
88
90
|
collector.add_binds(attrs, &bind_block)
|
@@ -296,8 +298,21 @@ module Arel
|
|
296
298
|
def primary_Key_From_Table(t)
|
297
299
|
return unless t
|
298
300
|
|
299
|
-
|
300
|
-
|
301
|
+
primary_keys = @connection.schema_cache.primary_keys(t.name)
|
302
|
+
column_name = nil
|
303
|
+
|
304
|
+
case primary_keys
|
305
|
+
when NilClass
|
306
|
+
column_name = @connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
|
307
|
+
when String
|
308
|
+
column_name = primary_keys
|
309
|
+
when Array
|
310
|
+
candidate_columns = @connection.schema_cache.columns_hash(t.name).slice(*primary_keys).values
|
311
|
+
candidate_column = candidate_columns.find(&:is_identity?)
|
312
|
+
candidate_column ||= candidate_columns.first
|
313
|
+
column_name = candidate_column.try(:name)
|
314
|
+
end
|
315
|
+
|
301
316
|
column_name ? t[column_name] : nil
|
302
317
|
end
|
303
318
|
|
@@ -120,6 +120,14 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
120
120
|
"expected database #{db_config.database} to exist"
|
121
121
|
end
|
122
122
|
|
123
|
+
it "test primary key violation" do
|
124
|
+
Post.create!(id: 0, title: 'Setup', body: 'Create post with primary key of zero')
|
125
|
+
|
126
|
+
assert_raise ActiveRecord::RecordNotUnique do
|
127
|
+
Post.create!(id: 0, title: 'Test', body: 'Try to create another post with primary key of zero')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
123
131
|
describe "with different language" do
|
124
132
|
before do
|
125
133
|
@default_language = connection.user_options_language
|
@@ -377,7 +385,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
377
385
|
assert !SSTestCustomersView.columns.blank?
|
378
386
|
assert_equal columns.size, SSTestCustomersView.columns.size
|
379
387
|
columns.each do |colname|
|
380
|
-
assert_instance_of ActiveRecord::ConnectionAdapters::
|
388
|
+
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
|
381
389
|
SSTestCustomersView.columns_hash[colname],
|
382
390
|
"Column name #{colname.inspect} was not found in these columns #{SSTestCustomersView.columns.map(&:name).inspect}"
|
383
391
|
end
|
@@ -404,7 +412,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
|
|
404
412
|
assert !SSTestStringDefaultsView.columns.blank?
|
405
413
|
assert_equal columns.size, SSTestStringDefaultsView.columns.size
|
406
414
|
columns.each do |colname|
|
407
|
-
assert_instance_of ActiveRecord::ConnectionAdapters::
|
415
|
+
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
|
408
416
|
SSTestStringDefaultsView.columns_hash[colname],
|
409
417
|
"Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
|
410
418
|
end
|