New API for Agg requests
This commit is contained in:
		
							parent
							
								
									fa9f1b9f69
								
							
						
					
					
						commit
						7cc2943d18
					
				@ -22,7 +22,6 @@ import com.google.common.util.concurrent.MoreExecutors;
 | 
				
			|||||||
import lombok.Getter;
 | 
					import lombok.Getter;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.checkerframework.checker.nullness.qual.Nullable;
 | 
					import org.checkerframework.checker.nullness.qual.Nullable;
 | 
				
			||||||
import org.jetbrains.annotations.NotNull;
 | 
					 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Value;
 | 
					import org.springframework.beans.factory.annotation.Value;
 | 
				
			||||||
import org.springframework.context.annotation.Lazy;
 | 
					import org.springframework.context.annotation.Lazy;
 | 
				
			||||||
@ -30,8 +29,7 @@ import org.springframework.scheduling.annotation.Scheduled;
 | 
				
			|||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
import org.springframework.web.socket.CloseStatus;
 | 
					import org.springframework.web.socket.CloseStatus;
 | 
				
			||||||
import org.thingsboard.common.util.ThingsBoardThreadFactory;
 | 
					import org.thingsboard.common.util.ThingsBoardThreadFactory;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.CustomerId;
 | 
					import org.thingsboard.server.common.data.kv.Aggregation;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
					 | 
				
			||||||
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
 | 
					import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
 | 
				
			||||||
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 | 
					import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 | 
				
			||||||
import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult;
 | 
					import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult;
 | 
				
			||||||
@ -52,6 +50,9 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
 | 
				
			|||||||
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
 | 
					import org.thingsboard.server.service.executors.DbCallbackExecutorService;
 | 
				
			||||||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
 | 
					import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
 | 
				
			||||||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
 | 
					import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.telemetry.cmd.v2.AggHistoryCmd;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.telemetry.cmd.v2.AggKey;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.telemetry.cmd.v2.AggTimeSeriesCmd;
 | 
				
			||||||
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
 | 
					import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
 | 
				
			||||||
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate;
 | 
					import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate;
 | 
				
			||||||
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
 | 
					import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
 | 
				
			||||||
@ -69,7 +70,6 @@ import javax.annotation.PreDestroy;
 | 
				
			|||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.HashMap;
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.LinkedHashMap;
 | 
					 | 
				
			||||||
import java.util.LinkedHashSet;
 | 
					import java.util.LinkedHashSet;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
@ -133,6 +133,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
 | 
				
			|||||||
    private int maxEntitiesPerAlarmSubscription;
 | 
					    private int maxEntitiesPerAlarmSubscription;
 | 
				
			||||||
    @Value("${server.ws.dynamic_page_link.max_alarm_queries_per_refresh_interval:10}")
 | 
					    @Value("${server.ws.dynamic_page_link.max_alarm_queries_per_refresh_interval:10}")
 | 
				
			||||||
    private int maxAlarmQueriesPerRefreshInterval;
 | 
					    private int maxAlarmQueriesPerRefreshInterval;
 | 
				
			||||||
 | 
					    @Value("${ui.dashboard.max_datapoints_limit:50000}")
 | 
				
			||||||
 | 
					    private int maxDatapointLimit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private ExecutorService wsCallBackExecutor;
 | 
					    private ExecutorService wsCallBackExecutor;
 | 
				
			||||||
    private boolean tsInSqlDB;
 | 
					    private boolean tsInSqlDB;
 | 
				
			||||||
