Skip to content

Commit 04a42c0

Browse files
authored
bugfix: different element order in two lists causes failure to set the index as primary key index (#7966)
1 parent 19a14d6 commit 04a42c0

File tree

6 files changed

+295
-34
lines changed

6 files changed

+295
-34
lines changed

changes/en-us/2.6.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ Add changes here for all PR submitted to the 2.x branch.
7575
- [[#7908](https://github.com/apache/incubator-seata/pull/7908)] handle timestamp with time zone in postgresql primary key
7676
- [[#7938](https://github.com/apache/incubator-seata/pull/7938)] ensure the Jakarta-related package paths are correct.
7777
- [[#7959](https://github.com/apache/incubator-seata/pull/7959)] fix consoleApiService bean that could not be found
78+
- [[#7966](https://github.com/apache/incubator-seata/pull/7966)] fix different element order in two lists causes failure to set the index as primary key index
7879

7980

8081
### optimize:

changes/zh-cn/2.6.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
- [[#7908](https://github.com/apache/incubator-seata/pull/7908)] 在 PostgreSQL 主键中处理带时区的时间戳
7676
- [[#7938](https://github.com/apache/incubator-seata/pull/7938)] 保证Jakarta相关包路径正确
7777
- [[#7959](https://github.com/apache/incubator-seata/pull/7959)] 修复consoleApiService bean未加载的问题
78+
- [[#7966](https://github.com/apache/incubator-seata/pull/7966)] 修复dm和kingbase中因列顺序不同导致索引未被识别为主键索引的问题
7879

7980

8081
### optimize:

rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/DmTableMetaCache.java

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import java.sql.DatabaseMetaData;
2929
import java.sql.ResultSet;
3030
import java.sql.SQLException;
31-
import java.util.ArrayList;
32-
import java.util.List;
31+
import java.util.LinkedHashSet;
32+
import java.util.Set;
3333
import java.util.stream.Collectors;
3434

3535
/**
@@ -124,25 +124,53 @@ protected void processIndexes(TableMeta tableMeta, ResultSet rs) throws SQLExcep
124124
}
125125

126126
protected void processPrimaries(TableMeta tableMeta, ResultSet rs) throws SQLException {
127-
List<String> primaryKeyColumns = new ArrayList<>();
127+
// Collect primary key column names that couldn't be matched directly by PK_NAME
128+
// In Oracle/DM: when primary key constraint name differs from unique index name,
129+
// we need to match by column names instead
130+
Set<String> unmatchedPkColumns = new LinkedHashSet<>();
131+
132+
// Iterate through each row of getPrimaryKeys() result set
133+
// For composite primary key, there will be multiple rows with same PK_NAME
128134
while (rs.next()) {
129-
String pkColName;
130-
try {
131-
pkColName = rs.getString("COLUMN_NAME");
132-
} catch (Exception e) {
133-
pkColName = rs.getString("PK_NAME");
135+
String pkConstraintName = getStringSafely(rs, "PK_NAME");
136+
String pkColName = getStringSafely(rs, "COLUMN_NAME");
137+
if (StringUtils.isBlank(pkColName)) {
138+
pkColName = pkConstraintName;
134139
}
135-
primaryKeyColumns.add(pkColName);
136-
}
137-
138-
for (IndexMeta index : tableMeta.getAllIndexes().values()) {
139-
List<String> indexColumns = index.getValues().stream()
140-
.filter(col -> col != null && StringUtils.isNotBlank(col.getColumnName()))
141-
.map(ColumnMeta::getColumnName)
142-
.collect(Collectors.toList());
143140

144-
if (indexColumns.equals(primaryKeyColumns)) {
141+
// Strategy 1: Try direct match by PK constraint name
142+
// If the index name matches the primary key constraint name, mark it as PRIMARY
143+
if (StringUtils.isNotBlank(pkConstraintName)
144+
&& tableMeta.getAllIndexes().containsKey(pkConstraintName)) {
145+
IndexMeta index = tableMeta.getAllIndexes().get(pkConstraintName);
145146
index.setIndextype(IndexType.PRIMARY);
147+
} else {
148+
// Save columns for Strategy 2: fallback column-based matching
149+
if (StringUtils.isNotBlank(pkColName)) {
150+
unmatchedPkColumns.add(pkColName.toUpperCase());
151+
}
152+
}
153+
}
154+
155+
// Strategy 2: Fallback - find index whose columns match the primary key columns
156+
// This handles the case where PK constraint name differs from unique index name
157+
if (!unmatchedPkColumns.isEmpty()) {
158+
for (IndexMeta index : tableMeta.getAllIndexes().values()) {
159+
// Only check UNIQUE indexes as candidates (primary key is always unique)
160+
if (index.getIndextype().value() == IndexType.UNIQUE.value()) {
161+
// Build index column set, normalized to uppercase and deduplicated
162+
Set<String> indexColsSet = index.getValues().stream()
163+
.filter(col -> col != null && StringUtils.isNotBlank(col.getColumnName()))
164+
.map(col -> col.getColumnName().toUpperCase())
165+
.collect(Collectors.toSet());
166+
167+
// If sets are equal, this index exactly matches primary key columns
168+
if (indexColsSet.equals(unmatchedPkColumns)) {
169+
index.setIndextype(IndexType.PRIMARY);
170+
// Each table has only one primary key
171+
break;
172+
}
173+
}
146174
}
147175
}
148176
}
@@ -186,4 +214,12 @@ protected IndexMeta toIndexMeta(ResultSet rs, String indexName, ColumnMeta colum
186214
}
187215
return result;
188216
}
217+
218+
private static String getStringSafely(ResultSet rs, String columnLabel) {
219+
try {
220+
return rs.getString(columnLabel);
221+
} catch (Exception e) {
222+
return null;
223+
}
224+
}
189225
}

rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/KingbaseTableMetaCache.java

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import java.sql.DatabaseMetaData;
2929
import java.sql.ResultSet;
3030
import java.sql.SQLException;
31-
import java.util.ArrayList;
32-
import java.util.List;
31+
import java.util.LinkedHashSet;
32+
import java.util.Set;
3333
import java.util.stream.Collectors;
3434

3535
/**
@@ -123,25 +123,44 @@ protected void processIndexes(TableMeta tableMeta, ResultSet rs) throws SQLExcep
123123
}
124124

125125
protected void processPrimaries(TableMeta tableMeta, ResultSet rs) throws SQLException {
126-
List<String> primaryKeyColumns = new ArrayList<>();
126+
// Collect primary key column names that couldn't be matched directly by PK_NAME
127+
Set<String> unmatchedPkColumns = new LinkedHashSet<>();
128+
129+
// Iterate through each row of getPrimaryKeys() result set
127130
while (rs.next()) {
128-
String pkColName;
129-
try {
130-
pkColName = rs.getString("COLUMN_NAME");
131-
} catch (Exception e) {
132-
pkColName = rs.getString("PK_NAME");
131+
String pkConstraintName = getStringSafely(rs, "PK_NAME");
132+
String pkColName = getStringSafely(rs, "COLUMN_NAME");
133+
if (StringUtils.isBlank(pkColName)) {
134+
pkColName = pkConstraintName;
133135
}
134-
primaryKeyColumns.add(pkColName);
135-
}
136-
137-
for (IndexMeta index : tableMeta.getAllIndexes().values()) {
138-
List<String> indexColumns = index.getValues().stream()
139-
.filter(col -> col != null && StringUtils.isNotBlank(col.getColumnName()))
140-
.map(ColumnMeta::getColumnName)
141-
.collect(Collectors.toList());
142136

143-
if (indexColumns.equals(primaryKeyColumns)) {
137+
// Strategy 1: Try direct match by PK constraint name
138+
if (StringUtils.isNotBlank(pkConstraintName)
139+
&& tableMeta.getAllIndexes().containsKey(pkConstraintName)) {
140+
IndexMeta index = tableMeta.getAllIndexes().get(pkConstraintName);
144141
index.setIndextype(IndexType.PRIMARY);
142+
} else {
143+
// Save columns for fallback column-based matching
144+
if (StringUtils.isNotBlank(pkColName)) {
145+
unmatchedPkColumns.add(pkColName.toUpperCase());
146+
}
147+
}
148+
}
149+
150+
// Strategy 2: fallback - match by column set equality (order-insensitive, deduped)
151+
if (!unmatchedPkColumns.isEmpty()) {
152+
for (IndexMeta index : tableMeta.getAllIndexes().values()) {
153+
if (index.getIndextype().value() == IndexType.UNIQUE.value()) {
154+
Set<String> indexColsSet = index.getValues().stream()
155+
.filter(col -> col != null && StringUtils.isNotBlank(col.getColumnName()))
156+
.map(col -> col.getColumnName().toUpperCase())
157+
.collect(Collectors.toSet());
158+
159+
if (indexColsSet.equals(unmatchedPkColumns)) {
160+
index.setIndextype(IndexType.PRIMARY);
161+
break;
162+
}
163+
}
145164
}
146165
}
147166
}
@@ -185,4 +204,12 @@ protected IndexMeta toIndexMeta(ResultSet rs, String indexName, ColumnMeta colum
185204
}
186205
return result;
187206
}
207+
208+
private static String getStringSafely(ResultSet rs, String columnLabel) {
209+
try {
210+
return rs.getString(columnLabel);
211+
} catch (Exception e) {
212+
return null;
213+
}
214+
}
188215
}

rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/struct/cache/DmTableMetaCacheTest.java

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,4 +332,102 @@ public void testGetTableMetaOriginalTableNamePreserved() throws SQLException {
332332
Assertions.assertNotNull(tableMeta);
333333
Assertions.assertEquals(originalName, tableMeta.getOriginalTableName());
334334
}
335+
336+
@Test
337+
public void testCompositeIndexWithDifferentColumnOrder() throws SQLException {
338+
// Regression test for a bug fix: different column order caused List.equals to fail, which could lead to
339+
// failing to recognize a primary key index.
340+
// Scenario: composite primary key (ID, USER_ID) while a unique index lists the same columns in reverse order
341+
// (USER_ID, ID).
342+
// Before the fix: List.equals returned FALSE because of the different order, causing the primary key index to
343+
// be unrecognized.
344+
// After the fix: using Set.equals should return TRUE and correctly identify the index as PRIMARY.
345+
346+
Object[][] compositeIndexDiffOrder = new Object[][] {
347+
new Object[] {"idx_id_userid", "id", false, "", 3, 1, "A", 34}, // column order 1
348+
new Object[] {"idx_id_userid", "user_id", false, "", 3, 2, "A", 34} // column order 2
349+
};
350+
Object[][] compositePKMetas = new Object[][] {
351+
new Object[] {"user_id"}, // PK column order 1 (intentionally different from index order)
352+
new Object[] {"id"} // PK column order 2
353+
};
354+
Object[][] compositeColumnMetas = new Object[][] {
355+
new Object[] {"", "", "dt2", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"
356+
},
357+
new Object[] {
358+
"", "", "dt2", "user_id", Types.INTEGER, "INTEGER", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"
359+
}
360+
};
361+
Object[][] compositeTableMetas = new Object[][] {new Object[] {"", "t", "dt2"}};
362+
363+
MockDriver mockDriver =
364+
new MockDriver(compositeColumnMetas, compositeIndexDiffOrder, compositePKMetas, compositeTableMetas);
365+
DruidDataSource dataSource = new DruidDataSource();
366+
dataSource.setUrl("jdbc:mock:dm");
367+
dataSource.setDriver(mockDriver);
368+
369+
DataSourceProxy proxy = DataSourceProxyTest.getDataSourceProxy(dataSource);
370+
TableMetaCache tableMetaCache = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.DM);
371+
372+
TableMeta tableMeta = tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.dt2", proxy.getResourceId());
373+
374+
Assertions.assertNotNull(tableMeta);
375+
IndexMeta compositeIndex = tableMeta.getAllIndexes().get("idx_id_userid");
376+
Assertions.assertNotNull(compositeIndex);
377+
// Key assertion: should be recognized as PRIMARY (after the fix), not mistakenly treated as UNIQUE
378+
Assertions.assertEquals(
379+
IndexType.PRIMARY,
380+
compositeIndex.getIndextype(),
381+
"Composite index with different column order should be recognized as PRIMARY");
382+
}
383+
384+
@Test
385+
public void testCompositeIndexWithExtraColumnsNotMarkedAsPrimary() throws SQLException {
386+
// Regression test for a bug fix: a composite unique index that contains primary key columns plus extra columns
387+
// should NOT be marked as PRIMARY.
388+
// Scenario: primary key (ID), unique index (ID, NAME) — unique index contains PK column but has extra NAME.
389+
// Before the fix: the previous DM implementation matched by count (matchCols == pkcol.size()) and could misidentify
390+
// PRIMARY.
391+
// After the fix: using Set equality ({ID, NAME} != {ID}) prevents the index from being marked as PRIMARY
392+
393+
Object[][] mixedIndexMetas = new Object[][] {
394+
new Object[] {"idx_id", "id", false, "", 3, 1, "A", 34},
395+
new Object[] {"uk_id_name", "id", false, "", 3, 1, "A", 34}, // unique index contains the primary key column
396+
new Object[] {"uk_id_name", "name1", false, "", 3, 2, "A", 34} // but it also has extra columns
397+
};
398+
399+
// Use a dedicated table name to avoid cache hits from earlier tests
400+
Object[][] extraColumnMetas = new Object[][] {
401+
new Object[] {"", "", "dt3", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"
402+
},
403+
new Object[] {
404+
"", "", "dt3", "name1", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"
405+
}
406+
};
407+
Object[][] extraPkMetas = new Object[][] {new Object[] {"id"}};
408+
Object[][] extraTableMetas = new Object[][] {new Object[] {"", "t", "dt3"}};
409+
410+
MockDriver mockDriver = new MockDriver(extraColumnMetas, mixedIndexMetas, extraPkMetas, extraTableMetas);
411+
DruidDataSource dataSource = new DruidDataSource();
412+
dataSource.setUrl("jdbc:mock:dm");
413+
dataSource.setDriver(mockDriver);
414+
415+
DataSourceProxy proxy = DataSourceProxyTest.getDataSourceProxy(dataSource);
416+
TableMetaCache tableMetaCache = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.DM);
417+
418+
TableMeta tableMeta = tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.dt3", proxy.getResourceId());
419+
420+
Assertions.assertNotNull(tableMeta);
421+
IndexMeta pkIndex = tableMeta.getAllIndexes().get("idx_id");
422+
Assertions.assertNotNull(pkIndex);
423+
Assertions.assertEquals(IndexType.PRIMARY, pkIndex.getIndextype(), "Single column PK index should be PRIMARY");
424+
425+
IndexMeta mixedIndex = tableMeta.getAllIndexes().get("uk_id_name");
426+
Assertions.assertNotNull(mixedIndex);
427+
// Key assertion: should remain UNIQUE (should not be misidentified as PRIMARY)
428+
Assertions.assertEquals(
429+
IndexType.UNIQUE,
430+
mixedIndex.getIndextype(),
431+
"Composite index with extra columns should NOT be marked as PRIMARY");
432+
}
335433
}

0 commit comments

Comments
 (0)