Skip to content

Commit 5bfa9c6

Browse files
committed
fix(tdengine): 修复多属性聚合结果串值
1 parent b7af3c7 commit 5bfa9c6

2 files changed

Lines changed: 102 additions & 29 deletions

File tree

jetlinks-components/tdengine-component/src/main/java/org/jetlinks/community/tdengine/things/TDengineRowModeQueryOperations.java

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,14 @@
2525
import org.jetlinks.community.things.data.AggregationRequest;
2626
import org.jetlinks.community.things.data.PropertyAggregation;
2727
import org.jetlinks.community.things.data.ThingPropertyDetail;
28-
import org.jetlinks.community.things.data.ThingsDataConstants;
28+
import org.jetlinks.community.things.data.ThingsDataUtils;
2929
import org.jetlinks.community.things.data.operations.DataSettings;
3030
import org.jetlinks.community.things.data.operations.MetricBuilder;
3131
import org.jetlinks.community.things.data.operations.RowModeQueryOperationsBase;
3232
import org.jetlinks.community.timeseries.TimeSeriesData;
3333
import org.jetlinks.community.timeseries.query.Aggregation;
3434
import org.jetlinks.community.timeseries.query.AggregationData;
3535
import org.jetlinks.community.utils.SqlSecurityUtils;
36-
import org.jetlinks.reactor.ql.utils.CastUtils;
3736
import reactor.core.publisher.Flux;
3837
import reactor.core.publisher.Mono;
3938

@@ -44,6 +43,7 @@
4443
import java.time.format.DateTimeFormatter;
4544
import java.util.*;
4645
import java.util.function.Function;
46+
import java.util.stream.Collectors;
4747

4848
class TDengineRowModeQueryOperations extends RowModeQueryOperationsBase {
4949

@@ -138,36 +138,32 @@ protected Flux<AggregationData> doAggregation(String metric,
138138
.take(request.getLimit())
139139
;
140140
}
141+
NavigableMap<Long, Map<String, Object>> prepares =
142+
ThingsDataUtils.prepareAggregationData(request, properties);
143+
Map<String, List<PropertyAggregation>> propertyAgg = Arrays
144+
.stream(properties)
145+
.collect(Collectors.groupingBy(PropertyAggregation::getProperty));
141146
return helper
142147
.query(dataSql)
143-
.map(timeSeriesData -> {
144-
long ts = timeSeriesData.getTimestamp();
145-
Map<String, Object> newData = timeSeriesData.getData();
146-
newData.put("time", formatter.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneId.systemDefault())));
147-
newData.put("_time", ts);
148-
return newData;
149-
})
150-
.groupBy(data -> (String) data.get("time"), Integer.MAX_VALUE)
151-
.flatMap(group -> group
152-
.reduceWith(HashMap::new, (a, b) -> {
153-
a.putAll(b);
154-
return a;
155-
})
156-
.map(map -> {
157-
Map<String, Object> newResult = new HashMap<>();
158-
for (PropertyAggregation property : properties) {
159-
String alias = property.getAlias();
160-
String key = aliases.get(alias);
161-
newResult.put(alias, Optional.ofNullable(map.get(key)).orElse(property.getDefaultValue()));
148+
.doOnNext(data -> {
149+
long timestamp = data.getTimestamp();
150+
Map<String, Object> prepare = ThingsDataUtils.findAggregationData(timestamp, prepares);
151+
if (prepare != null) {
152+
Object propertyValue = data.getData().get("property");
153+
List<PropertyAggregation> proAggs = propertyValue == null
154+
? null
155+
: propertyAgg.get(propertyValue.toString());
156+
// 每个 partition 行都会计算全部投影,只消费当前 property 对应的聚合列。
157+
if (proAggs != null) {
158+
for (PropertyAggregation proAgg : proAggs) {
159+
String alias = proAgg.getAlias();
160+
prepare.put(alias, data.get(aliases.get(alias)).orElse(proAgg.getDefaultValue()));
161+
}
162162
}
163-
newResult.put("time", group.key());
164-
newResult.put("_time", map.getOrDefault("_time", new Date()));
165-
return AggregationData.of(newResult);
166-
}))
167-
.sort(Comparator
168-
.<AggregationData, Date>comparing(data -> CastUtils.castDate(data.values().get("_time")))
169-
.reversed())
170-
.doOnNext(data -> data.values().remove("_time"))
163+
}
164+
})
165+
.thenMany(Flux.fromIterable(prepares.descendingMap().values()))
166+
.map(AggregationData::of)
171167
.take(request.getLimit());
172168
}
173169

