Skip to content

Commit e4d91fc

Browse files
authored
Fix schema cache generation (#935)
1 parent f35f47e commit e4d91fc

File tree

7 files changed

+130
-44
lines changed

7 files changed

+130
-44
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#### Fixed
44

55
- [#933](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/933) Conditionally apply SQL Server monkey patches to ActiveRecord so that it is safe to use this gem alongside other database adapters (e.g. PostgreSQL) in a multi-database Rails app
6+
- [#935](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/935) Fix schema cache generation
7+
(**breaking change**)
68

79
#### Changed
810

lib/active_record/connection_adapters/sqlserver/quoting.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ def fetch_type_metadata(sql_type, sqlserver_options = {})
1717
precision: cast_type.precision,
1818
scale: cast_type.scale
1919
)
20-
SQLServer::TypeMetadata.new(simple_type, sqlserver_options: sqlserver_options)
20+
21+
SQLServer::TypeMetadata.new(simple_type, **sqlserver_options)
2122
end
2223

2324
def quote_string(s)

lib/active_record/connection_adapters/sqlserver/schema_statements.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def columns(table_name)
8484
end
8585

8686
def new_column(name, default, sql_type_metadata, null, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
87-
SQLServerColumn.new(
87+
SQLServer::Column.new(
8888
name,
8989
default,
9090
sql_type_metadata,

lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,33 @@ class TypeMetadata < DelegateClass(SqlTypeMetadata)
88

99
include Deduplicable
1010

11-
attr_reader :sqlserver_options
11+
attr_reader :is_identity, :is_primary, :table_name, :ordinal_position
1212

13-
def initialize(type_metadata, sqlserver_options: nil)
13+
def initialize(type_metadata, is_identity: nil, is_primary: nil, table_name: nil, ordinal_position: nil)
1414
super(type_metadata)
15-
@sqlserver_options = sqlserver_options
15+
@is_identity = is_identity
16+
@is_primary = is_primary
17+
@table_name = table_name
18+
@ordinal_position = ordinal_position
1619
end
1720

1821
def ==(other)
1922
other.is_a?(TypeMetadata) &&
2023
__getobj__ == other.__getobj__ &&
21-
sqlserver_options == other.sqlserver_options
24+
is_identity == other.is_identity &&
25+
is_primary == other.is_primary &&
26+
table_name == other.table_name &&
27+
ordinal_position == other.ordinal_position
2228
end
2329
alias eql? ==
2430

2531
def hash
2632
TypeMetadata.hash ^
2733
__getobj__.hash ^
28-
sqlserver_options.hash
34+
is_identity.hash ^
35+
is_primary.hash ^
36+
table_name.hash ^
37+
ordinal_position.hash
2938
end
3039

3140
private

lib/active_record/connection_adapters/sqlserver_column.rb

Lines changed: 74 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,87 @@
22

33
module ActiveRecord
44
module ConnectionAdapters
5-
class SQLServerColumn < Column
6-
def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **sqlserver_options)
7-
@sqlserver_options = sqlserver_options
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
108

11-
def is_identity?
12-
@sqlserver_options[:is_identity]
13-
end
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
1416

15-
def is_primary?
16-
@sqlserver_options[:is_primary]
17-
end
17+
def is_identity?
18+
is_identity
19+
end
1820

19-
def table_name
20-
@sqlserver_options[:table_name]
21-
end
21+
def is_primary?
22+
is_primary
23+
end
2224

23-
def is_utf8?
24-
sql_type =~ /nvarchar|ntext|nchar/i
25-
end
25+
def is_utf8?
26+
sql_type =~ /nvarchar|ntext|nchar/i
27+
end
2628

27-
def case_sensitive?
28-
collation && collation.match(/_CS/)
29-
end
29+
def case_sensitive?
30+
collation && collation.match(/_CS/)
31+
end
3032

31-
private
32-
33-
# In the Rails version of this method there is an assumption that the `default` value will always be a
34-
# `String` class, which must be true for the MySQL/PostgreSQL/SQLite adapters. However, in the SQL Server
35-
# adapter the `default` value can also be Boolean/Date/Time/etc. Changed the implementation of this method
36-
# to handle non-String `default` objects.
37-
def deduplicated
38-
@name = -name
39-
@sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
40-
@default = (default.is_a?(String) ? -default : default.dup.freeze) if default
41-
@default_function = -default_function if default_function
42-
@collation = -collation if collation
43-
@comment = -comment if comment
44-
45-
freeze
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
4683
end
84+
85+
SQLServerColumn = SQLServer::Column
4786
end
4887
end
4988
end

test/cases/adapter_test_sqlserver.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
377377
assert !SSTestCustomersView.columns.blank?
378378
assert_equal columns.size, SSTestCustomersView.columns.size
379379
columns.each do |colname|
380-
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
380+
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
381381
SSTestCustomersView.columns_hash[colname],
382382
"Column name #{colname.inspect} was not found in these columns #{SSTestCustomersView.columns.map(&:name).inspect}"
383383
end
@@ -404,7 +404,7 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
404404
assert !SSTestStringDefaultsView.columns.blank?
405405
assert_equal columns.size, SSTestStringDefaultsView.columns.size
406406
columns.each do |colname|
407-
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
407+
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServer::Column,
408408
SSTestStringDefaultsView.columns_hash[colname],
409409
"Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
410410
end

test/cases/rake_test_sqlserver.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,38 @@ class SQLServerRakeStructureDumpLoadTest < SQLServerRakeTest
156156
_(connection.tables).must_include "users"
157157
end
158158
end
159+
160+
class SQLServerRakeSchemaCacheDumpLoadTest < SQLServerRakeTest
161+
let(:filename) { File.join ARTest::SQLServer.test_root_sqlserver, "schema_cache.yml" }
162+
let(:filedata) { File.read(filename) }
163+
164+
before do
165+
quietly { db_tasks.create(configuration) }
166+
167+
connection.create_table :users, force: true do |t|
168+
t.string :name, null: false
169+
end
170+
end
171+
172+
after do
173+
FileUtils.rm_rf(filename)
174+
end
175+
176+
it "dumps schema cache with SQL Server metadata" do
177+
quietly { db_tasks.dump_schema_cache connection, filename }
178+
179+
schema_cache = YAML.load(File.read(filename))
180+
181+
col_id, col_name = connection.schema_cache.columns("users")
182+
183+
assert col_id.is_identity
184+
assert col_id.is_primary
185+
assert_equal col_id.ordinal_position, 1
186+
assert_equal col_id.table_name, "users"
187+
188+
assert_not col_name.is_identity
189+
assert_not col_name.is_primary
190+
assert_equal col_name.ordinal_position, 2
191+
assert_equal col_name.table_name, "users"
192+
end
193+
end

0 commit comments

Comments
 (0)