Skip to content

Commit dec6836

Browse files
authored
Fix deteministic fetch when table has a composite primary key (#936)
Even though Rails does not support tables with primary keys, it still allows those tables to be defined without any problems. The SQL Server adapter will always try to retrieve records in a deterministic way but it currently fails if the table has a composite primary key, which shouldn't happen. This PR fixes how the adapter determines which column (generally the PK) is used as the default order when multiple primary keys are detected. It first checks if one of the primary keys is also an identity column. If there's an identity column, it will choose it as the "main primary key", otherwise it will just fallback to the first of the primary keys.
1 parent e4d91fc commit dec6836

File tree

5 files changed

+61
-2
lines changed

5 files changed

+61
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
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
66
- [#935](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/935) Fix schema cache generation
77
(**breaking change**)
8+
- [#936](https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/pull/936) Fix deteministic fetch when table has a composite primary key
89

910
#### Changed
1011

lib/arel/visitors/sqlserver.rb

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,21 @@ def table_From_Statement(o)
296296
def primary_Key_From_Table(t)
297297
return unless t
298298

299-
column_name = @connection.schema_cache.primary_keys(t.name) ||
300-
@connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
299+
primary_keys = @connection.schema_cache.primary_keys(t.name)
300+
column_name = nil
301+
302+
case primary_keys
303+
when NilClass
304+
column_name = @connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
305+
when String
306+
column_name = primary_keys
307+
when Array
308+
candidate_columns = @connection.schema_cache.columns_hash(t.name).slice(*primary_keys).values
309+
candidate_column = candidate_columns.find(&:is_identity?)
310+
candidate_column ||= candidate_columns.first
311+
column_name = candidate_column.try(:name)
312+
end
313+
301314
column_name ? t[column_name] : nil
302315
end
303316

test/cases/fetch_test_sqlserver.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,21 @@ def create_10_books
4949
@books = (1..10).map { |i| Book.create! name: "Name-#{i}" }
5050
end
5151
end
52+
53+
class DeterministicFetchWithCompositePkTestSQLServer < ActiveRecord::TestCase
54+
it "orders by the identity column if table has one" do
55+
SSCompositePkWithIdentity.delete_all
56+
SSCompositePkWithIdentity.create(pk_col_two: 2)
57+
SSCompositePkWithIdentity.create(pk_col_two: 1)
58+
59+
_(SSCompositePkWithIdentity.take(1).map(&:pk_col_two)).must_equal [2]
60+
end
61+
62+
it "orders by the first column if table has no identity column" do
63+
SSCompositePkWithoutIdentity.delete_all
64+
SSCompositePkWithoutIdentity.create(pk_col_one: 2, pk_col_two: 2)
65+
SSCompositePkWithoutIdentity.create(pk_col_one: 1, pk_col_two: 1)
66+
67+
_(SSCompositePkWithoutIdentity.take(1).map(&:pk_col_two)).must_equal [1]
68+
end
69+
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
class SSCompositePkWithoutIdentity < ActiveRecord::Base
4+
self.table_name = :sst_composite_without_identity
5+
end
6+
7+
class SSCompositePkWithIdentity < ActiveRecord::Base
8+
self.table_name = :sst_composite_with_identity
9+
end

test/schema/sqlserver_specific_schema.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,4 +294,22 @@
294294
CONSTRAINT PK_UNIQUE_KEY PRIMARY KEY (id)
295295
);
296296
SQLSERVERUNIQUEKEYS
297+
298+
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_composite_without_identity') DROP TABLE sst_composite_without_identity"
299+
execute <<-COMPOSITE_WITHOUT_IDENTITY
300+
CREATE TABLE sst_composite_without_identity (
301+
pk_col_one int NOT NULL,
302+
pk_col_two int NOT NULL,
303+
CONSTRAINT PK_sst_composite_without_identity PRIMARY KEY (pk_col_one, pk_col_two)
304+
);
305+
COMPOSITE_WITHOUT_IDENTITY
306+
307+
execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_composite_with_identity') DROP TABLE sst_composite_with_identity"
308+
execute <<-COMPOSITE_WITH_IDENTITY
309+
CREATE TABLE sst_composite_with_identity (
310+
pk_col_one int IDENTITY NOT NULL,
311+
pk_col_two int NOT NULL,
312+
CONSTRAINT PK_sst_composite_with_identity PRIMARY KEY (pk_col_one, pk_col_two)
313+
);
314+
COMPOSITE_WITH_IDENTITY
297315
end

0 commit comments

Comments
 (0)