Skip to content

Commit 1b51c19

Browse files
Duansgtomsun28
andauthored
[fix]fixed sql formatting error (#4008)
Co-authored-by: Tomsun28 <tomsun28@outlook.com>
1 parent a83cb0f commit 1b51c19

3 files changed

Lines changed: 168 additions & 4 deletions

File tree

hertzbeat-common/src/main/java/org/apache/hertzbeat/common/util/StrBuffer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,8 @@ public static double parseDouble(String s) {
159159
}
160160
return Double.parseDouble(s);
161161
}
162+
163+
public static String escapeForFormat(String value) {
164+
return value.replace("%", "%%");
165+
}
162166
}

hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/tdengine/TdEngineDataStorage.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.apache.hertzbeat.common.entity.dto.Value;
4848
import org.apache.hertzbeat.common.entity.message.CollectRep;
4949
import org.apache.hertzbeat.common.util.JsonUtil;
50+
import org.apache.hertzbeat.common.util.StrBuffer;
5051
import org.apache.hertzbeat.warehouse.store.history.tsdb.AbstractHistoryDataStorage;
5152
import org.jetbrains.annotations.NotNull;
5253
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -221,7 +222,12 @@ public void saveData(CollectRep.MetricsData metricsData) {
221222
} else {
222223
try {
223224
double number = Double.parseDouble(value);
224-
sqlRowBuffer.append(number);
225+
// TDengine doesn't support NaN or Infinity literals, convert to NULL
226+
if (Double.isNaN(number) || Double.isInfinite(number)) {
227+
sqlRowBuffer.append("NULL");
228+
} else {
229+
sqlRowBuffer.append(number);
230+
}
225231
} catch (Exception e) {
226232

227233
if (log.isWarnEnabled()) {
@@ -236,7 +242,7 @@ public void saveData(CollectRep.MetricsData metricsData) {
236242
if (CommonConstants.NULL_VALUE.equals(value)) {
237243
sqlRowBuffer.append("NULL");
238244
} else {
239-
sqlRowBuffer.append("'").append(formatStringValue(value)).append("'");
245+
sqlRowBuffer.append("'").append(StrBuffer.escapeForFormat(formatStringValue(value))).append("'");
240246
}
241247
}
242248

hertzbeat-warehouse/src/test/java/org/apache/hertzbeat/warehouse/store/TdEngineDataStorageTest.java

Lines changed: 156 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,102 @@
1717

1818
package org.apache.hertzbeat.warehouse.store;
1919

20+
import com.zaxxer.hikari.HikariDataSource;
21+
import org.apache.arrow.vector.types.pojo.ArrowType;
22+
import org.apache.arrow.vector.types.pojo.Field;
23+
import org.apache.arrow.vector.types.pojo.FieldType;
24+
import org.apache.hertzbeat.common.constants.CommonConstants;
25+
import org.apache.hertzbeat.common.constants.MetricDataConstants;
26+
import org.apache.hertzbeat.common.entity.arrow.ArrowCell;
27+
import org.apache.hertzbeat.common.entity.arrow.RowWrapper;
28+
import org.apache.hertzbeat.common.entity.message.CollectRep;
2029
import org.apache.hertzbeat.warehouse.store.history.tsdb.tdengine.TdEngineDataStorage;
30+
import org.apache.hertzbeat.warehouse.store.history.tsdb.tdengine.TdEngineProperties;
2131
import org.junit.jupiter.api.BeforeEach;
2232
import org.junit.jupiter.api.Test;
33+
import org.junit.jupiter.api.extension.ExtendWith;
34+
import org.mockito.ArgumentCaptor;
35+
import org.mockito.Mock;
36+
import org.mockito.Mockito;
37+
import org.mockito.junit.jupiter.MockitoExtension;
38+
import org.mockito.junit.jupiter.MockitoSettings;
39+
import org.mockito.quality.Strictness;
40+
41+
import java.sql.Connection;
42+
import java.sql.Statement;
43+
import java.util.List;
44+
45+
import static org.junit.jupiter.api.Assertions.assertNotNull;
46+
import static org.junit.jupiter.api.Assertions.assertTrue;
47+
import static org.mockito.Mockito.atLeastOnce;
48+
import static org.mockito.Mockito.verify;
49+
import static org.mockito.Mockito.when;
2350

2451
/**
2552
* Test case for {@link TdEngineDataStorage}
2653
*/
54+
@ExtendWith(MockitoExtension.class)
55+
@MockitoSettings(strictness = Strictness.LENIENT)
2756
class TdEngineDataStorageTest {
2857

58+
@Mock
59+
private TdEngineProperties tdEngineProperties;
60+
61+
@Mock
62+
private HikariDataSource mockHikariDataSource;
63+
64+
@Mock
65+
private Connection mockConnection;
66+
67+
@Mock
68+
private Statement mockStatement;
69+
70+
private TdEngineDataStorage tdEngineDataStorage;
71+
2972
@BeforeEach
30-
void setUp() {
73+
void setUp() throws Exception {
74+
when(tdEngineProperties.enabled()).thenReturn(true);
75+
when(tdEngineProperties.url()).thenReturn("jdbc:TAOS-RS://localhost:6041/demo");
76+
when(tdEngineProperties.username()).thenReturn("root");
77+
when(tdEngineProperties.password()).thenReturn("root");
78+
when(tdEngineProperties.tableStrColumnDefineMaxLength()).thenReturn(200);
79+
when(mockHikariDataSource.getConnection()).thenReturn(mockConnection);
80+
when(mockConnection.createStatement()).thenReturn(mockStatement);
3181
}
3282

3383
@Test
3484
void isServerAvailable() {
3585
}
3686

3787
@Test
38-
void saveData() {
88+
void testSaveData() throws Exception {
89+
tdEngineDataStorage = new TdEngineDataStorage(tdEngineProperties);
90+
setPrivateField(tdEngineDataStorage, "hikariDataSource", mockHikariDataSource);
91+
setParentPrivateField(tdEngineDataStorage, "serverAvailable", true);
92+
93+
CollectRep.MetricsData metricsData = generateMockedMetricsData();
94+
tdEngineDataStorage.saveData(metricsData);
95+
96+
ArgumentCaptor<String> sqlCaptor = ArgumentCaptor.forClass(String.class);
97+
verify(mockStatement, atLeastOnce()).execute(sqlCaptor.capture());
98+
99+
String executedSql = sqlCaptor.getValue();
100+
101+
// Verify SQL structure
102+
assertNotNull(executedSql);
103+
assertTrue(executedSql.startsWith("INSERT INTO"), "SQL should start with INSERT INTO");
104+
assertTrue(executedSql.contains("USING"), "SQL should contain USING clause");
105+
assertTrue(executedSql.contains("TAGS"), "SQL should contain TAGS clause");
106+
assertTrue(executedSql.contains("VALUES"), "SQL should contain VALUES clause");
107+
108+
// Verify table name format: app_metrics_instance_v2
109+
assertTrue(executedSql.contains("app_cpu_test-%server-01_v2"), "Should contain correct table name");
110+
// Verify super table name format: app_metrics_super_v2
111+
assertTrue(executedSql.contains("app_cpu_super_v2"), "Should contain correct super table name");
112+
// Verify tags format: test-%server-01
113+
assertTrue(executedSql.contains("TAGS ('test-%server-01')"), "Should contain correct super table name");
114+
// Verify VALUES clause structure (timestamp + data values)
115+
assertTrue(executedSql.matches(".*VALUES\\s+\\(\\d+.*68\\.7\\)"), "Should contain timestamp and value 68.7");
39116
}
40117

41118
@Test
@@ -49,4 +126,81 @@ void getHistoryMetricData() {
49126
@Test
50127
void getHistoryIntervalMetricData() {
51128
}
129+
130+
/**
131+
* Helper method to set private field using reflection
132+
*/
133+
private void setPrivateField(Object target, String fieldName, Object value) throws Exception {
134+
java.lang.reflect.Field field = target.getClass().getDeclaredField(fieldName);
135+
field.setAccessible(true);
136+
field.set(target, value);
137+
}
138+
139+
/**
140+
* Helper method to set private field from parent class using reflection
141+
*/
142+
private void setParentPrivateField(Object target, String fieldName, Object value) throws Exception {
143+
java.lang.reflect.Field field = target.getClass().getSuperclass().getDeclaredField(fieldName);
144+
field.setAccessible(true);
145+
field.set(target, value);
146+
}
147+
148+
public static CollectRep.MetricsData generateMockedMetricsData() {
149+
CollectRep.MetricsData mockMetricsData = Mockito.mock(CollectRep.MetricsData.class);
150+
151+
when(mockMetricsData.getId()).thenReturn(0L);
152+
when(mockMetricsData.getMetrics()).thenReturn("cpu");
153+
when(mockMetricsData.getTime()).thenReturn(System.currentTimeMillis());
154+
when(mockMetricsData.getCode()).thenReturn(CollectRep.Code.SUCCESS);
155+
when(mockMetricsData.getApp()).thenReturn("app");
156+
when(mockMetricsData.getInstance()).thenReturn("test-%server-01");
157+
158+
CollectRep.ValueRow mockValueRow = Mockito.mock(CollectRep.ValueRow.class);
159+
List<String> columnValues = List.of("test-%server-01", "68.7");
160+
when(mockValueRow.getColumnsList()).thenReturn(columnValues);
161+
when(mockValueRow.getColumns(0)).thenReturn("test-%server-01");
162+
when(mockValueRow.getColumns(1)).thenReturn("68.7");
163+
List<CollectRep.ValueRow> mockValueRowsList = List.of(mockValueRow);
164+
when(mockMetricsData.getValues()).thenReturn(mockValueRowsList);
165+
166+
CollectRep.Field instanceField = Mockito.mock(CollectRep.Field.class);
167+
when(instanceField.getName()).thenReturn("instance");
168+
CollectRep.Field usageField = Mockito.mock(CollectRep.Field.class);
169+
when(usageField.getName()).thenReturn("usage");
170+
CollectRep.Field systemField = Mockito.mock(CollectRep.Field.class);
171+
when(systemField.getName()).thenReturn("system");
172+
List<CollectRep.Field> mockFields = List.of(instanceField, usageField, systemField);
173+
when(mockMetricsData.getFields()).thenReturn(mockFields);
174+
175+
ArrowType instanceArrowType = new ArrowType.Utf8();
176+
FieldType instanceFieldType = new FieldType(true, instanceArrowType, null, null);
177+
Field instanceArrowField = new Field("instance", instanceFieldType, null);
178+
ArrowCell instanceCell = Mockito.mock(ArrowCell.class);
179+
when(instanceCell.getField()).thenReturn(instanceArrowField);
180+
when(instanceCell.getValue()).thenReturn("test-%server-01");
181+
when(instanceCell.getMetadataAsBoolean(MetricDataConstants.LABEL)).thenReturn(true);
182+
when(instanceCell.getMetadataAsByte(MetricDataConstants.TYPE)).thenReturn(CommonConstants.TYPE_STRING);
183+
184+
ArrowType usageArrowType = new ArrowType.Utf8();
185+
FieldType usageFieldType = new FieldType(true, usageArrowType, null, null);
186+
Field usageArrowField = new Field("usage", usageFieldType, null);
187+
ArrowCell usageCell = Mockito.mock(ArrowCell.class);
188+
when(usageCell.getField()).thenReturn(usageArrowField);
189+
when(usageCell.getValue()).thenReturn("68.7");
190+
when(usageCell.getMetadataAsBoolean(MetricDataConstants.LABEL)).thenReturn(false);
191+
when(usageCell.getMetadataAsByte(MetricDataConstants.TYPE)).thenReturn(CommonConstants.TYPE_NUMBER);
192+
List<ArrowCell> mockCells = List.of(instanceCell, usageCell);
193+
194+
// Create Arrow Field list for RowWrapper
195+
List<org.apache.arrow.vector.types.pojo.Field> arrowFields = List.of(instanceArrowField, usageArrowField);
196+
197+
RowWrapper mockRowWrapper = Mockito.mock(RowWrapper.class);
198+
when(mockRowWrapper.hasNextRow()).thenReturn(true).thenReturn(false);
199+
when(mockRowWrapper.nextRow()).thenReturn(mockRowWrapper);
200+
when(mockRowWrapper.cellStream()).thenAnswer(invocation -> mockCells.stream());
201+
when(mockRowWrapper.getFieldList()).thenReturn(arrowFields);
202+
when(mockMetricsData.readRow()).thenReturn(mockRowWrapper);
203+
return mockMetricsData;
204+
}
205+
52206
}

0 commit comments

Comments
 (0)