Skip to content

Commit 57cde86

Browse files
branch-3.1: [Fix](autoinc) Fix wrong auto inc value after FE do checkpoint and restart #57104 (#57118)
Cherry-picked from #57104 Co-authored-by: bobhan1 <baohan@selectdb.com>
1 parent 2ade251 commit 57cde86

2 files changed

Lines changed: 179 additions & 1 deletion

File tree

fe/fe-core/src/main/java/org/apache/doris/catalog/AutoIncrementGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public AutoIncrementGenerator(long dbId, long tableId, long columnId, long nextI
6161
this.tableId = tableId;
6262
this.columnId = columnId;
6363
this.nextId = nextId;
64-
this.batchEndId = -1;
64+
this.batchEndId = nextId;
6565
}
6666

6767
public void setEditLog(EditLog editLog) {
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.doris.catalog;
19+
20+
import org.apache.doris.common.Pair;
21+
import org.apache.doris.common.UserException;
22+
import org.apache.doris.persist.EditLog;
23+
import org.apache.doris.persist.gson.GsonUtils;
24+
import org.apache.doris.utframe.TestWithFeService;
25+
26+
import org.junit.jupiter.api.Assertions;
27+
import org.junit.jupiter.api.Test;
28+
import org.mockito.Mockito;
29+
30+
import java.io.IOException;
31+
32+
public class AutoIncrementGeneratorTest extends TestWithFeService {
33+
34+
@Override
35+
protected void runBeforeAll() throws Exception {
36+
createDatabase("test_db");
37+
createTable("CREATE TABLE test_db.test_auto_inc_table (\n"
38+
+ " id BIGINT NOT NULL AUTO_INCREMENT,\n"
39+
+ " name VARCHAR(100)\n"
40+
+ ") DUPLICATE KEY(id)\n"
41+
+ "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
42+
+ "PROPERTIES (\"replication_num\" = \"1\");");
43+
}
44+
45+
/**
46+
* Test to reproduce the bug where AutoIncrementGenerator.gsonPostProcess
47+
* sets nextId to batchEndId (-1 by default) after checkpoint and restart.
48+
*
49+
* Scenario:
50+
* 1. Create a table with auto-increment column (batchEndId = -1 by default)
51+
* 2. Master FE does checkpoint (serializes the table to JSON)
52+
* 3. Master FE restarts and deserializes the table from JSON
53+
* 4. gsonPostProcess() is called on AutoIncrementGenerator, setting nextId = batchEndId = -1
54+
* 5. getAutoIncrementRange() returns id starting from -1, which is WRONG
55+
*/
56+
@Test
57+
public void testBugAfterCheckpointRestart() throws Exception {
58+
// Get the table and its AutoIncrementGenerator
59+
Database db = Env.getCurrentInternalCatalog().getDbOrMetaException("test_db");
60+
OlapTable table = (OlapTable) db.getTableOrMetaException("test_auto_inc_table");
61+
AutoIncrementGenerator generator = table.getAutoIncrementGenerator();
62+
63+
Assertions.assertNotNull(generator, "AutoIncrementGenerator should not be null");
64+
65+
long initialBatchEndId = getBatchEndId(generator);
66+
System.out.println("Initial batchEndId: " + initialBatchEndId);
67+
Assertions.assertTrue(initialBatchEndId >= 0, "Initial batchEndId should be non-negative");
68+
69+
// Step 2: Serialize the table to JSON (simulating checkpoint)
70+
String tableJson = GsonUtils.GSON.toJson(table);
71+
System.out.println("Serialized table to JSON");
72+
73+
// Step 3: Deserialize the table from JSON (simulating restart)
74+
// This will trigger gsonPostProcess() which sets nextId = batchEndId
75+
OlapTable deserializedTable = GsonUtils.GSON.fromJson(tableJson, OlapTable.class);
76+
AutoIncrementGenerator deserializedGenerator = deserializedTable.getAutoIncrementGenerator();
77+
78+
Assertions.assertNotNull(deserializedGenerator,
79+
"AutoIncrementGenerator should not be null after deserialization");
80+
81+
// Step 4: Try to get auto-increment range
82+
// This should trigger the bug where startId is -1
83+
long columnId = getAutoIncrementColumnId(table);
84+
Pair<Long, Long> range = deserializedGenerator.getAutoIncrementRange(columnId, 10, 0);
85+
86+
long startId = range.first;
87+
System.out.println("Start ID after deserialization: " + startId);
88+
89+
// BUG REPRODUCED: The startId should be >= 0, but due to gsonPostProcess()
90+
// setting nextId = batchEndId = -1, we get a negative startId
91+
if (startId < 0) {
92+
Assertions.fail("Bug reproduced: startId should not be negative, but got " + startId
93+
+ ". This is because gsonPostProcess() set nextId to batchEndId (-1)");
94+
}
95+
96+
Assertions.assertTrue(startId >= 0, "Start ID should be non-negative, but got " + startId);
97+
}
98+
99+
/**
100+
* Test the normal case without checkpoint/restart - using simple generator
101+
*/
102+
@Test
103+
public void testNormalGetAutoIncrementRange() throws UserException {
104+
long dbId = 1L;
105+
long tableId = 2L;
106+
long columnId = 3L;
107+
long initialNextId = 100L;
108+
109+
AutoIncrementGenerator generator = new AutoIncrementGenerator(dbId, tableId, columnId, initialNextId);
110+
EditLog mockEditLog = Mockito.mock(EditLog.class);
111+
generator.setEditLog(mockEditLog);
112+
113+
// Get a range without checkpoint/restart
114+
Pair<Long, Long> range = generator.getAutoIncrementRange(columnId, 10, 0);
115+
116+
Assertions.assertEquals(100L, range.first.longValue(), "Start ID should be 100");
117+
Assertions.assertEquals(10L, range.second.longValue(), "Length should be 10");
118+
}
119+
120+
/**
121+
* Test gsonPostProcess behavior when batchEndId is already set
122+
*/
123+
@Test
124+
public void testGsonPostProcessWithValidBatchEndId() throws UserException, IOException {
125+
long dbId = 1L;
126+
long tableId = 2L;
127+
long columnId = 3L;
128+
long initialNextId = 100L;
129+
130+
AutoIncrementGenerator generator = new AutoIncrementGenerator(dbId, tableId, columnId, initialNextId);
131+
EditLog mockEditLog = Mockito.mock(EditLog.class);
132+
generator.setEditLog(mockEditLog);
133+
134+
// First, get a range to trigger batchEndId update
135+
generator.getAutoIncrementRange(columnId, 10, 0);
136+
137+
// At this point, batchEndId should be > 0
138+
long batchEndId = getBatchEndId(generator);
139+
Assertions.assertTrue(batchEndId > 0, "batchEndId should be positive after getAutoIncrementRange");
140+
141+
// Serialize and deserialize to simulate checkpoint/restart
142+
String json = GsonUtils.GSON.toJson(generator);
143+
AutoIncrementGenerator deserializedGenerator = GsonUtils.GSON.fromJson(json, AutoIncrementGenerator.class);
144+
deserializedGenerator.setEditLog(mockEditLog);
145+
146+
// After deserialization, gsonPostProcess sets nextId = batchEndId
147+
// This should work correctly when batchEndId > 0
148+
Pair<Long, Long> range = deserializedGenerator.getAutoIncrementRange(columnId, 10, 0);
149+
150+
Assertions.assertTrue(range.first >= 0, "Start ID should be positive");
151+
Assertions.assertEquals(batchEndId, range.first.longValue(), "Start ID should equal previous batchEndId");
152+
}
153+
154+
/**
155+
* Helper method to access private batchEndId field via reflection
156+
*/
157+
private long getBatchEndId(AutoIncrementGenerator generator) {
158+
try {
159+
java.lang.reflect.Field field = AutoIncrementGenerator.class.getDeclaredField("batchEndId");
160+
field.setAccessible(true);
161+
return (long) field.get(generator);
162+
} catch (Exception e) {
163+
throw new RuntimeException("Failed to get batchEndId", e);
164+
}
165+
}
166+
167+
/**
168+
* Helper method to get auto-increment column ID from table
169+
*/
170+
private long getAutoIncrementColumnId(OlapTable table) {
171+
for (Column column : table.getBaseSchema()) {
172+
if (column.isAutoInc()) {
173+
return column.getUniqueId();
174+
}
175+
}
176+
throw new RuntimeException("No auto-increment column found in table");
177+
}
178+
}

0 commit comments

Comments
 (0)