jetlinks-components/tdengine-component/src/test/java/org/jetlinks/community/tdengine/things/TDengineSqlSecurityTest.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,25 @@
1515
*/
1616
package org.jetlinks.community.tdengine.things;
1717

18+
import org.jetlinks.core.things.ThingMetadata;
19+
import org.jetlinks.community.things.data.AggregationRequest;
1820
import org.jetlinks.community.things.data.PropertyAggregation;
21+
import org.jetlinks.community.things.data.operations.DataSettings;
22+
import org.jetlinks.community.things.data.operations.MetricBuilder;
23+
import org.jetlinks.community.timeseries.TimeSeriesData;
1924
import org.jetlinks.community.timeseries.query.Aggregation;
25+
import org.jetlinks.community.timeseries.query.AggregationData;
2026
import org.junit.jupiter.api.Test;
27+
import reactor.core.publisher.Flux;
28+
29+
import java.util.List;
30+
import java.util.Map;
2131

2232
import static org.junit.jupiter.api.Assertions.assertEquals;
2333
import static org.junit.jupiter.api.Assertions.assertFalse;
34+
import static org.mockito.ArgumentMatchers.anyString;
35+
import static org.mockito.Mockito.mock;
36+
import static org.mockito.Mockito.when;
2437

2538
class TDengineSqlSecurityTest {
2639

@@ -64,4 +77,68 @@ void shouldEscapePropertyAndUseInternalAliasInColumnMode() {
6477
assertEquals("max(`temperature``), current_user --`) `__agg_1`", sql);
6578
assertFalse(sql.contains(aggregation.getAlias()));
6679
}
80+
81+
@Test
82+
void shouldMapAggregationAliasByPropertyPartition() {
83+
PropertyAggregation temperatureAvg = new PropertyAggregation(
84+
"temperature", "temperatureAvg", Aggregation.AVG);
85+
PropertyAggregation humidityMax = new PropertyAggregation(
86+
"humidity", "humidityMax", Aggregation.MAX);
87+
PropertyAggregation temperatureCount = new PropertyAggregation(
88+
"temperature", "temperatureCount", Aggregation.COUNT);
89+
90+
TDengineThingDataHelper helper = mock(TDengineThingDataHelper.class);
91+
when(helper.query(anyString())).thenReturn(Flux.just(
92+
TimeSeriesData.of(0, Map.of(
93+
"property", "temperature",
94+
"__agg_0", 20D,
95+
"__agg_1", 20D,
96+
"__agg_2", 2L
97+
)),
98+
TimeSeriesData.of(0, Map.of(
99+
"property", "humidity",
100+
"__agg_0", 80D,
101+
"__agg_1", 80D,
102+
"__agg_2", 3L
103+
))
104+
));
105+
106+
AggregationRequest request = AggregationRequest
107+
.builder()
108+
.interval(null)
109+
.limit(1)
110+
.build();
111+
112+
List<AggregationData> result = new TestOperations(helper)
113+
.aggregate(request, temperatureAvg, humidityMax, temperatureCount)
114+
.collectList()
115+
.block();
116+
117+
assertEquals(1, result.size(), String.valueOf(result));
118+
assertEquals(
119+
Map.of(
120+
"temperatureAvg", 20D,
121+
"humidityMax", 80D,
122+
"temperatureCount", 2L
123+
),
124+
result.get(0).asMap()
125+
);
126+
}
127+
128+
private static class TestOperations extends TDengineRowModeQueryOperations {
129+
130+
TestOperations(TDengineThingDataHelper helper) {
131+
super("device", "product", null,
132+
MetricBuilder.DEFAULT, new DataSettings(), null, helper);
133+
}
134+
135+
Flux<AggregationData> aggregate(AggregationRequest request,
136+
PropertyAggregation... properties) {
137+
return doAggregation(
138+
"device_properties_product",
139+
request,
140+
new AggregationContext(mock(ThingMetadata.class), properties)
141+
);
142+
}
143+
}
67144
}

0 commit comments

Comments
 (0)