Skip to content

Commit 8907f0c

Browse files
authored
Add unit tests for JDBC query DAO SQL building (#13800)
Add unit tests for SQL building in JDBCAlarmQueryDAO, JDBCLogQueryDAO, JDBCTraceQueryDAO, and JDBCTopologyQueryDAO, verifying correct WHERE clause construction, JOIN generation, parameter binding, and ORDER BY/LIMIT handling across various filter combinations.
1 parent 86b6d62 commit 8907f0c

File tree

4 files changed

+699
-0
lines changed

4 files changed

+699
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* 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, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package org.apache.skywalking.oap.server.storage.plugin.jdbc.common.dao;
20+
21+
import org.apache.skywalking.oap.server.core.alarm.AlarmRecord;
22+
import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
23+
import org.apache.skywalking.oap.server.core.query.input.Duration;
24+
import org.apache.skywalking.oap.server.library.client.jdbc.hikaricp.JDBCClient;
25+
import org.apache.skywalking.oap.server.library.module.ModuleManager;
26+
import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.JDBCTableInstaller;
27+
import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.SQLAndParameters;
28+
import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.TableHelper;
29+
import org.junit.jupiter.api.BeforeEach;
30+
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.api.extension.ExtendWith;
32+
import org.mockito.Mock;
33+
import org.mockito.junit.jupiter.MockitoExtension;
34+
import org.mockito.junit.jupiter.MockitoSettings;
35+
import org.mockito.quality.Strictness;
36+
37+
import java.util.Arrays;
38+
import java.util.Collections;
39+
import java.util.List;
40+
41+
import static org.assertj.core.api.Assertions.assertThat;
42+
43+
@ExtendWith(MockitoExtension.class)
44+
@MockitoSettings(strictness = Strictness.LENIENT)
45+
class JDBCAlarmQueryDAOTest {
46+
47+
private static final String TABLE = "alarm_record_20260406";
48+
private static final String TAG_TABLE = "alarm_record_tag_20260406";
49+
50+
@Mock
51+
private JDBCClient jdbcClient;
52+
@Mock
53+
private ModuleManager moduleManager;
54+
@Mock
55+
private TableHelper tableHelper;
56+
57+
private JDBCAlarmQueryDAO dao;
58+
59+
@BeforeEach
60+
void setUp() {
61+
dao = new JDBCAlarmQueryDAO(jdbcClient, moduleManager, tableHelper);
62+
}
63+
64+
@Test
65+
void buildSQL_shouldContainTableColumnConditionOnlyOnce() {
66+
final SQLAndParameters result = dao.buildSQL(null, null, 10, 0, null, null, TABLE);
67+
final String sql = result.sql();
68+
69+
final long count = countOccurrences(sql, JDBCTableInstaller.TABLE_COLUMN + " = ?");
70+
assertThat(count).as("TABLE_COLUMN condition should appear exactly once").isEqualTo(1);
71+
}
72+
73+
@Test
74+
void buildSQL_withNoConditions_shouldProduceMinimalQuery() {
75+
final SQLAndParameters result = dao.buildSQL(null, null, 10, 0, null, null, TABLE);
76+
final String sql = result.sql();
77+
78+
assertThat(sql).contains("select * from " + TABLE);
79+
assertThat(sql).contains("where " + TABLE + "." + JDBCTableInstaller.TABLE_COLUMN + " = ?");
80+
assertThat(sql).contains("order by " + AlarmRecord.START_TIME + " desc");
81+
assertThat(sql).contains("limit 10");
82+
assertThat(sql).doesNotContain("inner join");
83+
assertThat(sql).doesNotContain(AlarmRecord.SCOPE);
84+
assertThat(sql).doesNotContain("like");
85+
}
86+
87+
@Test
88+
void buildSQL_withScopeId_shouldIncludeScopeCondition() {
89+
final SQLAndParameters result = dao.buildSQL(1, null, 10, 0, null, null, TABLE);
90+
final String sql = result.sql();
91+
92+
assertThat(sql).contains("and " + AlarmRecord.SCOPE + " = ?");
93+
assertThat(result.parameters()).contains(1);
94+
}
95+
96+
@Test
97+
void buildSQL_withKeyword_shouldIncludeLikeCondition() {
98+
final SQLAndParameters result = dao.buildSQL(null, "error", 10, 0, null, null, TABLE);
99+
final String sql = result.sql();
100+
101+
assertThat(sql).contains("and " + AlarmRecord.ALARM_MESSAGE + " like concat('%',?,'%')");
102+
assertThat(result.parameters()).contains("error");
103+
}
104+
105+
@Test
106+
void buildSQL_withDuration_shouldIncludeTimeBucketConditions() {
107+
final Duration duration = new Duration();
108+
duration.setStart("2026-04-06 0000");
109+
duration.setEnd("2026-04-06 2359");
110+
duration.setStep(org.apache.skywalking.oap.server.core.query.enumeration.Step.MINUTE);
111+
112+
final SQLAndParameters result = dao.buildSQL(null, null, 10, 0, duration, null, TABLE);
113+
final String sql = result.sql();
114+
115+
assertThat(sql).contains("and " + TABLE + "." + AlarmRecord.TIME_BUCKET + " >= ?");
116+
assertThat(sql).contains("and " + TABLE + "." + AlarmRecord.TIME_BUCKET + " <= ?");
117+
}
118+
119+
@Test
120+
void buildSQL_withSingleTag_shouldUseInnerJoin() {
121+
final List<Tag> tags = Collections.singletonList(new Tag("env", "prod"));
122+
123+
final SQLAndParameters result = dao.buildSQL(null, null, 10, 0, null, tags, TABLE);
124+
final String sql = result.sql();
125+
126+
assertThat(sql).contains("inner join " + TAG_TABLE + " " + TAG_TABLE + "0");
127+
assertThat(sql).contains(TAG_TABLE + "0." + AlarmRecord.TAGS + " = ?");
128+
assertThat(result.parameters()).contains("env=prod");
129+
}
130+
131+
@Test
132+
void buildSQL_withMultipleTags_shouldUseMultipleInnerJoins() {
133+
final List<Tag> tags = Arrays.asList(new Tag("env", "prod"), new Tag("region", "us-east"));
134+
135+
final SQLAndParameters result = dao.buildSQL(null, null, 10, 0, null, tags, TABLE);
136+
final String sql = result.sql();
137+
138+
assertThat(sql).contains("inner join " + TAG_TABLE + " " + TAG_TABLE + "0");
139+
assertThat(sql).contains("inner join " + TAG_TABLE + " " + TAG_TABLE + "1");
140+
assertThat(sql).contains(TAG_TABLE + "0." + AlarmRecord.TAGS + " = ?");
141+
assertThat(sql).contains(TAG_TABLE + "1." + AlarmRecord.TAGS + " = ?");
142+
}
143+
144+
@Test
145+
void buildSQL_withLimitAndOffset_shouldApplyTotalAsLimit() {
146+
final SQLAndParameters result = dao.buildSQL(null, null, 20, 5, null, null, TABLE);
147+
final String sql = result.sql();
148+
149+
// JDBC uses offset+limit as the database LIMIT, then skips in application
150+
assertThat(sql).contains("limit 25");
151+
}
152+
153+
private long countOccurrences(final String text, final String pattern) {
154+
int count = 0;
155+
int index = 0;
156+
while ((index = text.indexOf(pattern, index)) != -1) {
157+
count++;
158+
index += pattern.length();
159+
}
160+
return count;
161+
}
162+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* 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, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package org.apache.skywalking.oap.server.storage.plugin.jdbc.common.dao;
20+
21+
import org.apache.skywalking.oap.server.core.analysis.manual.log.AbstractLogRecord;
22+
import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
23+
import org.apache.skywalking.oap.server.core.query.enumeration.Order;
24+
import org.apache.skywalking.oap.server.core.query.input.TraceScopeCondition;
25+
import org.apache.skywalking.oap.server.library.client.jdbc.hikaricp.JDBCClient;
26+
import org.apache.skywalking.oap.server.library.module.ModuleManager;
27+
import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.JDBCTableInstaller;
28+
import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.SQLAndParameters;
29+
import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.TableHelper;
30+
import org.junit.jupiter.api.BeforeEach;
31+
import org.junit.jupiter.api.Test;
32+
import org.junit.jupiter.api.extension.ExtendWith;
33+
import org.mockito.Mock;
34+
import org.mockito.junit.jupiter.MockitoExtension;
35+
import org.mockito.junit.jupiter.MockitoSettings;
36+
import org.mockito.quality.Strictness;
37+
38+
import java.util.Arrays;
39+
import java.util.Collections;
40+
import java.util.List;
41+
42+
import static org.assertj.core.api.Assertions.assertThat;
43+
44+
@ExtendWith(MockitoExtension.class)
45+
@MockitoSettings(strictness = Strictness.LENIENT)
46+
class JDBCLogQueryDAOTest {
47+
48+
private static final String TABLE = "log_20260406";
49+
private static final String TAG_TABLE = "log_tag_20260406";
50+
51+
@Mock
52+
private JDBCClient jdbcClient;
53+
@Mock
54+
private ModuleManager moduleManager;
55+
@Mock
56+
private TableHelper tableHelper;
57+
58+
private JDBCLogQueryDAO dao;
59+
60+
@BeforeEach
61+
void setUp() {
62+
dao = new JDBCLogQueryDAO(jdbcClient, moduleManager, tableHelper);
63+
}
64+
65+
@Test
66+
void buildSQL_shouldContainTableColumnConditionOnlyOnce() {
67+
final SQLAndParameters result = dao.buildSQL(
68+
null, null, null, null, Order.DES, 0, 10, null, null, null, null, TABLE);
69+
final String sql = result.sql();
70+
71+
final long count = countOccurrences(sql, JDBCTableInstaller.TABLE_COLUMN + " = ?");
72+
assertThat(count).as("TABLE_COLUMN condition should appear exactly once").isEqualTo(1);
73+
}
74+
75+
@Test
76+
void buildSQL_withNoConditions_shouldProduceMinimalQuery() {
77+
final SQLAndParameters result = dao.buildSQL(
78+
null, null, null, null, Order.DES, 0, 10, null, null, null, null, TABLE);
79+
final String sql = result.sql();
80+
81+
assertThat(sql).contains("select * from " + TABLE);
82+
assertThat(sql).contains("where " + JDBCTableInstaller.TABLE_COLUMN + " = ?");
83+
assertThat(sql).contains("order by " + AbstractLogRecord.TIMESTAMP + " desc");
84+
assertThat(sql).contains("limit 10");
85+
assertThat(sql).doesNotContain("inner join");
86+
}
87+
88+
@Test
89+
void buildSQL_withAscOrder_shouldProduceAscQuery() {
90+
final SQLAndParameters result = dao.buildSQL(
91+
null, null, null, null, Order.ASC, 0, 10, null, null, null, null, TABLE);
92+
final String sql = result.sql();
93+
94+
assertThat(sql).contains("order by " + AbstractLogRecord.TIMESTAMP + " asc");
95+
}
96+
97+
@Test
98+
void buildSQL_withServiceId_shouldIncludeServiceCondition() {
99+
final SQLAndParameters result = dao.buildSQL(
100+
"service-1", null, null, null, Order.DES, 0, 10, null, null, null, null, TABLE);
101+
final String sql = result.sql();
102+
103+
assertThat(sql).contains("and " + TABLE + "." + AbstractLogRecord.SERVICE_ID + " = ?");
104+
assertThat(result.parameters()).contains("service-1");
105+
}
106+
107+
@Test
108+
void buildSQL_withServiceInstanceId_shouldIncludeInstanceCondition() {
109+
final SQLAndParameters result = dao.buildSQL(
110+
null, "instance-1", null, null, Order.DES, 0, 10, null, null, null, null, TABLE);
111+
final String sql = result.sql();
112+
113+
assertThat(sql).contains("and " + AbstractLogRecord.SERVICE_INSTANCE_ID + " = ?");
114+
assertThat(result.parameters()).contains("instance-1");
115+
}
116+
117+
@Test
118+
void buildSQL_withEndpointId_shouldIncludeEndpointCondition() {
119+
final SQLAndParameters result = dao.buildSQL(
120+
null, null, "endpoint-1", null, Order.DES, 0, 10, null, null, null, null, TABLE);
121+
final String sql = result.sql();
122+
123+
assertThat(sql).contains("and " + AbstractLogRecord.ENDPOINT_ID + " = ?");
124+
assertThat(result.parameters()).contains("endpoint-1");
125+
}
126+
127+
@Test
128+
void buildSQL_withTraceId_shouldIncludeTraceCondition() {
129+
final TraceScopeCondition traceCondition = new TraceScopeCondition();
130+
traceCondition.setTraceId("trace-abc");
131+
132+
final SQLAndParameters result = dao.buildSQL(
133+
null, null, null, traceCondition, Order.DES, 0, 10, null, null, null, null, TABLE);
134+
final String sql = result.sql();
135+
136+
assertThat(sql).contains("and " + AbstractLogRecord.TRACE_ID + " = ?");
137+
assertThat(result.parameters()).contains("trace-abc");
138+
}
139+
140+
@Test
141+
void buildSQL_withSegmentIdAndSpanId_shouldIncludeBothConditions() {
142+
final TraceScopeCondition traceCondition = new TraceScopeCondition();
143+
traceCondition.setSegmentId("segment-abc");
144+
traceCondition.setSpanId(1);
145+
146+
final SQLAndParameters result = dao.buildSQL(
147+
null, null, null, traceCondition, Order.DES, 0, 10, null, null, null, null, TABLE);
148+
final String sql = result.sql();
149+
150+
assertThat(sql).contains("and " + AbstractLogRecord.TRACE_SEGMENT_ID + " = ?");
151+
assertThat(sql).contains("and " + AbstractLogRecord.SPAN_ID + " = ?");
152+
assertThat(result.parameters()).contains("segment-abc");
153+
assertThat(result.parameters()).contains(1);
154+
}
155+
156+
@Test
157+
void buildSQL_withSingleTag_shouldUseInnerJoin() {
158+
final List<Tag> tags = Collections.singletonList(new Tag("level", "ERROR"));
159+
160+
final SQLAndParameters result = dao.buildSQL(
161+
null, null, null, null, Order.DES, 0, 10, null, tags, null, null, TABLE);
162+
final String sql = result.sql();
163+
164+
assertThat(sql).contains("inner join " + TAG_TABLE + " " + TAG_TABLE + "0");
165+
assertThat(sql).contains(TAG_TABLE + "0." + AbstractLogRecord.TAGS + " = ?");
166+
assertThat(result.parameters()).contains("level=ERROR");
167+
}
168+
169+
@Test
170+
void buildSQL_withMultipleTags_shouldUseMultipleInnerJoins() {
171+
final List<Tag> tags = Arrays.asList(new Tag("level", "ERROR"), new Tag("service", "order"));
172+
173+
final SQLAndParameters result = dao.buildSQL(
174+
null, null, null, null, Order.DES, 0, 10, null, tags, null, null, TABLE);
175+
final String sql = result.sql();
176+
177+
assertThat(sql).contains("inner join " + TAG_TABLE + " " + TAG_TABLE + "0");
178+
assertThat(sql).contains("inner join " + TAG_TABLE + " " + TAG_TABLE + "1");
179+
assertThat(sql).contains(TAG_TABLE + "0." + AbstractLogRecord.TAGS + " = ?");
180+
assertThat(sql).contains(TAG_TABLE + "1." + AbstractLogRecord.TAGS + " = ?");
181+
}
182+
183+
@Test
184+
void buildSQL_withLimitAndOffset_shouldApplyTotalAsLimit() {
185+
final SQLAndParameters result = dao.buildSQL(
186+
null, null, null, null, Order.DES, 5, 20, null, null, null, null, TABLE);
187+
final String sql = result.sql();
188+
189+
assertThat(sql).contains("limit 25");
190+
}
191+
192+
private long countOccurrences(final String text, final String pattern) {
193+
int count = 0;
194+
int index = 0;
195+
while ((index = text.indexOf(pattern, index)) != -1) {
196+
count++;
197+
index += pattern.length();
198+
}
199+
return count;
200+
}
201+
}

0 commit comments

Comments
 (0)