@ -167,7 +169,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
 | 
				
			|||||||
        TbEntityDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
 | 
					        TbEntityDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
 | 
				
			||||||
        if (ctx != null) {
 | 
					        if (ctx != null) {
 | 
				
			||||||
            log.debug("[{}][{}] Updating existing subscriptions using: {}", session.getSessionId(), cmd.getCmdId(), cmd);
 | 
					            log.debug("[{}][{}] Updating existing subscriptions using: {}", session.getSessionId(), cmd.getCmdId(), cmd);
 | 
				
			||||||
            if (cmd.getLatestCmd() != null || cmd.getTsCmd() != null || cmd.getHistoryCmd() != null) {
 | 
					            if (cmd.hasAnyCmd()) {
 | 
				
			||||||
                ctx.clearEntitySubscriptions();
 | 
					                ctx.clearEntitySubscriptions();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
@ -206,6 +208,18 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
 | 
				
			|||||||
                finalCtx.setRefreshTask(task);
 | 
					                finalCtx.setRefreshTask(task);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (cmd.getAggHistoryCmd() != null) {
 | 
				
			||||||
 | 
					            handleAggHistoryCmd(session, ctx, cmd.getAggHistoryCmd());
 | 
				
			||||||
 | 
					        } else if (cmd.getAggTsCmd() != null) {
 | 
				
			||||||
 | 
					            handleAggTsCmd(session, ctx, cmd.getAggTsCmd());
 | 
				
			||||||
 | 
					        } else if (cmd.hasRegularCmds()) {
 | 
				
			||||||
 | 
					            handleRegularCommands(session, ctx, cmd);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            checkAndSendInitialData(ctx);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void handleRegularCommands(TelemetryWebSocketSessionRef session, TbEntityDataSubCtx ctx, EntityDataCmd cmd) {
 | 
				
			||||||
        ListenableFuture<TbEntityDataSubCtx> historyFuture;
 | 
					        ListenableFuture<TbEntityDataSubCtx> historyFuture;
 | 
				
			||||||
        if (cmd.getHistoryCmd() != null) {
 | 
					        if (cmd.getHistoryCmd() != null) {
 | 
				
			||||||
            log.trace("[{}][{}] Going to process history command: {}", session.getSessionId(), cmd.getCmdId(), cmd.getHistoryCmd());
 | 
					            log.trace("[{}][{}] Going to process history command: {}", session.getSessionId(), cmd.getCmdId(), cmd.getHistoryCmd());
 | 
				
			||||||
@ -229,10 +243,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
 | 
				
			|||||||
                        if (cmd.getTsCmd() != null) {
 | 
					                        if (cmd.getTsCmd() != null) {
 | 
				
			||||||
                            handleTimeSeriesCmd(theCtx, cmd.getTsCmd());
 | 
					                            handleTimeSeriesCmd(theCtx, cmd.getTsCmd());
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    } else if (!theCtx.isInitialDataSent()) {
 | 
					                    } else {
 | 
				
			||||||
                        EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null, theCtx.getMaxEntitiesPerDataSubscription());
 | 
					                        checkAndSendInitialData(theCtx);
 | 
				
			||||||
                        theCtx.sendWsMsg(update);
 | 
					 | 
				
			||||||
                        theCtx.setInitialDataSent(true);
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } catch (RuntimeException e) {
 | 
					                } catch (RuntimeException e) {
 | 
				
			||||||
                    handleWsCmdRuntimeException(theCtx.getSessionId(), e, cmd);
 | 
					                    handleWsCmdRuntimeException(theCtx.getSessionId(), e, cmd);
 | 
				
			||||||
@ -246,6 +258,94 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
 | 
				
			|||||||
        }, wsCallBackExecutor);
 | 
					        }, wsCallBackExecutor);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void checkAndSendInitialData(@Nullable TbEntityDataSubCtx theCtx) {
 | 
				
			||||||
 | 
					        if (!theCtx.isInitialDataSent()) {
 | 
				
			||||||
 | 
					            EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null, theCtx.getMaxEntitiesPerDataSubscription());
 | 
				
			||||||
 | 
					            theCtx.sendWsMsg(update);
 | 
				
			||||||
 | 
					            theCtx.setInitialDataSent(true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void handleAggHistoryCmd(TelemetryWebSocketSessionRef session, TbEntityDataSubCtx ctx, AggHistoryCmd cmd) {
 | 
				
			||||||
 | 
					        var keys = cmd.getKeys();
 | 
				
			||||||
 | 
					        long interval = cmd.getEndTs() - cmd.getStartTs();
 | 
				
			||||||
 | 
					        List<ReadTsKvQuery> queries = keys.stream().map(key -> new BaseReadTsKvQuery(
 | 
				
			||||||
 | 
					                key.getKey(), cmd.getStartTs(), cmd.getEndTs(), interval, 1, key.getAgg()
 | 
				
			||||||
 | 
					        )).distinct().collect(Collectors.toList());
 | 
				
			||||||
 | 
					        handleAggCmd(session, ctx, cmd.getKeys(), queries, cmd.getStartTs(), cmd.getEndTs(), false, false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void handleAggTsCmd(TelemetryWebSocketSessionRef session, TbEntityDataSubCtx ctx, AggTimeSeriesCmd cmd) {
 | 
				
			||||||
 | 
					        long endTs = cmd.getStartTs() + cmd.getTimeWindow();
 | 
				
			||||||
 | 
					        List<ReadTsKvQuery> queries = cmd.getKeys().stream().map(key -> {
 | 
				
			||||||
 | 
					            if (cmd.isFloating()) {
 | 
				
			||||||
 | 
					                return new BaseReadTsKvQuery(key.getKey(), cmd.getStartTs(), endTs, cmd.getTimeWindow(), getLimit(maxDatapointLimit), Aggregation.NONE);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return new BaseReadTsKvQuery(key.getKey(), cmd.getStartTs(), endTs, cmd.getTimeWindow(), 1, key.getAgg());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }).distinct().collect(Collectors.toList());
 | 
				
			||||||
 | 
					        handleAggCmd(session, ctx, cmd.getKeys(), queries, cmd.getStartTs(), endTs, cmd.isFloating(), true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void handleAggCmd(TelemetryWebSocketSessionRef session, TbEntityDataSubCtx ctx, List<AggKey> keys, List<ReadTsKvQuery> queries,
 | 
				
			||||||
 | 
					                              long startTs, long endTs, boolean floating, boolean subscribe) {
 | 
				
			||||||
 | 
					        Map<EntityData, ListenableFuture<List<ReadTsKvQueryResult>>> fetchResultMap = new HashMap<>();
 | 
				
			||||||
 | 
					        List<EntityData> entityDataList = ctx.getData().getData();
 | 
				
			||||||
 | 
					        entityDataList.forEach(entityData -> fetchResultMap.put(entityData,
 | 
				
			||||||
 | 
					                tsService.findAllByQueries(ctx.getTenantId(), entityData.getEntityId(), queries)));
 | 
				
			||||||
 | 
					        Futures.transform(Futures.allAsList(fetchResultMap.values()), f -> {
 | 
				
			||||||
 | 
					            // Map that holds last ts for each key for each entity.
 | 
				
			||||||
 | 
					            Map<EntityData, Map<String, Long>> lastTsEntityMap = new HashMap<>();
 | 
				
			||||||
 | 
					            fetchResultMap.forEach((entityData, future) -> {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    Map<String, Long> lastTsMap = new HashMap<>();
 | 
				
			||||||
 | 
					                    lastTsEntityMap.put(entityData, lastTsMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    List<ReadTsKvQueryResult> queryResults = future.get();
 | 
				
			||||||
 | 
					                    if (queryResults != null) {
 | 
				
			||||||
 | 
					                        for (ReadTsKvQueryResult queryResult : queryResults) {
 | 
				
			||||||
 | 
					                            if (floating) {
 | 
				
			||||||
 | 
					                                entityData.getAggFloating().put(queryResult.getKey(), queryResult.toTsValues());
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                entityData.getAggLatest().computeIfAbsent(queryResult.getAgg(), agg -> new HashMap<>()).put(queryResult.getKey(), queryResult.toTsValue());
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            lastTsMap.put(queryResult.getKey(), queryResult.getLastEntryTs());
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    // Populate with empty values if no data found.
 | 
				
			||||||
 | 
					                    keys.forEach(key -> {
 | 
				
			||||||
 | 
					                        if (floating) {
 | 
				
			||||||
 | 
					                            entityData.getAggFloating().putIfAbsent(key.getKey(), new TsValue[]{TsValue.EMPTY});
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            entityData.getAggLatest().computeIfAbsent(key.getAgg(), agg -> new HashMap<>()).putIfAbsent(key.getKey(), TsValue.EMPTY);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                } catch (InterruptedException | ExecutionException e) {
 | 
				
			||||||
 | 
					                    log.warn("[{}][{}][{}] Failed to fetch historical data", ctx.getSessionId(), ctx.getCmdId(), entityData.getEntityId(), e);
 | 
				
			||||||
 | 
					                    ctx.sendWsMsg(new EntityDataUpdate(ctx.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR.getCode(), "Failed to fetch historical data!"));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            ctx.getWsLock().lock();
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                EntityDataUpdate update;
 | 
				
			||||||
 | 
					                if (!ctx.isInitialDataSent()) {
 | 
				
			||||||
 | 
					                    update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
 | 
				
			||||||
 | 
					                    ctx.setInitialDataSent(true);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    update = new EntityDataUpdate(ctx.getCmdId(), null, entityDataList, ctx.getMaxEntitiesPerDataSubscription());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (subscribe) {
 | 
				
			||||||
 | 
					                    ctx.createTimeSeriesSubscriptions(lastTsEntityMap, startTs, endTs, true);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                ctx.sendWsMsg(update);
 | 
				
			||||||
 | 
					                entityDataList.forEach(ed -> ed.getTimeseries().clear());
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
 | 
					                ctx.getWsLock().unlock();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return ctx;
 | 
				
			||||||
 | 
					        }, wsCallBackExecutor);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void handleWsCmdRuntimeException(String sessionId, RuntimeException e, EntityDataCmd cmd) {
 | 
					    private void handleWsCmdRuntimeException(String sessionId, RuntimeException e, EntityDataCmd cmd) {
 | 
				
			||||||
        log.debug("[{}] Failed to process ws cmd: {}", sessionId, cmd, e);
 | 
					        log.debug("[{}] Failed to process ws cmd: {}", sessionId, cmd, e);
 | 
				
			||||||
        wsService.close(sessionId, CloseStatus.SERVICE_RESTARTED);
 | 
					        wsService.close(sessionId, CloseStatus.SERVICE_RESTARTED);
 | 
				
			||||||
@ -420,12 +520,12 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
 | 
				
			|||||||
    private ListenableFuture<TbEntityDataSubCtx> handleGetTsCmd(TbEntityDataSubCtx ctx, GetTsCmd cmd, boolean subscribe) {
 | 
					    private ListenableFuture<TbEntityDataSubCtx> handleGetTsCmd(TbEntityDataSubCtx ctx, GetTsCmd cmd, boolean subscribe) {
 | 
				
			||||||
        List<String> keys = cmd.getKeys();
 | 
					        List<String> keys = cmd.getKeys();
 | 
				
			||||||
        List<ReadTsKvQuery> finalTsKvQueryList;
 | 
					        List<ReadTsKvQuery> finalTsKvQueryList;
 | 
				
			||||||
        List<ReadTsKvQuery> tsKvQueryList = cmd.getKeys().stream().map(key -> new BaseReadTsKvQuery(
 | 
					        List<ReadTsKvQuery> tsKvQueryList = keys.stream().map(key -> new BaseReadTsKvQuery(
 | 
				
			||||||
                key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), cmd.getAgg()
 | 
					                key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), cmd.getAgg()
 | 
				
			||||||
        )).collect(Collectors.toList());
 | 
					        )).collect(Collectors.toList());
 | 
				
			||||||
        if (cmd.isFetchLatestPreviousPoint()) {
 | 
					        if (cmd.isFetchLatestPreviousPoint()) {
 | 
				
			||||||
            finalTsKvQueryList = new ArrayList<>(tsKvQueryList);
 | 
					            finalTsKvQueryList = new ArrayList<>(tsKvQueryList);
 | 
				
			||||||
            finalTsKvQueryList.addAll(cmd.getKeys().stream().map(key -> new BaseReadTsKvQuery(
 | 
					            finalTsKvQueryList.addAll(keys.stream().map(key -> new BaseReadTsKvQuery(
 | 
				
			||||||
                    key, cmd.getStartTs() - TimeUnit.DAYS.toMillis(365), cmd.getStartTs(), cmd.getInterval(), 1, cmd.getAgg()
 | 
					                    key, cmd.getStartTs() - TimeUnit.DAYS.toMillis(365), cmd.getStartTs(), cmd.getInterval(), 1, cmd.getAgg()
 | 
				
			||||||
            )).collect(Collectors.toList()));
 | 
					            )).collect(Collectors.toList()));
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
@ -451,7 +551,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
 | 
				
			|||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    // Populate with empty values if no data found.
 | 
					                    // Populate with empty values if no data found.
 | 
				
			||||||
                    cmd.getKeys().forEach(key -> {
 | 
					                    keys.forEach(key -> {
 | 
				
			||||||
                        if (!entityData.getTimeseries().containsKey(key)) {
 | 
					                        if (!entityData.getTimeseries().containsKey(key)) {
 | 
				
			||||||
                            entityData.getTimeseries().put(key, new TsValue[0]);
 | 
					                            entityData.getTimeseries().put(key, new TsValue[0]);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
@ -475,7 +575,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
 | 
				
			|||||||
                    update = new EntityDataUpdate(ctx.getCmdId(), null, entityDataList, ctx.getMaxEntitiesPerDataSubscription());
 | 
					                    update = new EntityDataUpdate(ctx.getCmdId(), null, entityDataList, ctx.getMaxEntitiesPerDataSubscription());
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if (subscribe) {
 | 
					                if (subscribe) {
 | 
				
			||||||
                    ctx.createTimeseriesSubscriptions(lastTsEntityMap, cmd.getStartTs(), cmd.getEndTs());
 | 
					                    ctx.createTimeSeriesSubscriptions(lastTsEntityMap, cmd.getStartTs(), cmd.getEndTs());
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                ctx.sendWsMsg(update);
 | 
					                ctx.sendWsMsg(update);
 | 
				
			||||||
                entityDataList.forEach(ed -> ed.getTimeseries().clear());
 | 
					                entityDataList.forEach(ed -> ed.getTimeseries().clear());
 | 
				
			||||||
@ -546,11 +646,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
 | 
				
			|||||||
            ctx.getWsLock().lock();
 | 
					            ctx.getWsLock().lock();
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                ctx.createLatestValuesSubscriptions(latestCmd.getKeys());
 | 
					                ctx.createLatestValuesSubscriptions(latestCmd.getKeys());
 | 
				
			||||||
                if (!ctx.isInitialDataSent()) {
 | 
					                checkAndSendInitialData(ctx);
 | 
				
			||||||
                    EntityDataUpdate update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
 | 
					 | 
				
			||||||
                    ctx.sendWsMsg(update);
 | 
					 | 
				
			||||||
                    ctx.setInitialDataSent(true);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } finally {
 | 
					            } finally {
 | 
				
			||||||
                ctx.getWsLock().unlock();
 | 
					                ctx.getWsLock().unlock();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -122,12 +122,16 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
 | 
				
			|||||||
        createSubscriptions(keys, true, 0, 0);
 | 
					        createSubscriptions(keys, true, 0, 0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void createTimeseriesSubscriptions(Map<EntityData, Map<String, Long>> entityKeyStates, long startTs, long endTs) {
 | 
					    public void createTimeSeriesSubscriptions(Map<EntityData, Map<String, Long>> entityKeyStates, long startTs, long endTs) {
 | 
				
			||||||
 | 
					        createTimeSeriesSubscriptions(entityKeyStates, startTs, endTs, false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void createTimeSeriesSubscriptions(Map<EntityData, Map<String, Long>> entityKeyStates, long startTs, long endTs, boolean resultToLatestValues) {
 | 
				
			||||||
        entityKeyStates.forEach((entityData, keyStates) -> {
 | 
					        entityKeyStates.forEach((entityData, keyStates) -> {
 | 
				
			||||||
            int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet();
 | 
					            int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet();
 | 
				
			||||||
            subToEntityIdMap.put(subIdx, entityData.getEntityId());
 | 
					            subToEntityIdMap.put(subIdx, entityData.getEntityId());
 | 
				
			||||||
            localSubscriptionService.addSubscription(
 | 
					            localSubscriptionService.addSubscription(
 | 
				
			||||||
                    createTsSub(entityData, subIdx, false, startTs, endTs, keyStates));
 | 
					                    createTsSub(entityData, subIdx, false, startTs, endTs, keyStates, resultToLatestValues));
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -200,6 +204,10 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private TbTimeseriesSubscription createTsSub(EntityData entityData, int subIdx, boolean latestValues, long startTs, long endTs, Map<String, Long> keyStates) {
 | 
					    private TbTimeseriesSubscription createTsSub(EntityData entityData, int subIdx, boolean latestValues, long startTs, long endTs, Map<String, Long> keyStates) {
 | 
				
			||||||
 | 
					        return createTsSub(entityData, subIdx, latestValues, startTs, endTs, keyStates, latestValues);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private TbTimeseriesSubscription createTsSub(EntityData entityData, int subIdx, boolean latestValues, long startTs, long endTs, Map<String, Long> keyStates, boolean resultToLatestValues) {
 | 
				
			||||||
        log.trace("[{}][{}][{}] Creating time-series subscription for [{}] with keys: {}", serviceId, cmdId, subIdx, entityData.getEntityId(), keyStates);
 | 
					        log.trace("[{}][{}][{}] Creating time-series subscription for [{}] with keys: {}", serviceId, cmdId, subIdx, entityData.getEntityId(), keyStates);
 | 
				
			||||||
        return TbTimeseriesSubscription.builder()
 | 
					        return TbTimeseriesSubscription.builder()
 | 
				
			||||||
                .serviceId(serviceId)
 | 
					                .serviceId(serviceId)
 | 
				
			||||||
@ -207,7 +215,7 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
 | 
				
			|||||||
                .subscriptionId(subIdx)
 | 
					                .subscriptionId(subIdx)
 | 
				
			||||||
                .tenantId(sessionRef.getSecurityCtx().getTenantId())
 | 
					                .tenantId(sessionRef.getSecurityCtx().getTenantId())
 | 
				
			||||||
                .entityId(entityData.getEntityId())
 | 
					                .entityId(entityData.getEntityId())
 | 
				
			||||||
                .updateConsumer((sessionId, subscriptionUpdate) -> sendWsMsg(sessionId, subscriptionUpdate, EntityKeyType.TIME_SERIES, latestValues))
 | 
					                .updateConsumer((sessionId, subscriptionUpdate) -> sendWsMsg(sessionId, subscriptionUpdate, EntityKeyType.TIME_SERIES, resultToLatestValues))
 | 
				
			||||||
                .allKeys(false)
 | 
					                .allKeys(false)
 | 
				
			||||||
                .keyStates(keyStates)
 | 
					                .keyStates(keyStates)
 | 
				
			||||||
                .latestValues(latestValues)
 | 
					                .latestValues(latestValues)
 | 
				
			||||||
 | 
				
			|||||||
@ -716,7 +716,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
 | 
				
			|||||||
                    "Cmd id is negative value!");
 | 
					                    "Cmd id is negative value!");
 | 
				
			||||||
            sendWsMsg(sessionRef, update);
 | 
					            sendWsMsg(sessionRef, update);
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        } else if (cmd.getQuery() == null && cmd.getLatestCmd() == null && cmd.getHistoryCmd() == null && cmd.getTsCmd() == null) {
 | 
					        } else if (cmd.getQuery() == null && !cmd.hasAnyCmd()) {
 | 
				
			||||||
            TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
 | 
					            TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
 | 
				
			||||||
                    "Query is empty!");
 | 
					                    "Query is empty!");
 | 
				
			||||||
            sendWsMsg(sessionRef, update);
 | 
					            sendWsMsg(sessionRef, update);
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,7 @@
 | 
				
			|||||||
package org.thingsboard.server.service.telemetry.cmd.v2;
 | 
					package org.thingsboard.server.service.telemetry.cmd.v2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.fasterxml.jackson.annotation.JsonCreator;
 | 
					import com.fasterxml.jackson.annotation.JsonCreator;
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JsonIgnore;
 | 
				
			||||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
					import com.fasterxml.jackson.annotation.JsonProperty;
 | 
				
			||||||
import lombok.Getter;
 | 
					import lombok.Getter;
 | 
				
			||||||
import org.thingsboard.server.common.data.query.EntityDataQuery;
 | 
					import org.thingsboard.server.common.data.query.EntityDataQuery;
 | 
				
			||||||
@ -55,4 +56,22 @@ public class EntityDataCmd extends DataCmd {
 | 
				
			|||||||
        this.aggHistoryCmd = aggHistoryCmd;
 | 
					        this.aggHistoryCmd = aggHistoryCmd;
 | 
				
			||||||
        this.aggTsCmd = aggTsCmd;
 | 
					        this.aggTsCmd = aggTsCmd;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @JsonIgnore
 | 
				
			||||||
 | 
					    public boolean hasAnyCmd() {
 | 
				
			||||||
 | 
					        return historyCmd != null || latestCmd != null || tsCmd != null || aggHistoryCmd != null || aggTsCmd != null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @JsonIgnore
 | 
				
			||||||
 | 
					    public boolean hasRegularCmds() {
 | 
				
			||||||
 | 
					        return historyCmd != null || latestCmd != null || tsCmd != null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @JsonIgnore
 | 
				
			||||||
 | 
					    public boolean hasAggCmds() {
 | 
				
			||||||
 | 
					        return aggHistoryCmd != null || aggTsCmd != null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -25,12 +25,13 @@ import java.util.List;
 | 
				
			|||||||
public class ReadTsKvQueryResult {
 | 
					public class ReadTsKvQueryResult {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final String key;
 | 
					    private final String key;
 | 
				
			||||||
 | 
					    // Holds the aggregation from the query
 | 
				
			||||||
 | 
					    private final Aggregation agg;
 | 
				
			||||||
    // Holds the data list;
 | 
					    // Holds the data list;
 | 
				
			||||||
    private final List<TsKvEntry> data;
 | 
					    private final List<TsKvEntry> data;
 | 
				
			||||||
    // Holds the max ts of the records that match aggregation intervals (not the ts of the aggregation window, but the ts of the last record among all the intervals)
 | 
					    // Holds the max ts of the records that match aggregation intervals (not the ts of the aggregation window, but the ts of the last record among all the intervals)
 | 
				
			||||||
    private final long lastEntryTs;
 | 
					    private final long lastEntryTs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public TsValue[] toTsValues() {
 | 
					    public TsValue[] toTsValues() {
 | 
				
			||||||
        if (data != null && !data.isEmpty()) {
 | 
					        if (data != null && !data.isEmpty()) {
 | 
				
			||||||
            List<TsValue> queryValues = new ArrayList<>();
 | 
					            List<TsValue> queryValues = new ArrayList<>();
 | 
				
			||||||
@ -43,4 +44,14 @@ public class ReadTsKvQueryResult {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public TsValue toTsValue() {
 | 
				
			||||||
 | 
					        if (data == null || data.isEmpty()) {
 | 
				
			||||||
 | 
					            return TsValue.EMPTY;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (data.size() > 1) {
 | 
				
			||||||
 | 
					            throw new RuntimeException("Query Result has multiple data points!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return data.get(0).toTsValue();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,16 +15,25 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
package org.thingsboard.server.common.data.query;
 | 
					package org.thingsboard.server.common.data.query;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import lombok.AllArgsConstructor;
 | 
				
			||||||
import lombok.Data;
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
					import org.thingsboard.server.common.data.id.EntityId;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.kv.Aggregation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Data
 | 
					@Data
 | 
				
			||||||
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
public class EntityData {
 | 
					public class EntityData {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final EntityId entityId;
 | 
					    private final EntityId entityId;
 | 
				
			||||||
    private final Map<EntityKeyType, Map<String, TsValue>> latest;
 | 
					    private final Map<EntityKeyType, Map<String, TsValue>> latest;
 | 
				
			||||||
    private final Map<String, TsValue[]> timeseries;
 | 
					    private final Map<String, TsValue[]> timeseries;
 | 
				
			||||||
 | 
					    private final Map<Aggregation, Map<String, TsValue>> aggLatest;
 | 
				
			||||||
 | 
					    private final Map<String, TsValue[]> aggFloating;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public EntityData(EntityId entityId, Map<EntityKeyType, Map<String, TsValue>> latest, Map<String, TsValue[]> timeseries) {
 | 
				
			||||||
 | 
					        this(entityId, latest, timeseries, null, null);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -24,6 +24,8 @@ import lombok.RequiredArgsConstructor;
 | 
				
			|||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
 | 
					@JsonInclude(JsonInclude.Include.NON_NULL)
 | 
				
			||||||
public class TsValue {
 | 
					public class TsValue {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static final TsValue EMPTY = new TsValue(0, "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final long ts;
 | 
					    private final long ts;
 | 
				
			||||||
    private final String value;
 | 
					    private final String value;
 | 
				
			||||||
    private final Long count;
 | 
					    private final Long count;
 | 
				
			||||||
 | 
				
			|||||||
@ -54,8 +54,8 @@ public class EntityDataAdapter {
 | 
				
			|||||||
        EntityType entityType = EntityType.valueOf((String) row.get("entity_type"));
 | 
					        EntityType entityType = EntityType.valueOf((String) row.get("entity_type"));
 | 
				
			||||||
        EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, id);
 | 
					        EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, id);
 | 
				
			||||||
        Map<EntityKeyType, Map<String, TsValue>> latest = new HashMap<>();
 | 
					        Map<EntityKeyType, Map<String, TsValue>> latest = new HashMap<>();
 | 
				
			||||||
        Map<String, TsValue[]> timeseries = new HashMap<>();
 | 
					        //Maybe avoid empty hashmaps?
 | 
				
			||||||
        EntityData entityData = new EntityData(entityId, latest, timeseries);
 | 
					        EntityData entityData = new EntityData(entityId, latest, new HashMap<>(), new HashMap<>(), new HashMap<>());
 | 
				
			||||||
        for (EntityKeyMapping mapping : selectionMapping) {
 | 
					        for (EntityKeyMapping mapping : selectionMapping) {
 | 
				
			||||||
            if (!mapping.isIgnore()) {
 | 
					            if (!mapping.isIgnore()) {
 | 
				
			||||||
                EntityKey entityKey = mapping.getEntityKey();
 | 
					                EntityKey entityKey = mapping.getEntityKey();
 | 
				
			||||||
 | 
				
			|||||||
@ -149,7 +149,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq
 | 
				
			|||||||
        tsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(query.getKey()));
 | 
					        tsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(query.getKey()));
 | 
				
			||||||
        List<TsKvEntry> tsKvEntries = DaoUtil.convertDataList(tsKvEntities);
 | 
					        List<TsKvEntry> tsKvEntries = DaoUtil.convertDataList(tsKvEntities);
 | 
				
			||||||
        long lastTs = tsKvEntries.stream().map(TsKvEntry::getTs).max(Long::compare).orElse(query.getStartTs());
 | 
					        long lastTs = tsKvEntries.stream().map(TsKvEntry::getTs).max(Long::compare).orElse(query.getStartTs());
 | 
				
			||||||
        return new ReadTsKvQueryResult(query.getKey(), tsKvEntries, lastTs);
 | 
					        return new ReadTsKvQueryResult(query.getKey(), query.getAggregation(), tsKvEntries, lastTs);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Optional<TsKvEntity> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) {
 | 
					    Optional<TsKvEntity> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) {
 | 
				
			||||||
 | 
				
			|||||||
@ -99,7 +99,7 @@ public abstract class BaseAbstractSqlTimeseriesDao extends JpaAbstractDaoListeni
 | 
				
			|||||||
                if (lastTs.isEmpty()) {
 | 
					                if (lastTs.isEmpty()) {
 | 
				
			||||||
                    lastTs = data.stream().map(AbstractTsKvEntity::getTs).filter(Objects::nonNull).max(Long::compare);
 | 
					                    lastTs = data.stream().map(AbstractTsKvEntity::getTs).filter(Objects::nonNull).max(Long::compare);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                return new ReadTsKvQueryResult(query.getKey(), DaoUtil.convertDataList(data), lastTs.orElse(query.getStartTs()));
 | 
					                return new ReadTsKvQueryResult(query.getKey(), query.getAggregation(), DaoUtil.convertDataList(data), lastTs.orElse(query.getStartTs()));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }, service);
 | 
					        }, service);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -181,7 +181,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
 | 
				
			|||||||
        timescaleTsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(strKey));
 | 
					        timescaleTsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(strKey));
 | 
				
			||||||
        var tsKvEntries = DaoUtil.convertDataList(timescaleTsKvEntities);
 | 
					        var tsKvEntries = DaoUtil.convertDataList(timescaleTsKvEntities);
 | 
				
			||||||
        long lastTs = tsKvEntries.stream().map(TsKvEntry::getTs).max(Long::compare).orElse(query.getStartTs());
 | 
					        long lastTs = tsKvEntries.stream().map(TsKvEntry::getTs).max(Long::compare).orElse(query.getStartTs());
 | 
				
			||||||
        return new ReadTsKvQueryResult(query.getKey(), tsKvEntries, lastTs);
 | 
					        return new ReadTsKvQueryResult(query.getKey(), query.getAggregation(), tsKvEntries, lastTs);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private List<Optional<? extends AbstractTsKvEntity>> findAllAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long timeBucket, Aggregation aggregation) {
 | 
					    private List<Optional<? extends AbstractTsKvEntity>> findAllAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long timeBucket, Aggregation aggregation) {
 | 
				
			||||||
 | 
				
			|||||||
@ -285,7 +285,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
 | 
				
			|||||||
                @Override
 | 
					                @Override
 | 
				
			||||||
                public ReadTsKvQueryResult apply(@Nullable List<Optional<TsKvEntryAggWrapper>> input) {
 | 
					                public ReadTsKvQueryResult apply(@Nullable List<Optional<TsKvEntryAggWrapper>> input) {
 | 
				
			||||||
                    if (input == null) {
 | 
					                    if (input == null) {
 | 
				
			||||||
                        return new ReadTsKvQueryResult(query.getKey(), Collections.emptyList(), query.getStartTs());
 | 
					                        return new ReadTsKvQueryResult(query.getKey(), query.getAggregation(), Collections.emptyList(), query.getStartTs());
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        long maxTs = query.getStartTs();
 | 
					                        long maxTs = query.getStartTs();
 | 
				
			||||||
                        List<TsKvEntry> data = new ArrayList<>();
 | 
					                        List<TsKvEntry> data = new ArrayList<>();
 | 
				
			||||||
@ -296,7 +296,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
 | 
				
			|||||||
                                data.add(tsKvEntryAggWrapper.getEntry());
 | 
					                                data.add(tsKvEntryAggWrapper.getEntry());
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        return new ReadTsKvQueryResult(query.getKey(), data, maxTs);
 | 
					                        return new ReadTsKvQueryResult(query.getKey(), query.getAggregation(), data, maxTs);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -333,7 +333,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
 | 
				
			|||||||
            if (tsKvEntries != null) {
 | 
					            if (tsKvEntries != null) {
 | 
				
			||||||
                lastTs = tsKvEntries.stream().map(TsKvEntry::getTs).max(Long::compare).orElse(query.getStartTs());
 | 
					                lastTs = tsKvEntries.stream().map(TsKvEntry::getTs).max(Long::compare).orElse(query.getStartTs());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return new ReadTsKvQueryResult(query.getKey(), tsKvEntries, lastTs);
 | 
					            return new ReadTsKvQueryResult(query.getKey(), query.getAggregation(), tsKvEntries, lastTs);
 | 
				
			||||||
        }, MoreExecutors.directExecutor());
 | 
					        }, MoreExecutors.directExecutor());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user