Copilot commented on code in PR #8050:
URL: https://github.com/apache/incubator-seata/pull/8050#discussion_r3107830022


##########
changes/en-us/2.x.md:
##########
@@ -1,4 +1,4 @@
-<!--
+<!--
     Licensed to the Apache Software Foundation (ASF) under one or more
     contributor license agreements.  See the NOTICE file distributed with

Review Comment:
   The very first character on this file appears to be a UTF-8 BOM / invisible 
character (line starts with "\uFEFF<!--"). This can create noisy diffs and 
occasionally breaks tooling; please remove the BOM so the file starts with a 
plain "<!--" like the other changelog files.



##########
rm-datasource/src/main/java/org/apache/seata/rm/datasource/SqlGenerateUtils.java:
##########
@@ -115,6 +121,45 @@ public static List<WhereSql> buildWhereConditionListByPKs(
         return whereSqls;
     }
 
+    /**
+     * Build where condition list by PKs for SQL Server.
+     * SQL Server does not support tuple IN syntax: (col1,col2) IN 
((?,?),(?,?))
+     * Use AND/OR syntax instead: (col1=? AND col2=?) OR (col1=? AND col2=?)
+     *
+     * @param pkNameList pk column name list
+     * @param rowSize the row size of records
+     * @param maxInSize the max in size
+     * @return where condition sql list for SQL Server
+     */
+    private static List<WhereSql> buildWhereConditionListByPKsForSqlServer(
+            List<String> pkNameList, int rowSize, int maxInSize, String 
dbType) {
+        List<WhereSql> whereSqls = new ArrayList<>();

Review Comment:
   Javadoc for buildWhereConditionListByPKsForSqlServer() is missing an 
`@param` entry for the dbType argument, even though it is part of the signature 
and used for escaping. Please either document dbType or remove it from the 
signature (since this method is SQL Server-specific).



##########
changes/zh-cn/2.x.md:
##########
@@ -31,6 +31,7 @@
 - [[#8002](https://github.com/apache/incubator-seata/pull/8002)] 
为namingserver指标增加Grafana dashboard JSON
 - [[#8020](https://github.com/apache/incubator-seata/pull/8020)] 新增 
UnregisterRM 协议,在客户端销毁时通知服务端
 - [[#8044](https://github.com/apache/incubator-seata/pull/8044)] 为 
UnregisterRM 协议添加 protobuf 序列化支持
+- [[#8050](https://github.com/apache/incubator-seata/pull/8050)] 新增SQL Server 
多主键支持
 - [[#8046](https://github.com/apache/incubator-seata/pull/8046)] 添加了 fastjson2 
和 jackson3

Review Comment:
   The changelog entry links to PR #8050, but this PR/issue context is #8041. 
Please update the PR number/link in the changelog entry so it points to the 
correct pull request.



##########
changes/en-us/2.x.md:
##########
@@ -30,6 +30,7 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#8002](https://github.com/apache/incubator-seata/pull/8002)] add Grafana 
dashboard JSON for NamingServer metrics
 - [[#8020](https://github.com/apache/incubator-seata/pull/8020)] add 
UnregisterRM protocol to notify server on client destroy
 - [[#8044](https://github.com/apache/incubator-seata/pull/8044)] add protobuf 
serialization support for UnregisterRM protocol
+- [[#8050](https://github.com/apache/incubator-seata/pull/8050)] add SQL 
Server composite primary keys
 - [[#8046](https://github.com/apache/incubator-seata/pull/8046)] add fastjson2 
and jackson3

Review Comment:
   The changelog entry links to PR #8050, but this PR/issue context is #8041. 
Please update the PR number/link in the changelog entry so it points to the 
correct pull request.



##########
rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/SqlServerInsertExecutorTest.java:
##########
@@ -209,4 +209,114 @@ private void mockStatementInsertRows() {
         rows.add(Arrays.asList(Null.get(), "xx", "xx", "xx"));
         
when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows);
     }
+
+    @Test
+    public void testGetPkValues_compositePrimaryKey_withAllPkInInsert() throws 
Exception {
+        // Mock composite primary key: id + user_id
+        List<String> compositePkList = Arrays.asList(ID_COLUMN, 
USER_ID_COLUMN);
+        when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(compositePkList);
+
+        doReturn(tableMeta).when(insertExecutor).getTableMeta();
+        doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); // PK columns 
are in INSERT statement
+
+        // Mock getPkValuesByColumn to return expected values directly
+        Map<String, List<Object>> expectedPkValues = new HashMap<>();
+        expectedPkValues.put(ID_COLUMN, Arrays.asList(1, 2));
+        expectedPkValues.put(USER_ID_COLUMN, Arrays.asList("user1", "user2"));
+        doReturn(expectedPkValues).when(insertExecutor).getPkValuesByColumn();
+
+        Map<String, List<Object>> pkValues = insertExecutor.getPkValues();
+
+        // Verify composite primary key values are correctly retrieved
+        Assertions.assertNotNull(pkValues);
+        Assertions.assertEquals(expectedPkValues, pkValues);
+
+        // Verify that getPkValuesByColumn was called, confirming the code 
path for composite keys with manual values
+        verify(insertExecutor).getPkValuesByColumn();
+    }
+
+    @Test
+    public void testGetPkValues_compositePrimaryKey_withAutoIncrement() throws 
Exception {
+        // Mock composite primary key with auto-increment columns
+        List<String> compositePkList = Arrays.asList(ID_COLUMN, 
USER_ID_COLUMN);
+        when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(compositePkList);
+
+        Map<String, ColumnMeta> pkMap = new HashMap<>();
+        ColumnMeta idMeta = mock(ColumnMeta.class);
+        when(idMeta.isAutoincrement()).thenReturn(true); // Auto-increment
+        pkMap.put(ID_COLUMN, idMeta);
+
+        ColumnMeta userIdMeta = mock(ColumnMeta.class);
+        when(userIdMeta.isAutoincrement()).thenReturn(true); // Auto-increment
+        pkMap.put(USER_ID_COLUMN, userIdMeta);
+        when(tableMeta.getPrimaryKeyMap()).thenReturn(pkMap);
+
+        doReturn(tableMeta).when(insertExecutor).getTableMeta();
+        doReturn(new HashMap<String, 
Integer>()).when(insertExecutor).getPkIndex(); // No PK columns in INSERT
+        
doReturn(Arrays.asList(PK_VALUE)).when(insertExecutor).getGeneratedKeys();
+
+        Map<String, List<Object>> pkValues = insertExecutor.getPkValues();
+
+        // Verify auto-increment column gets value from generated keys
+        Assertions.assertEquals(Arrays.asList(PK_VALUE), 
pkValues.get(ID_COLUMN));
+        Assertions.assertEquals(Arrays.asList(PK_VALUE), 
pkValues.get(USER_ID_COLUMN));

Review Comment:
   This test models a composite PK where *both* columns are autoincrement and 
expects the same generated key to be applied to both. SQL Server allows only 
one IDENTITY column per table (and the production code comment states that), so 
this scenario is not valid and could mask real behavior. Please adjust the test 
to a realistic composite-PK case (e.g., one provided PK + one IDENTITY, which 
you already test) or assert that multiple autoincrement PK columns are rejected.
   ```suggestion
       public void 
testGetPkValues_compositePrimaryKey_withOneAutoIncrementAndOnePkInInsert() 
throws Exception {
           // Mock realistic SQL Server composite primary key: one IDENTITY 
column + one provided PK column
           List<String> compositePkList = Arrays.asList(ID_COLUMN, 
USER_ID_COLUMN);
           when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(compositePkList);
   
           Map<String, ColumnMeta> pkMap = new HashMap<>();
           ColumnMeta idMeta = mock(ColumnMeta.class);
           when(idMeta.isAutoincrement()).thenReturn(true);
           pkMap.put(ID_COLUMN, idMeta);
   
           ColumnMeta userIdMeta = mock(ColumnMeta.class);
           when(userIdMeta.isAutoincrement()).thenReturn(false);
           pkMap.put(USER_ID_COLUMN, userIdMeta);
           when(tableMeta.getPrimaryKeyMap()).thenReturn(pkMap);
   
           doReturn(tableMeta).when(insertExecutor).getTableMeta();
   
           Map<String, Integer> pkIndex = new HashMap<>();
           pkIndex.put(USER_ID_COLUMN, 0);
           doReturn(pkIndex).when(insertExecutor).getPkIndex();
   
           Map<String, List<Object>> pkValuesByColumn = new HashMap<>();
           pkValuesByColumn.put(USER_ID_COLUMN, Arrays.asList("user1"));
           
doReturn(pkValuesByColumn).when(insertExecutor).getPkValuesByColumn();
           
doReturn(Arrays.asList(PK_VALUE)).when(insertExecutor).getGeneratedKeys();
   
           Map<String, List<Object>> pkValues = insertExecutor.getPkValues();
   
           Assertions.assertEquals(Arrays.asList(PK_VALUE), 
pkValues.get(ID_COLUMN));
           Assertions.assertEquals(Arrays.asList("user1"), 
pkValues.get(USER_ID_COLUMN));
   ```



##########
rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/sqlserver/SqlServerInsertExecutor.java:
##########
@@ -84,8 +86,44 @@ public Map<String, List<Object>> getPkValues() throws 
SQLException {
                 pkValuesMap = getPkValuesWithNoColumn();
             }
         } else {
-            // when there is a composite primary key
-            throw new NotSupportYetException("composite primary key is not 
supported in sqlserver");
+            // when there is a composite primary key - Fix: Support SQL Server 
composite primary keys.
+            // SQL Server allows only one IDENTITY column per table.
+            // So composite PK can have at most one auto-increment column.
+            // Strategy: parse PK values from INSERT columns, then fill 
missing auto-increment PK from generated keys.
+            if (!getPkIndex().isEmpty()) {
+                // At least one PK column is in the INSERT statement.
+                pkValuesMap = getPkValuesByColumn();
+                Map<String, ColumnMeta> primaryKeyMap = 
getTableMeta().getPrimaryKeyMap();
+
+                // Fill any missing auto-increment PK columns from generated 
keys.
+                for (String pkColumnName : pkColumnNameList) {
+                    if (!pkValuesMap.containsKey(pkColumnName)) {
+                        ColumnMeta pkMeta = primaryKeyMap.get(pkColumnName);
+                        if (pkMeta.isAutoincrement()) {
+                            pkValuesMap.put(pkColumnName, getGeneratedKeys());
+                        } else {
+                            throw new NotSupportYetException(
+                                    "composite primary key with 
non-autoincrement column not in INSERT is not supported in sqlserver: "
+                                            + pkColumnName);
+                        }
+                    }
+                }
+            } else {
+                // No PK columns in INSERT statement.
+                // For composite PK, this means all PK columns must have 
values from elsewhere.
+                // Since SQL Server only supports one IDENTITY column, 
non-identity PK columns would fail.
+                Map<String, ColumnMeta> primaryKeyMap = 
getTableMeta().getPrimaryKeyMap();
+                for (String pkColumnName : pkColumnNameList) {
+                    ColumnMeta pkMeta = primaryKeyMap.get(pkColumnName);
+                    if (pkMeta.isAutoincrement()) {
+                        pkValuesMap.put(pkColumnName, getGeneratedKeys());
+                    } else {

Review Comment:
   In composite-PK branches, this calls getGeneratedKeys() inside a loop and 
may invoke it multiple times (e.g., when more than one PK column is missing or 
marked autoincrement). SqlServerInsertExecutor.getGeneratedKeys() consumes the 
generated-keys ResultSet and relies on beforeFirst(), which may fail on some 
drivers; repeated calls can therefore return empty and throw 
NotSupportYetException or produce inconsistent results. Consider fetching 
generated keys once and reusing the list, and/or explicitly enforcing that at 
most one PK column can be autoincrement (otherwise fail fast).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to