added version to timeseries and attribute protos
This commit is contained in:
parent
77a420b6d6
commit
d6367c9680
@ -45,11 +45,10 @@ public abstract class VersionedCaffeineTbCache<K extends Serializable, V extends
|
||||
@Override
|
||||
public void put(K key, V value) {
|
||||
Long version = value != null ? value.getVersion() : 0;
|
||||
put(key, value, version);
|
||||
doPut(key, value, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V value, Long version) {
|
||||
private void doPut(K key, V value, Long version) {
|
||||
if (version == null) {
|
||||
return;
|
||||
}
|
||||
@ -86,7 +85,7 @@ public abstract class VersionedCaffeineTbCache<K extends Serializable, V extends
|
||||
}
|
||||
lock.lock();
|
||||
try {
|
||||
put(key, null, version);
|
||||
doPut(key, null, version);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
@ -96,13 +96,15 @@ public abstract class VersionedRedisTbCache<K extends Serializable, V extends Se
|
||||
|
||||
@Override
|
||||
public void put(K key, V value) {
|
||||
Long version = value != null ? value.getVersion() : 0;
|
||||
put(key, value, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V value, Long version) {
|
||||
log.trace("put [{}][{}][{}]", key, value, version);
|
||||
log.trace("put [{}][{}]", key, value);
|
||||
Long version;
|
||||
if (value == null) {
|
||||
version = 0L;
|
||||
} else if (value.getVersion() != null) {
|
||||
version = value.getVersion();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
doPut(key, value, version, cacheTtl);
|
||||
}
|
||||
|
||||
|
||||
@ -25,8 +25,6 @@ public interface VersionedTbCache<K extends Serializable, V extends Serializable
|
||||
|
||||
void put(K key, V value);
|
||||
|
||||
void put(K key, V value, Long version);
|
||||
|
||||
void evict(K key);
|
||||
|
||||
void evict(K key, Long version);
|
||||
|
||||
@ -16,11 +16,14 @@
|
||||
package org.thingsboard.server.common.data.kv;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author Andrew Shvayka
|
||||
*/
|
||||
@Data
|
||||
public class BaseAttributeKvEntry implements AttributeKvEntry {
|
||||
|
||||
private static final long serialVersionUID = -6460767583563159407L;
|
||||
@ -47,11 +50,6 @@ public class BaseAttributeKvEntry implements AttributeKvEntry {
|
||||
this(kv, lastUpdateTs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastUpdateTs() {
|
||||
return lastUpdateTs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return kv.getKey();
|
||||
@ -97,35 +95,4 @@ public class BaseAttributeKvEntry implements AttributeKvEntry {
|
||||
return kv.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
BaseAttributeKvEntry that = (BaseAttributeKvEntry) o;
|
||||
|
||||
if (lastUpdateTs != that.lastUpdateTs) return false;
|
||||
return kv.equals(that.kv);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (int) (lastUpdateTs ^ (lastUpdateTs >>> 32));
|
||||
result = 31 * result + kv.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BaseAttributeKvEntry{" +
|
||||
"lastUpdateTs=" + lastUpdateTs +
|
||||
", kv=" + kv +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,9 +16,12 @@
|
||||
package org.thingsboard.server.common.data.kv;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@Data
|
||||
public class BasicTsKvEntry implements TsKvEntry {
|
||||
private static final int MAX_CHARS_PER_DATA_POINT = 512;
|
||||
protected final long ts;
|
||||
@ -79,33 +82,6 @@ public class BasicTsKvEntry implements TsKvEntry {
|
||||
return kv.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTs() {
|
||||
return ts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof BasicTsKvEntry)) return false;
|
||||
BasicTsKvEntry that = (BasicTsKvEntry) o;
|
||||
return getTs() == that.getTs() &&
|
||||
Objects.equals(kv, that.kv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getTs(), kv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BasicTsKvEntry{" +
|
||||
"ts=" + ts +
|
||||
", kv=" + kv +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueAsString() {
|
||||
return kv.getValueAsString();
|
||||
@ -127,8 +103,4 @@ public class BasicTsKvEntry implements TsKvEntry {
|
||||
return Math.max(1, (length + MAX_CHARS_PER_DATA_POINT - 1) / MAX_CHARS_PER_DATA_POINT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,8 +86,15 @@ public class KvProtoUtil {
|
||||
.setKv(KvProtoUtil.toKeyValueTypeProto(kvEntry)).build();
|
||||
}
|
||||
|
||||
public static TransportProtos.TsKvProto toTsKvProto(long ts, KvEntry kvEntry, long version) {
|
||||
return TransportProtos.TsKvProto.newBuilder()
|
||||
.setTs(ts)
|
||||
.setVersion(version)
|
||||
.setKv(KvProtoUtil.toKeyValueTypeProto(kvEntry)).build();
|
||||
}
|
||||
|
||||
public static TsKvEntry fromTsKvProto(TransportProtos.TsKvProto proto) {
|
||||
return new BasicTsKvEntry(proto.getTs(), fromTsKvProto(proto.getKv()));
|
||||
return new BasicTsKvEntry(proto.getTs(), fromTsKvProto(proto.getKv()), proto.getVersion());
|
||||
}
|
||||
|
||||
public static TransportProtos.KeyValueProto toKeyValueTypeProto(KvEntry kvEntry) {
|
||||
|
||||
@ -17,7 +17,9 @@ package org.thingsboard.server.common.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.server.common.data.ApiUsageState;
|
||||
import org.thingsboard.server.common.data.ApiUsageStateValue;
|
||||
@ -256,42 +258,47 @@ public class ProtoUtils {
|
||||
|
||||
if (msg.getValues() != null) {
|
||||
for (AttributeKvEntry attributeKvEntry : msg.getValues()) {
|
||||
TransportProtos.AttributeValueProto.Builder attributeValueBuilder = TransportProtos.AttributeValueProto.newBuilder()
|
||||
.setLastUpdateTs(attributeKvEntry.getLastUpdateTs())
|
||||
.setKey(attributeKvEntry.getKey());
|
||||
switch (attributeKvEntry.getDataType()) {
|
||||
case BOOLEAN -> {
|
||||
attributeKvEntry.getBooleanValue().ifPresent(attributeValueBuilder::setBoolV);
|
||||
attributeValueBuilder.setHasV(attributeKvEntry.getBooleanValue().isPresent());
|
||||
attributeValueBuilder.setType(TransportProtos.KeyValueType.BOOLEAN_V);
|
||||
}
|
||||
case STRING -> {
|
||||
attributeKvEntry.getStrValue().ifPresent(attributeValueBuilder::setStringV);
|
||||
attributeValueBuilder.setHasV(attributeKvEntry.getStrValue().isPresent());
|
||||
attributeValueBuilder.setType(TransportProtos.KeyValueType.STRING_V);
|
||||
}
|
||||
case DOUBLE -> {
|
||||
attributeKvEntry.getDoubleValue().ifPresent(attributeValueBuilder::setDoubleV);
|
||||
attributeValueBuilder.setHasV(attributeKvEntry.getDoubleValue().isPresent());
|
||||
attributeValueBuilder.setType(TransportProtos.KeyValueType.DOUBLE_V);
|
||||
}
|
||||
case LONG -> {
|
||||
attributeKvEntry.getLongValue().ifPresent(attributeValueBuilder::setLongV);
|
||||
attributeValueBuilder.setHasV(attributeKvEntry.getLongValue().isPresent());
|
||||
attributeValueBuilder.setType(TransportProtos.KeyValueType.LONG_V);
|
||||
}
|
||||
case JSON -> {
|
||||
attributeKvEntry.getJsonValue().ifPresent(attributeValueBuilder::setJsonV);
|
||||
attributeValueBuilder.setHasV(attributeKvEntry.getJsonValue().isPresent());
|
||||
attributeValueBuilder.setType(TransportProtos.KeyValueType.JSON_V);
|
||||
}
|
||||
}
|
||||
builder.addValues(attributeValueBuilder.build());
|
||||
builder.addValues(toProto(attributeKvEntry));
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static TransportProtos.AttributeValueProto toProto(AttributeKvEntry attributeKvEntry) {
|
||||
TransportProtos.AttributeValueProto.Builder builder = TransportProtos.AttributeValueProto.newBuilder()
|
||||
.setLastUpdateTs(attributeKvEntry.getLastUpdateTs())
|
||||
.setKey(attributeKvEntry.getKey());
|
||||
switch (attributeKvEntry.getDataType()) {
|
||||
case BOOLEAN:
|
||||
attributeKvEntry.getBooleanValue().ifPresent(builder::setBoolV);
|
||||
builder.setHasV(attributeKvEntry.getBooleanValue().isPresent());
|
||||
builder.setType(TransportProtos.KeyValueType.BOOLEAN_V);
|
||||
break;
|
||||
case STRING:
|
||||
attributeKvEntry.getStrValue().ifPresent(builder::setStringV);
|
||||
builder.setHasV(attributeKvEntry.getStrValue().isPresent());
|
||||
builder.setType(TransportProtos.KeyValueType.STRING_V);
|
||||
break;
|
||||
case DOUBLE:
|
||||
attributeKvEntry.getDoubleValue().ifPresent(builder::setDoubleV);
|
||||
builder.setHasV(attributeKvEntry.getDoubleValue().isPresent());
|
||||
builder.setType(TransportProtos.KeyValueType.DOUBLE_V);
|
||||
break;
|
||||
case LONG:
|
||||
attributeKvEntry.getLongValue().ifPresent(builder::setLongV);
|
||||
builder.setHasV(attributeKvEntry.getLongValue().isPresent());
|
||||
builder.setType(TransportProtos.KeyValueType.LONG_V);
|
||||
break;
|
||||
case JSON:
|
||||
attributeKvEntry.getJsonValue().ifPresent(builder::setJsonV);
|
||||
builder.setHasV(attributeKvEntry.getJsonValue().isPresent());
|
||||
builder.setType(TransportProtos.KeyValueType.JSON_V);
|
||||
break;
|
||||
}
|
||||
builder.setVersion(attributeKvEntry.getVersion());
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static ToDeviceActorNotificationMsg fromProto(TransportProtos.DeviceAttributesEventMsgProto proto) {
|
||||
return new DeviceAttributesEventNotificationMsg(
|
||||
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
|
||||
@ -500,20 +507,25 @@ public class ProtoUtils {
|
||||
}
|
||||
List<AttributeKvEntry> result = new ArrayList<>();
|
||||
for (TransportProtos.AttributeValueProto kvEntry : valuesList) {
|
||||
boolean hasValue = kvEntry.getHasV();
|
||||
KvEntry entry = switch (kvEntry.getType()) {
|
||||
case BOOLEAN_V -> new BooleanDataEntry(kvEntry.getKey(), hasValue ? kvEntry.getBoolV() : null);
|
||||
case LONG_V -> new LongDataEntry(kvEntry.getKey(), hasValue ? kvEntry.getLongV() : null);
|
||||
case DOUBLE_V -> new DoubleDataEntry(kvEntry.getKey(), hasValue ? kvEntry.getDoubleV() : null);
|
||||
case STRING_V -> new StringDataEntry(kvEntry.getKey(), hasValue ? kvEntry.getStringV() : null);
|
||||
case JSON_V -> new JsonDataEntry(kvEntry.getKey(), hasValue ? kvEntry.getJsonV() : null);
|
||||
default -> null;
|
||||
};
|
||||
result.add(new BaseAttributeKvEntry(kvEntry.getLastUpdateTs(), entry));
|
||||
result.add(fromProto(kvEntry));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static AttributeKvEntry fromProto(TransportProtos.AttributeValueProto proto) {
|
||||
boolean hasValue = proto.getHasV();
|
||||
String key = proto.getKey();
|
||||
KvEntry entry = switch (proto.getType()) {
|
||||
case BOOLEAN_V -> new BooleanDataEntry(key, hasValue ? proto.getBoolV() : null);
|
||||
case LONG_V -> new LongDataEntry(key, hasValue ? proto.getLongV() : null);
|
||||
case DOUBLE_V -> new DoubleDataEntry(key, hasValue ? proto.getDoubleV() : null);
|
||||
case STRING_V -> new StringDataEntry(key, hasValue ? proto.getStringV() : null);
|
||||
case JSON_V -> new JsonDataEntry(key, hasValue ? proto.getJsonV() : null);
|
||||
default -> null;
|
||||
};
|
||||
return new BaseAttributeKvEntry(entry, proto.getLastUpdateTs(), proto.getVersion());
|
||||
}
|
||||
|
||||
public static TransportProtos.DeviceProto toProto(Device device) {
|
||||
var builder = TransportProtos.DeviceProto.newBuilder()
|
||||
.setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits())
|
||||
|
||||
@ -157,11 +157,13 @@ message AttributeValueProto {
|
||||
string string_v = 7;
|
||||
string json_v = 8;
|
||||
optional string key = 9;
|
||||
int64 version = 10;
|
||||
}
|
||||
|
||||
message TsKvProto {
|
||||
int64 ts = 1;
|
||||
KeyValueProto kv = 2;
|
||||
int64 version = 3;
|
||||
}
|
||||
|
||||
message TsKvListProto {
|
||||
|
||||
@ -26,15 +26,8 @@ import org.thingsboard.server.cache.TbRedisSerializer;
|
||||
import org.thingsboard.server.cache.VersionedRedisTbCache;
|
||||
import org.thingsboard.server.common.data.CacheConstants;
|
||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
|
||||
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
|
||||
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
|
||||
import org.thingsboard.server.common.data.kv.JsonDataEntry;
|
||||
import org.thingsboard.server.common.data.kv.KvEntry;
|
||||
import org.thingsboard.server.common.data.kv.LongDataEntry;
|
||||
import org.thingsboard.server.common.data.kv.StringDataEntry;
|
||||
import org.thingsboard.server.common.util.ProtoUtils;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.AttributeValueProto;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
|
||||
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
|
||||
@Service("AttributeCache")
|
||||
@ -44,54 +37,13 @@ public class AttributeRedisCache extends VersionedRedisTbCache<AttributeCacheKey
|
||||
super(CacheConstants.ATTRIBUTES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>() {
|
||||
@Override
|
||||
public byte[] serialize(AttributeKvEntry attributeKvEntry) throws SerializationException {
|
||||
AttributeValueProto.Builder builder = AttributeValueProto.newBuilder()
|
||||
.setLastUpdateTs(attributeKvEntry.getLastUpdateTs());
|
||||
switch (attributeKvEntry.getDataType()) {
|
||||
case BOOLEAN:
|
||||
attributeKvEntry.getBooleanValue().ifPresent(builder::setBoolV);
|
||||
builder.setHasV(attributeKvEntry.getBooleanValue().isPresent());
|
||||
builder.setType(KeyValueType.BOOLEAN_V);
|
||||
break;
|
||||
case STRING:
|
||||
attributeKvEntry.getStrValue().ifPresent(builder::setStringV);
|
||||
builder.setHasV(attributeKvEntry.getStrValue().isPresent());
|
||||
builder.setType(KeyValueType.STRING_V);
|
||||
break;
|
||||
case DOUBLE:
|
||||
attributeKvEntry.getDoubleValue().ifPresent(builder::setDoubleV);
|
||||
builder.setHasV(attributeKvEntry.getDoubleValue().isPresent());
|
||||
builder.setType(KeyValueType.DOUBLE_V);
|
||||
break;
|
||||
case LONG:
|
||||
attributeKvEntry.getLongValue().ifPresent(builder::setLongV);
|
||||
builder.setHasV(attributeKvEntry.getLongValue().isPresent());
|
||||
builder.setType(KeyValueType.LONG_V);
|
||||
break;
|
||||
case JSON:
|
||||
attributeKvEntry.getJsonValue().ifPresent(builder::setJsonV);
|
||||
builder.setHasV(attributeKvEntry.getJsonValue().isPresent());
|
||||
builder.setType(KeyValueType.JSON_V);
|
||||
break;
|
||||
|
||||
}
|
||||
return builder.build().toByteArray();
|
||||
return ProtoUtils.toProto(attributeKvEntry).toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeKvEntry deserialize(AttributeCacheKey key, byte[] bytes) throws SerializationException {
|
||||
try {
|
||||
AttributeValueProto proto = AttributeValueProto.parseFrom(bytes);
|
||||
boolean hasValue = proto.getHasV();
|
||||
KvEntry entry = switch (proto.getType()) {
|
||||
case BOOLEAN_V -> new BooleanDataEntry(key.getKey(), hasValue ? proto.getBoolV() : null);
|
||||
case LONG_V -> new LongDataEntry(key.getKey(), hasValue ? proto.getLongV() : null);
|
||||
case DOUBLE_V -> new DoubleDataEntry(key.getKey(), hasValue ? proto.getDoubleV() : null);
|
||||
case STRING_V -> new StringDataEntry(key.getKey(), hasValue ? proto.getStringV() : null);
|
||||
case JSON_V -> new JsonDataEntry(key.getKey(), hasValue ? proto.getJsonV() : null);
|
||||
default ->
|
||||
throw new InvalidProtocolBufferException("Unrecognized type: " + proto.getType() + " !");
|
||||
};
|
||||
return new BaseAttributeKvEntry(proto.getLastUpdateTs(), entry);
|
||||
return ProtoUtils.fromProto(AttributeValueProto.parseFrom(bytes));
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new SerializationException(e.getMessage());
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
|
||||
import org.thingsboard.server.common.data.util.TbPair;
|
||||
import org.thingsboard.server.common.stats.DefaultCounter;
|
||||
import org.thingsboard.server.common.stats.StatsFactory;
|
||||
@ -159,7 +160,7 @@ public class CachedAttributesService implements AttributesService {
|
||||
log.trace("[{}][{}] Lookup attributes from db: {}", entityId, scope, notFoundAttributeKeys);
|
||||
List<AttributeKvEntry> result = attributesDao.find(tenantId, entityId, scope, notFoundAttributeKeys);
|
||||
for (AttributeKvEntry foundInDbAttribute : result) {
|
||||
put(entityId, scope, foundInDbAttribute, foundInDbAttribute.getVersion());
|
||||
put(entityId, scope, foundInDbAttribute);
|
||||
notFoundAttributeKeys.remove(foundInDbAttribute.getKey());
|
||||
}
|
||||
for (String key : notFoundAttributeKeys) {
|
||||
@ -218,8 +219,7 @@ public class CachedAttributesService implements AttributesService {
|
||||
public ListenableFuture<Long> save(TenantId tenantId, EntityId entityId, AttributeScope scope, AttributeKvEntry attribute) {
|
||||
validate(entityId, scope);
|
||||
AttributeUtils.validate(attribute, valueNoXssValidation);
|
||||
ListenableFuture<Long> future = attributesDao.save(tenantId, entityId, scope, attribute);
|
||||
return Futures.transform(future, version -> put(entityId, scope, attribute, version), cacheExecutor);
|
||||
return doSave(tenantId, entityId, scope, attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -234,19 +234,25 @@ public class CachedAttributesService implements AttributesService {
|
||||
|
||||
List<ListenableFuture<Long>> futures = new ArrayList<>(attributes.size());
|
||||
for (var attribute : attributes) {
|
||||
ListenableFuture<Long> future = attributesDao.save(tenantId, entityId, scope, attribute);
|
||||
futures.add(Futures.transform(future, version -> put(entityId, scope, attribute, version), cacheExecutor));
|
||||
futures.add(doSave(tenantId, entityId, scope, attribute));
|
||||
}
|
||||
|
||||
return Futures.allAsList(futures);
|
||||
}
|
||||
|
||||
private Long put(EntityId entityId, AttributeScope scope, AttributeKvEntry attribute, Long version) {
|
||||
private ListenableFuture<Long> doSave(TenantId tenantId, EntityId entityId, AttributeScope scope, AttributeKvEntry attribute) {
|
||||
ListenableFuture<Long> future = attributesDao.save(tenantId, entityId, scope, attribute);
|
||||
return Futures.transform(future, version -> {
|
||||
put(entityId, scope, new BaseAttributeKvEntry(((BaseAttributeKvEntry)attribute).getKv(), attribute.getLastUpdateTs(), version));
|
||||
return version;
|
||||
}, cacheExecutor);
|
||||
}
|
||||
|
||||
private void put(EntityId entityId, AttributeScope scope, AttributeKvEntry attribute) {
|
||||
String key = attribute.getKey();
|
||||
log.trace("[{}][{}][{}] Before cache put: {}", entityId, scope, key, attribute);
|
||||
cache.put(new AttributeCacheKey(scope, entityId, key), attribute, version);
|
||||
cache.put(new AttributeCacheKey(scope, entityId, key), attribute);
|
||||
log.trace("[{}][{}][{}] after cache put.", entityId, scope, key);
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -29,6 +29,7 @@ import org.thingsboard.server.cache.VersionedTbCache;
|
||||
import org.thingsboard.server.common.data.id.DeviceProfileId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
|
||||
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
|
||||
import org.thingsboard.server.common.data.kv.TsKvEntry;
|
||||
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
|
||||
@ -66,9 +67,9 @@ public class CachedRedisSqlTimeseriesLatestDao extends BaseAbstractSqlTimeseries
|
||||
@Override
|
||||
public ListenableFuture<Long> saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) {
|
||||
ListenableFuture<Long> future = sqlDao.saveLatest(tenantId, entityId, tsKvEntry);
|
||||
future = Futures.transform(future, x -> {
|
||||
cache.put(new TsLatestCacheKey(entityId, tsKvEntry.getKey()), tsKvEntry, x);
|
||||
return x;
|
||||
future = Futures.transform(future, version -> {
|
||||
cache.put(new TsLatestCacheKey(entityId, tsKvEntry.getKey()), new BasicTsKvEntry(tsKvEntry.getTs(), ((BasicTsKvEntry) tsKvEntry).getKv(), version));
|
||||
return version;
|
||||
},
|
||||
cacheExecutorService);
|
||||
if (log.isTraceEnabled()) {
|
||||
@ -94,8 +95,9 @@ public class CachedRedisSqlTimeseriesLatestDao extends BaseAbstractSqlTimeseries
|
||||
if (x.isRemoved()) {
|
||||
TsLatestCacheKey key = new TsLatestCacheKey(entityId, query.getKey());
|
||||
Long version = x.getVersion();
|
||||
if (x.getData() != null) {
|
||||
cache.put(key, x.getData(), version);
|
||||
TsKvEntry newTsKvEntry = x.getData();
|
||||
if (newTsKvEntry != null) {
|
||||
cache.put(key, new BasicTsKvEntry(newTsKvEntry.getTs(), ((BasicTsKvEntry) newTsKvEntry).getKv(), version));
|
||||
} else {
|
||||
cache.evict(key, version);
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ public class TsLatestRedisCache extends VersionedRedisTbCache<TsLatestCacheKey,
|
||||
super(CacheConstants.TS_LATEST_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>() {
|
||||
@Override
|
||||
public byte[] serialize(TsKvEntry tsKvEntry) throws SerializationException {
|
||||
return KvProtoUtil.toTsKvProto(tsKvEntry.getTs(), tsKvEntry).toByteArray();
|
||||
return KvProtoUtil.toTsKvProto(tsKvEntry.getTs(), tsKvEntry, tsKvEntry.getVersion()).toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -26,7 +26,6 @@ import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.thingsboard.server.cache.VersionedTbCache;
|
||||
import org.thingsboard.server.common.data.AttributeScope;
|
||||
import org.thingsboard.server.common.data.id.DeviceId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
@ -34,7 +33,6 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
|
||||
import org.thingsboard.server.common.data.kv.KvEntry;
|
||||
import org.thingsboard.server.common.data.kv.StringDataEntry;
|
||||
import org.thingsboard.server.dao.attributes.AttributeCacheKey;
|
||||
import org.thingsboard.server.dao.attributes.AttributesService;
|
||||
import org.thingsboard.server.dao.service.AbstractServiceTest;
|
||||
|
||||
@ -74,7 +72,7 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest {
|
||||
attributesService.save(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, Collections.singletonList(attr)).get();
|
||||
Optional<AttributeKvEntry> saved = attributesService.find(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, attr.getKey()).get();
|
||||
Assert.assertTrue(saved.isPresent());
|
||||
Assert.assertEquals(attr, saved.get());
|
||||
equalsIgnoreVersion(attr, saved.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -87,14 +85,15 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest {
|
||||
Optional<AttributeKvEntry> saved = attributesService.find(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, attrOld.getKey()).get();
|
||||
|
||||
Assert.assertTrue(saved.isPresent());
|
||||
Assert.assertEquals(attrOld, saved.get());
|
||||
equalsIgnoreVersion(attrOld, saved.get());
|
||||
|
||||
KvEntry attrNewValue = new StringDataEntry("attribute1", "value2");
|
||||
AttributeKvEntry attrNew = new BaseAttributeKvEntry(attrNewValue, 73L);
|
||||
attributesService.save(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, Collections.singletonList(attrNew)).get();
|
||||
|
||||
saved = attributesService.find(SYSTEM_TENANT_ID, deviceId, AttributeScope.CLIENT_SCOPE, attrOld.getKey()).get();
|
||||
Assert.assertEquals(attrNew, saved.get());
|
||||
Assert.assertTrue(saved.isPresent());
|
||||
equalsIgnoreVersion(attrNew, saved.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -117,8 +116,8 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest {
|
||||
Assert.assertNotNull(saved);
|
||||
Assert.assertEquals(2, saved.size());
|
||||
|
||||
Assert.assertEquals(attrANew, saved.get(0));
|
||||
Assert.assertEquals(attrBNew, saved.get(1));
|
||||
equalsIgnoreVersion(attrANew, saved.get(0));
|
||||
equalsIgnoreVersion(attrBNew, saved.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -253,6 +252,11 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest {
|
||||
}));
|
||||
futures.add(pool.submit(() -> saveAttribute(tenantId, deviceId, scope, key, NEW_VALUE)));
|
||||
Futures.allAsList(futures).get(10, TimeUnit.SECONDS);
|
||||
|
||||
String attributeValue = getAttributeValue(tenantId, deviceId, scope, key);
|
||||
if (!NEW_VALUE.equals(attributeValue)) {
|
||||
System.out.println();
|
||||
}
|
||||
Assert.assertEquals(NEW_VALUE, getAttributeValue(tenantId, deviceId, scope, key));
|
||||
}
|
||||
|
||||
@ -309,5 +313,10 @@ public abstract class BaseAttributesServiceTest extends AbstractServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void equalsIgnoreVersion(AttributeKvEntry expected, AttributeKvEntry actual) {
|
||||
Assert.assertEquals(expected.getKey(), actual.getKey());
|
||||
Assert.assertEquals(expected.getValue(), actual.getValue());
|
||||
Assert.assertEquals(expected.getLastUpdateTs(), actual.getLastUpdateTs());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.dao.service.attributes.sql;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.thingsboard.server.cache.TbCacheValueWrapper;
|
||||
import org.thingsboard.server.cache.VersionedTbCache;
|
||||
import org.thingsboard.server.common.data.AttributeScope;
|
||||
import org.thingsboard.server.common.data.id.DeviceId;
|
||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
|
||||
import org.thingsboard.server.common.data.kv.StringDataEntry;
|
||||
import org.thingsboard.server.dao.attributes.AttributeCacheKey;
|
||||
import org.thingsboard.server.dao.service.AbstractServiceTest;
|
||||
import org.thingsboard.server.dao.service.DaoSqlTest;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@DaoSqlTest
|
||||
public class AttributeCacheServiceSqlTest extends AbstractServiceTest {
|
||||
|
||||
private static final String TEST_KEY = "key";
|
||||
private static final String TEST_VALUE = "value";
|
||||
private static final DeviceId DEVICE_ID = new DeviceId(UUID.randomUUID());
|
||||
|
||||
@Autowired
|
||||
VersionedTbCache<AttributeCacheKey, AttributeKvEntry> cache;
|
||||
|
||||
@Test
|
||||
public void testPutAndGet() {
|
||||
AttributeCacheKey testKey = new AttributeCacheKey(AttributeScope.CLIENT_SCOPE, DEVICE_ID, TEST_KEY);
|
||||
AttributeKvEntry testValue = new BaseAttributeKvEntry(new StringDataEntry(TEST_KEY, TEST_VALUE), 1, 1L);
|
||||
cache.put(testKey, testValue);
|
||||
|
||||
TbCacheValueWrapper<AttributeKvEntry> wrapper = cache.get(testKey);
|
||||
assertNotNull(wrapper);
|
||||
|
||||
assertEquals(testValue, wrapper.get());
|
||||
|
||||
AttributeKvEntry testValue2 = new BaseAttributeKvEntry(new StringDataEntry(TEST_KEY, TEST_VALUE), 1, 2L);
|
||||
cache.put(testKey, testValue2);
|
||||
|
||||
wrapper = cache.get(testKey);
|
||||
assertNotNull(wrapper);
|
||||
|
||||
assertEquals(testValue2, wrapper.get());
|
||||
|
||||
AttributeKvEntry testValue3 = new BaseAttributeKvEntry(new StringDataEntry(TEST_KEY, TEST_VALUE), 1, 0L);
|
||||
cache.put(testKey, testValue3);
|
||||
|
||||
wrapper = cache.get(testKey);
|
||||
assertNotNull(wrapper);
|
||||
|
||||
assertEquals(testValue2, wrapper.get());
|
||||
|
||||
cache.evict(testKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvictWithVersion() {
|
||||
AttributeCacheKey testKey = new AttributeCacheKey(AttributeScope.CLIENT_SCOPE, DEVICE_ID, TEST_KEY);
|
||||
AttributeKvEntry testValue = new BaseAttributeKvEntry(new StringDataEntry(TEST_KEY, TEST_VALUE), 1, 1L);
|
||||
cache.put(testKey, testValue);
|
||||
|
||||
TbCacheValueWrapper<AttributeKvEntry> wrapper = cache.get(testKey);
|
||||
assertNotNull(wrapper);
|
||||
|
||||
assertEquals(testValue, wrapper.get());
|
||||
|
||||
cache.evict(testKey, 2L);
|
||||
|
||||
wrapper = cache.get(testKey);
|
||||
assertNotNull(wrapper);
|
||||
|
||||
assertNull(wrapper.get());
|
||||
|
||||
cache.evict(testKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvict() {
|
||||
AttributeCacheKey testKey = new AttributeCacheKey(AttributeScope.CLIENT_SCOPE, DEVICE_ID, TEST_KEY);
|
||||
AttributeKvEntry testValue = new BaseAttributeKvEntry(new StringDataEntry(TEST_KEY, TEST_VALUE), 1, 1L);
|
||||
cache.put(testKey, testValue);
|
||||
|
||||
TbCacheValueWrapper<AttributeKvEntry> wrapper = cache.get(testKey);
|
||||
assertNotNull(wrapper);
|
||||
|
||||
assertEquals(testValue, wrapper.get());
|
||||
|
||||
cache.evict(testKey);
|
||||
|
||||
wrapper = cache.get(testKey);
|
||||
assertNull(wrapper);
|
||||
}
|
||||
}
|
||||
@ -137,7 +137,12 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
|
||||
toTsEntry(TS, booleanKvEntry));
|
||||
Collections.sort(expected, Comparator.comparing(KvEntry::getKey));
|
||||
|
||||
assertEquals(expected, tsList);
|
||||
for (int i = 0; i < expected.size(); i++) {
|
||||
var expectedEntry = expected.get(i);
|
||||
var actualEntry = tsList.get(i);
|
||||
equalsIgnoreVersion(expectedEntry, actualEntry);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private EntityView saveAndCreateEntityView(DeviceId deviceId, List<String> timeseries) {
|
||||
@ -160,7 +165,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
|
||||
|
||||
List<TsKvEntry> entries = tsService.findLatest(tenantId, deviceId, Collections.singleton(STRING_KEY)).get(MAX_TIMEOUT, TimeUnit.SECONDS);
|
||||
Assert.assertEquals(1, entries.size());
|
||||
Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(0));
|
||||
equalsIgnoreVersion(toTsEntry(TS, stringKvEntry), entries.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -176,7 +181,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
|
||||
|
||||
Optional<TsKvEntry> entryOpt = tsService.findLatest(tenantId, deviceId, STRING_KEY).get(MAX_TIMEOUT, TimeUnit.SECONDS);
|
||||
assertThat(entryOpt).isNotNull().isPresent();
|
||||
Assert.assertEquals(toTsEntry(TS, stringKvEntry), entryOpt.orElse(null));
|
||||
equalsIgnoreVersion(toTsEntry(TS, stringKvEntry), entryOpt.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -186,7 +191,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
|
||||
|
||||
Optional<TsKvEntry> entryOpt = tsService.findLatest(tenantId, deviceId, STRING_KEY).get(MAX_TIMEOUT, TimeUnit.SECONDS);
|
||||
assertThat(entryOpt).isNotNull().isPresent();
|
||||
Assert.assertEquals(toTsEntry(TS, new StringDataEntry(STRING_KEY, "new")), entryOpt.orElse(null));
|
||||
equalsIgnoreVersion(toTsEntry(TS, new StringDataEntry(STRING_KEY, "new")), entryOpt.get());
|
||||
}
|
||||
|
||||
public void testFindLatestOpt_givenSaveWithSameTSOverwriteTypeAndValue() throws Exception {
|
||||
@ -209,7 +214,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
|
||||
|
||||
Optional<TsKvEntry> entryOpt = tsService.findLatest(tenantId, deviceId, STRING_KEY).get(MAX_TIMEOUT, TimeUnit.SECONDS);
|
||||
assertThat(entryOpt).isNotNull().isPresent();
|
||||
Assert.assertEquals(toTsEntry(TS, stringKvEntry), entryOpt.get());
|
||||
equalsIgnoreVersion(toTsEntry(TS, stringKvEntry), entryOpt.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -239,7 +244,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
|
||||
|
||||
List<TsKvEntry> entries = tsService.findLatest(tenantId, deviceId, Collections.singleton(STRING_KEY)).get(MAX_TIMEOUT, TimeUnit.SECONDS);
|
||||
Assert.assertEquals(1, entries.size());
|
||||
Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(0));
|
||||
equalsIgnoreVersion(toTsEntry(TS - 1, stringKvEntry), entries.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -794,5 +799,10 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
|
||||
return new BasicTsKvEntry(ts, entry);
|
||||
}
|
||||
|
||||
private static void equalsIgnoreVersion(TsKvEntry expected, TsKvEntry actual) {
|
||||
assertEquals(expected.getKey(), actual.getKey());
|
||||
assertEquals(expected.getValue(), actual.getValue());
|
||||
assertEquals(expected.getTs(), actual.getTs());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user