Persistence alias support (#18286)

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
pull/18302/head
Mark Herwege 2025-02-20 07:53:28 +01:00 committed by GitHub
parent d7abc08cfd
commit 32b0a40252
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 218 additions and 103 deletions

View File

@ -449,7 +449,7 @@ public abstract class AbstractDynamoDBItem<T> implements DynamoDBItem<T> {
if (deserializedState == null) {
return null;
}
return new DynamoDBHistoricItem(getName(), deserializedState, getTime().toInstant());
return new DynamoDBHistoricItem(item.getName(), deserializedState, getTime().toInstant());
} catch (Exception e) {
logger.trace("Failed to convert state '{}' to item {} {}: {} {}. Data persisted with incompatible item.",
this.state, item.getClass().getSimpleName(), item.getName(), e.getClass().getSimpleName(),

View File

@ -350,6 +350,11 @@ public class DynamoDBPersistenceService implements QueryablePersistenceService {
@Override
public Iterable<HistoricItem> query(FilterCriteria filter) {
return query(filter, null);
}
@Override
public Iterable<HistoricItem> query(FilterCriteria filter, @Nullable String alias) {
logIfManyQueuedTasks();
Instant start = Instant.now();
String filterDescription = filterToString(filter);
@ -419,7 +424,7 @@ public class DynamoDBPersistenceService implements QueryablePersistenceService {
item.getClass().getSimpleName(), dtoClass.getSimpleName(), tableName);
QueryEnhancedRequest queryExpression = DynamoDBQueryUtils.createQueryExpression(dtoClass,
localTableNameResolver.getTableSchema(), item, filter, unitProvider);
localTableNameResolver.getTableSchema(), item, alias, filter, unitProvider);
CompletableFuture<List<DynamoDBItem<?>>> itemsFuture = new CompletableFuture<>();
final SdkPublisher<? extends DynamoDBItem<?>> itemPublisher = table.query(queryExpression).items();

View File

@ -44,19 +44,21 @@ public class DynamoDBQueryUtils {
* @param dtoClass dto class
* @param expectedTableSchema table schema to query against
* @param item item corresponding to filter
* @param alias corresponding to item
* @param filter filter for the query
* @return DynamoDBQueryExpression corresponding to the given FilterCriteria
* @param unitProvider the unit provider for number with dimension
* @throws IllegalArgumentException when schema is not fully resolved
*/
public static QueryEnhancedRequest createQueryExpression(Class<? extends DynamoDBItem<?>> dtoClass,
ExpectedTableSchema expectedTableSchema, Item item, FilterCriteria filter, UnitProvider unitProvider) {
ExpectedTableSchema expectedTableSchema, Item item, @Nullable String alias, FilterCriteria filter,
UnitProvider unitProvider) {
if (!expectedTableSchema.isFullyResolved()) {
throw new IllegalArgumentException("Schema not resolved");
}
QueryEnhancedRequest.Builder queryBuilder = QueryEnhancedRequest.builder()
.scanIndexForward(filter.getOrdering() == Ordering.ASCENDING);
String itemName = filter.getItemName();
String itemName = alias != null ? alias : filter.getItemName();
if (itemName == null) {
throw new IllegalArgumentException("Item name not set");
}

View File

@ -204,7 +204,7 @@ public class InfluxDBPersistenceService implements ModifiablePersistenceService
logger.warn("InfluxDB service not ready. Storing {} rejected.", item);
return;
}
convert(item, state, date.toInstant(), null).thenAccept(point -> {
convert(item, state, date.toInstant(), alias).thenAccept(point -> {
if (point == null) {
logger.trace("Ignoring item {}, conversion to an InfluxDB point failed.", item.getName());
return;
@ -233,27 +233,33 @@ public class InfluxDBPersistenceService implements ModifiablePersistenceService
@Override
public Iterable<HistoricItem> query(FilterCriteria filter) {
return query(filter, null);
}
@Override
public Iterable<HistoricItem> query(FilterCriteria filter, @Nullable String alias) {
String itemName = filter.getItemName();
if (itemName == null) {
logger.warn("Item name is missing in filter {} when querying data.", filter);
return List.of();
}
if (serviceActivated && checkConnection()) {
logger.trace(
"Query-Filter: itemname: {}, ordering: {}, state: {}, operator: {}, getBeginDate: {}, getEndDate: {}, getPageSize: {}, getPageNumber: {}",
filter.getItemName(), filter.getOrdering().toString(), filter.getState(), filter.getOperator(),
itemName, filter.getOrdering().toString(), filter.getState(), filter.getOperator(),
filter.getBeginDate(), filter.getEndDate(), filter.getPageSize(), filter.getPageNumber());
if (filter.getItemName() == null) {
logger.warn("Item name is missing in filter {} when querying data.", filter);
return List.of();
}
List<InfluxDBRepository.InfluxRow> results = influxDBRepository.query(filter,
configuration.getRetentionPolicy());
return results.stream().map(this::mapRowToHistoricItem).collect(Collectors.toList());
configuration.getRetentionPolicy(), alias);
return results.stream().map(r -> mapRowToHistoricItem(r, itemName)).collect(Collectors.toList());
} else {
logger.debug("Query for persisted data ignored, InfluxDB is not connected");
return List.of();
}
}
private HistoricItem mapRowToHistoricItem(InfluxDBRepository.InfluxRow row) {
State state = InfluxDBStateConvertUtils.objectToState(row.value(), row.itemName(), itemRegistry);
private HistoricItem mapRowToHistoricItem(InfluxDBRepository.InfluxRow row, String itemName) {
State state = InfluxDBStateConvertUtils.objectToState(row.value(), itemName, itemRegistry);
return new InfluxDBHistoricItem(row.itemName(), state, row.time());
}
@ -314,8 +320,8 @@ public class InfluxDBPersistenceService implements ModifiablePersistenceService
}
return CompletableFuture.supplyAsync(() -> {
String measurementName = storeAlias != null && !storeAlias.isBlank() ? storeAlias : itemName;
measurementName = influxDBMetadataService.getMeasurementNameOrDefault(itemName, measurementName);
String alias = storeAlias != null && !storeAlias.isBlank() ? storeAlias : itemName;
String measurementName = influxDBMetadataService.getMeasurementNameOrDefault(alias);
if (configuration.isReplaceUnderscore()) {
measurementName = measurementName.replace('_', '.');
@ -326,7 +332,7 @@ public class InfluxDBPersistenceService implements ModifiablePersistenceService
Object value = InfluxDBStateConvertUtils.stateToObject(storeState);
InfluxPoint.Builder pointBuilder = InfluxPoint.newBuilder(measurementName).withTime(timeStamp)
.withValue(value).withTag(TAG_ITEM_NAME, itemName);
.withValue(value).withTag(TAG_ITEM_NAME, alias);
if (configuration.isAddCategoryTag()) {
String categoryName = Objects.requireNonNullElse(category, "n/a");
@ -342,7 +348,7 @@ public class InfluxDBPersistenceService implements ModifiablePersistenceService
pointBuilder.withTag(TAG_LABEL_NAME, labelName);
}
influxDBMetadataService.getMetaData(itemName)
influxDBMetadataService.getMetaData(alias)
.ifPresent(metadata -> metadata.getConfiguration().forEach(pointBuilder::withTag));
return pointBuilder.build();

View File

@ -13,6 +13,7 @@
package org.openhab.persistence.influxdb.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.persistence.FilterCriteria;
/**
@ -24,12 +25,13 @@ import org.openhab.core.persistence.FilterCriteria;
public interface FilterCriteriaQueryCreator {
/**
* Create query from {@link FilterCriteria}
*
*
* @param criteria Criteria to create query from
* @param retentionPolicy Name of the retentionPolicy/bucket to use in query
* @param alias
* @return Created query as a String
*/
String createQuery(FilterCriteria criteria, String retentionPolicy);
String createQuery(FilterCriteria criteria, String retentionPolicy, @Nullable String alias);
default String getOperationSymbol(FilterCriteria.Operator operator, InfluxDBVersion version) {
return switch (operator) {

View File

@ -42,10 +42,9 @@ public class InfluxDBMetadataService {
* get the measurement name from the item metadata or return the provided default
*
* @param itemName the item name
* @param defaultName the default measurement name (
* @return the metadata measurement name if present, defaultName otherwise
*/
public String getMeasurementNameOrDefault(String itemName, String defaultName) {
public String getMeasurementNameOrDefault(String itemName) {
Optional<Metadata> metadata = getMetaData(itemName);
if (metadata.isPresent()) {
String metaName = metadata.get().getValue();
@ -54,7 +53,7 @@ public class InfluxDBMetadataService {
}
}
return defaultName;
return itemName;
}
/**

View File

@ -17,6 +17,7 @@ import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.persistence.FilterCriteria;
/**
@ -63,10 +64,11 @@ public interface InfluxDBRepository {
* Executes Flux query
*
* @param filter the query filter
* @param alias
* @return Query results
*
*
*/
List<InfluxRow> query(FilterCriteria filter, String retentionPolicy);
List<InfluxRow> query(FilterCriteria filter, String retentionPolicy, @Nullable String alias);
/**
* Write points to database

View File

@ -50,9 +50,10 @@ public class InfluxDB1FilterCriteriaQueryCreatorImpl implements FilterCriteriaQu
}
@Override
public String createQuery(FilterCriteria criteria, String retentionPolicy) {
public String createQuery(FilterCriteria criteria, String retentionPolicy, @Nullable String alias) {
final String itemName = Objects.requireNonNull(criteria.getItemName()); // we checked non-null before
final String tableName = getTableName(itemName);
final String localAlias = alias != null ? alias : itemName;
final String tableName = getTableName(localAlias);
final boolean hasCriteriaName = itemName != null;
Select select = select().column("\"" + COLUMN_VALUE_NAME_V1 + "\"::field")
@ -61,8 +62,8 @@ public class InfluxDB1FilterCriteriaQueryCreatorImpl implements FilterCriteriaQu
Where where = select.where();
if (itemName != null && !tableName.equals(itemName)) {
where.and(BuiltQuery.QueryBuilder.eq(TAG_ITEM_NAME, itemName));
if (localAlias != null && !tableName.equals(localAlias)) {
where.and(BuiltQuery.QueryBuilder.eq(TAG_ITEM_NAME, localAlias));
}
if (criteria.getBeginDate() != null) {
where.and(BuiltQuery.QueryBuilder.gte(COLUMN_TIME_NAME_V1, criteria.getBeginDate().toInstant().toString()));
@ -99,7 +100,7 @@ public class InfluxDB1FilterCriteriaQueryCreatorImpl implements FilterCriteriaQu
return "/.*/";
}
String name = influxDBMetadataService.getMeasurementNameOrDefault(itemName, itemName);
String name = influxDBMetadataService.getMeasurementNameOrDefault(itemName);
if (configuration.isReplaceUnderscore()) {
name = name.replace('_', '.');

View File

@ -12,10 +12,7 @@
*/
package org.openhab.persistence.influxdb.internal.influx1;
import static org.openhab.persistence.influxdb.internal.InfluxDBConstants.COLUMN_TIME_NAME_V1;
import static org.openhab.persistence.influxdb.internal.InfluxDBConstants.COLUMN_VALUE_NAME_V1;
import static org.openhab.persistence.influxdb.internal.InfluxDBConstants.FIELD_VALUE_NAME;
import static org.openhab.persistence.influxdb.internal.InfluxDBConstants.TAG_ITEM_NAME;
import static org.openhab.persistence.influxdb.internal.InfluxDBConstants.*;
import java.time.Instant;
import java.util.ArrayList;
@ -168,11 +165,11 @@ public class InfluxDB1RepositoryImpl implements InfluxDBRepository {
}
@Override
public List<InfluxRow> query(FilterCriteria filter, String retentionPolicy) {
public List<InfluxRow> query(FilterCriteria filter, String retentionPolicy, @Nullable String alias) {
try {
final InfluxDB currentClient = client;
if (currentClient != null) {
String query = queryCreator.createQuery(filter, retentionPolicy);
String query = queryCreator.createQuery(filter, retentionPolicy, alias);
logger.trace("Query {}", query);
Query parsedQuery = new Query(query, configuration.getDatabaseName());
List<QueryResult.Result> results = currentClient.query(parsedQuery, TimeUnit.MILLISECONDS).getResults();

View File

@ -12,8 +12,7 @@
*/
package org.openhab.persistence.influxdb.internal.influx2;
import static com.influxdb.query.dsl.functions.restriction.Restrictions.measurement;
import static com.influxdb.query.dsl.functions.restriction.Restrictions.tag;
import static com.influxdb.query.dsl.functions.restriction.Restrictions.*;
import static org.openhab.persistence.influxdb.internal.InfluxDBConstants.*;
import static org.openhab.persistence.influxdb.internal.InfluxDBStateConvertUtils.stateToObject;
@ -21,6 +20,7 @@ import java.time.temporal.ChronoUnit;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.types.State;
import org.openhab.persistence.influxdb.internal.FilterCriteriaQueryCreator;
@ -49,7 +49,7 @@ public class InfluxDB2FilterCriteriaQueryCreatorImpl implements FilterCriteriaQu
}
@Override
public String createQuery(FilterCriteria criteria, String retentionPolicy) {
public String createQuery(FilterCriteria criteria, String retentionPolicy, @Nullable String alias) {
Flux flux = Flux.from(retentionPolicy);
RangeFlux range = flux.range();
@ -66,7 +66,8 @@ public class InfluxDB2FilterCriteriaQueryCreatorImpl implements FilterCriteriaQu
flux = range;
String itemName = Objects.requireNonNull(criteria.getItemName()); // we checked non-null before
String name = influxDBMetadataService.getMeasurementNameOrDefault(itemName, itemName);
final String localAlias = alias != null ? alias : itemName;
String name = influxDBMetadataService.getMeasurementNameOrDefault(localAlias);
String measurementName = configuration.isReplaceUnderscore() ? name.replace('_', '.') : name;
flux = flux.filter(measurement().equal(measurementName));
if (!measurementName.equals(itemName)) {

View File

@ -176,7 +176,7 @@ public class InfluxDB2RepositoryImpl implements InfluxDBRepository {
String predicate = "";
String itemName = filter.getItemName();
if (itemName != null) {
String name = influxDBMetadataService.getMeasurementNameOrDefault(itemName, itemName);
String name = influxDBMetadataService.getMeasurementNameOrDefault(itemName);
String measurementName = configuration.isReplaceUnderscore() ? name.replace('_', '.') : name;
predicate = "(_measurement=\"" + measurementName + "\")";
}
@ -213,11 +213,11 @@ public class InfluxDB2RepositoryImpl implements InfluxDBRepository {
}
@Override
public List<InfluxRow> query(FilterCriteria filter, String retentionPolicy) {
public List<InfluxRow> query(FilterCriteria filter, String retentionPolicy, @Nullable String alias) {
try {
final QueryApi currentQueryAPI = queryAPI;
if (currentQueryAPI != null) {
String query = queryCreator.createQuery(filter, retentionPolicy);
String query = queryCreator.createQuery(filter, retentionPolicy, alias);
logger.trace("Query {}", query);
List<FluxTable> clientResult = currentQueryAPI.query(query);
return clientResult.stream().flatMap(this::mapRawResultToHistoric).toList();

View File

@ -76,11 +76,11 @@ public class InfluxFilterCriteriaQueryCreatorImplTest {
public void testSimpleItemQueryWithoutParams() {
FilterCriteria criteria = createBaseCriteria();
String queryV1 = instanceV1.createQuery(criteria, RETENTION_POLICY);
String queryV1 = instanceV1.createQuery(criteria, RETENTION_POLICY, null);
assertThat(queryV1,
equalTo("SELECT \"value\"::field,\"item\"::tag FROM \"origin\".\"sampleItem\" ORDER BY time DESC;"));
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY);
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY, null);
assertThat(queryV2, equalTo("""
from(bucket:"origin")
\t|> range(start:-100y, stop:100y)
@ -97,13 +97,13 @@ public class InfluxFilterCriteriaQueryCreatorImplTest {
criteria.setBeginDate(now);
criteria.setEndDate(tomorrow);
String queryV1 = instanceV1.createQuery(criteria, RETENTION_POLICY);
String queryV1 = instanceV1.createQuery(criteria, RETENTION_POLICY, null);
String expectedQueryV1 = String.format(
"SELECT \"value\"::field,\"item\"::tag FROM \"origin\".\"sampleItem\" WHERE time >= '%s' AND time <= '%s' ORDER BY time DESC;",
now.toInstant(), tomorrow.toInstant());
assertThat(queryV1, equalTo(expectedQueryV1));
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY);
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY, null);
String expectedQueryV2 = String.format("""
from(bucket:"origin")
\t|> range(start:%s, stop:%s)
@ -120,11 +120,11 @@ public class InfluxFilterCriteriaQueryCreatorImplTest {
criteria.setOperator(FilterCriteria.Operator.LTE);
criteria.setState(new PercentType(90));
String query = instanceV1.createQuery(criteria, RETENTION_POLICY);
String query = instanceV1.createQuery(criteria, RETENTION_POLICY, null);
assertThat(query, equalTo(
"SELECT \"value\"::field,\"item\"::tag FROM \"origin\".\"sampleItem\" WHERE value <= 90 ORDER BY time DESC;"));
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY);
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY, null);
assertThat(queryV2, equalTo("""
from(bucket:"origin")
\t|> range(start:-100y, stop:100y)
@ -140,11 +140,11 @@ public class InfluxFilterCriteriaQueryCreatorImplTest {
criteria.setPageNumber(2);
criteria.setPageSize(10);
String query = instanceV1.createQuery(criteria, RETENTION_POLICY);
String query = instanceV1.createQuery(criteria, RETENTION_POLICY, null);
assertThat(query, equalTo(
"SELECT \"value\"::field,\"item\"::tag FROM \"origin\".\"sampleItem\" ORDER BY time DESC LIMIT 10 OFFSET 20;"));
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY);
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY, null);
assertThat(queryV2, equalTo("""
from(bucket:"origin")
\t|> range(start:-100y, stop:100y)
@ -159,11 +159,11 @@ public class InfluxFilterCriteriaQueryCreatorImplTest {
FilterCriteria criteria = createBaseCriteria();
criteria.setOrdering(FilterCriteria.Ordering.ASCENDING);
String query = instanceV1.createQuery(criteria, RETENTION_POLICY);
String query = instanceV1.createQuery(criteria, RETENTION_POLICY, null);
assertThat(query,
equalTo("SELECT \"value\"::field,\"item\"::tag FROM \"origin\".\"sampleItem\" ORDER BY time ASC;"));
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY);
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY, null);
assertThat(queryV2, equalTo("""
from(bucket:"origin")
\t|> range(start:-100y, stop:100y)
@ -177,7 +177,7 @@ public class InfluxFilterCriteriaQueryCreatorImplTest {
FilterCriteria criteria = createBaseCriteria();
criteria.setOrdering(FilterCriteria.Ordering.DESCENDING);
criteria.setPageSize(1);
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY);
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY, null);
assertThat(queryV2, equalTo("""
from(bucket:"origin")
\t|> range(start:-100y, stop:100y)
@ -200,11 +200,11 @@ public class InfluxFilterCriteriaQueryCreatorImplTest {
when(metadataRegistry.get(metadataKey))
.thenReturn(new Metadata(metadataKey, "measurementName", Map.of("key1", "val1", "key2", "val2")));
String queryV1 = instanceV1.createQuery(criteria, RETENTION_POLICY);
String queryV1 = instanceV1.createQuery(criteria, RETENTION_POLICY, null);
assertThat(queryV1, equalTo(
"SELECT \"value\"::field,\"item\"::tag FROM \"origin\".\"measurementName\" WHERE item = 'sampleItem' ORDER BY time DESC;"));
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY);
String queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY, null);
assertThat(queryV2, equalTo("""
from(bucket:"origin")
\t|> range(start:-100y, stop:100y)
@ -215,11 +215,11 @@ public class InfluxFilterCriteriaQueryCreatorImplTest {
when(metadataRegistry.get(metadataKey))
.thenReturn(new Metadata(metadataKey, "", Map.of("key1", "val1", "key2", "val2")));
queryV1 = instanceV1.createQuery(criteria, RETENTION_POLICY);
queryV1 = instanceV1.createQuery(criteria, RETENTION_POLICY, null);
assertThat(queryV1,
equalTo("SELECT \"value\"::field,\"item\"::tag FROM \"origin\".\"sampleItem\" ORDER BY time DESC;"));
queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY);
queryV2 = instanceV2.createQuery(criteria, RETENTION_POLICY, null);
assertThat(queryV2, equalTo("""
from(bucket:"origin")
\t|> range(start:-100y, stop:100y)

View File

@ -212,9 +212,10 @@ public class JdbcMapper {
logTime("alterTableColumn", timerStart, System.currentTimeMillis());
}
protected void storeItemValue(Item item, State itemState, @Nullable ZonedDateTime date) throws JdbcException {
protected void storeItemValue(Item item, State itemState, @Nullable ZonedDateTime date, @Nullable String alias)
throws JdbcException {
logger.debug("JDBC::storeItemValue: item={} state={} date={}", item, itemState, date);
String tableName = getTable(item);
String tableName = getTable(item, alias);
long timerStart = System.currentTimeMillis();
if (date == null) {
conf.getDBDAO().doStoreItemValue(item, itemState, new ItemVO(tableName, null));
@ -353,8 +354,8 @@ public class JdbcMapper {
}
}
protected String getTable(Item item) throws JdbcException {
String itemName = item.getName();
protected String getTable(Item item, @Nullable String alias) throws JdbcException {
String itemName = alias != null ? alias : item.getName();
if (!initialized) {
throw new JdbcException("Not initialized, unable to find table for item " + itemName);
}

View File

@ -137,27 +137,26 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
@Override
public void store(Item item) {
scheduler.execute(() -> internalStore(item, null, item.getState()));
scheduler.execute(() -> internalStore(item, null, item.getState(), null));
}
@Override
public void store(Item item, @Nullable String alias) {
// alias is not supported
scheduler.execute(() -> internalStore(item, null, item.getState()));
scheduler.execute(() -> internalStore(item, null, item.getState(), alias));
}
@Override
public void store(Item item, ZonedDateTime date, State state) {
scheduler.execute(() -> internalStore(item, date, state));
scheduler.execute(() -> internalStore(item, date, state, null));
}
@Override
public void store(Item item, ZonedDateTime date, State state, @Nullable String alias) {
// alias is not supported
scheduler.execute(() -> internalStore(item, date, state));
scheduler.execute(() -> internalStore(item, date, state, alias));
}
private synchronized void internalStore(Item item, @Nullable ZonedDateTime date, State state) {
private synchronized void internalStore(Item item, @Nullable ZonedDateTime date, State state,
@Nullable String alias) {
// Do not store undefined/uninitialized data
if (state instanceof UnDefType) {
logger.debug("JDBC::store: ignore Item '{}' because it is UnDefType", item.getName());
@ -171,7 +170,7 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
}
try {
long timerStart = System.currentTimeMillis();
storeItemValue(item, state, date);
storeItemValue(item, state, date, alias);
if (logger.isDebugEnabled()) {
logger.debug("JDBC: Stored item '{}' as '{}' in SQL database at {} in {} ms.", item.getName(), state,
new Date(), System.currentTimeMillis() - timerStart);
@ -190,12 +189,24 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
* Queries the {@link PersistenceService} for data with a given filter
* criteria
*
* @param filter
* the filter to apply to the query
* @param filter the filter to apply to the query
* @return a time series of items
*/
@Override
public Iterable<HistoricItem> query(FilterCriteria filter) {
return query(filter, null);
}
/**
* Queries the {@link PersistenceService} for data with a given filter
* criteria
*
* @param filter the filter to apply to the query
* @param alias for the item
* @return a time series of items
*/
@Override
public Iterable<HistoricItem> query(FilterCriteria filter, @Nullable String alias) {
if (!checkDBAccessability()) {
logger.warn("JDBC::query: database not connected, query aborted for item '{}'", filter.getItemName());
return List.of();
@ -231,9 +242,11 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
}
}
String table = itemNameToTableNameMap.get(itemName);
String localAlias = alias != null ? alias : itemName;
String table = itemNameToTableNameMap.get(localAlias);
if (table == null) {
logger.debug("JDBC::query: unable to find table for item with name: '{}', no data in database.", itemName);
logger.debug("JDBC::query: unable to find table for item with name or alias: '{}', no data in database.",
localAlias);
return List.of();
}

View File

@ -187,6 +187,11 @@ public class JpaPersistenceService implements QueryablePersistenceService {
@Override
public Iterable<HistoricItem> query(FilterCriteria filter) {
return query(filter, null);
}
@Override
public Iterable<HistoricItem> query(FilterCriteria filter, @Nullable String alias) {
logger.debug("Querying for historic item: {}", filter.getItemName());
if (!initialized) {
@ -235,7 +240,7 @@ public class JpaPersistenceService implements QueryablePersistenceService {
logger.debug("Creating query...");
Query query = em.createQuery(queryString);
query.setParameter("itemName", item.getName());
query.setParameter("itemName", alias != null ? alias : item.getName());
if (hasBeginDate) {
query.setParameter("beginDate", Date.from(filter.getBeginDate().toInstant()));
}

View File

@ -247,6 +247,15 @@ public class MongoDBPersistenceService implements ModifiablePersistenceService {
@Override
public Iterable<HistoricItem> query(FilterCriteria filter) {
return query(filter, null);
}
@Override
public Iterable<HistoricItem> query(FilterCriteria filter, @Nullable String alias) {
String realItemName = filter.getItemName();
if (alias != null) {
filter.setItemName(alias);
}
MongoCollection<Document> collection = prepareCollection(filter);
// If collection creation failed, return nothing.
if (collection == null) {
@ -259,8 +268,6 @@ public class MongoDBPersistenceService implements ModifiablePersistenceService {
return Collections.emptyList();
}
@Nullable
String realItemName = filter.getItemName();
if (realItemName == null) {
logger.warn("Item name is missing in filter {}", filter);
return Collections.emptyList();
@ -354,7 +361,8 @@ public class MongoDBPersistenceService implements ModifiablePersistenceService {
}
String realItemName = item.getName();
String collectionName = collectionPerItem ? realItemName : this.collection;
String name = (alias != null) ? alias : realItemName;
String collectionName = collectionPerItem ? name : this.collection;
@Nullable
MongoCollection<Document> collection = connectToCollection(collectionName);
@ -364,7 +372,6 @@ public class MongoDBPersistenceService implements ModifiablePersistenceService {
return;
}
String name = (alias != null) ? alias : realItemName;
Object value = MongoDBTypeConversions.convertValue(state);
Document obj = new Document();

View File

@ -12,8 +12,7 @@
*/
package org.openhab.persistence.mongodb.internal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
import java.text.DateFormat;
import java.time.Instant;
@ -30,6 +29,7 @@ import java.util.stream.Collectors;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.eclipse.jdt.annotation.NonNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@ -46,6 +46,7 @@ import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.persistence.HistoricItem;
import org.osgi.framework.BundleContext;
@ -206,7 +207,7 @@ public class MongoDBPersistenceServiceTest {
// Verification
MongoCollection<Document> collection = database.getCollection("testCollection");
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
List<Document> documents = collection.find().into(new ArrayList<>());
assertEquals(1, documents.size()); // Assert that there is only one document
@ -244,7 +245,7 @@ public class MongoDBPersistenceServiceTest {
// Verification
MongoCollection<Document> collection = database.getCollection("testCollection");
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
List<Document> documents = collection.find().into(new ArrayList<>());
assertEquals(1, documents.size()); // Assert that there is only one document
@ -284,7 +285,7 @@ public class MongoDBPersistenceServiceTest {
// Verification
MongoCollection<Document> collection = database.getCollection("testCollection");
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
List<Document> documents = collection.find().into(new ArrayList<>());
assertEquals(2, documents.size()); // Assert that there are two documents
@ -326,7 +327,7 @@ public class MongoDBPersistenceServiceTest {
// Verification
MongoCollection<Document> collection = database.getCollection("testCollection");
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
List<Document> documents = collection.find().into(new ArrayList<>());
assertEquals(2, documents.size()); // Assert that there are two documents
@ -366,7 +367,7 @@ public class MongoDBPersistenceServiceTest {
// Verification
MongoCollection<Document> collection = database.getCollection("testCollection");
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
List<Document> documents = collection.find().into(new ArrayList<>());
assertEquals(1, documents.size()); // Assert that there is only one document
@ -378,6 +379,48 @@ public class MongoDBPersistenceServiceTest {
}
}
/**
* Tests the reading of String Items stored with an alias
*
* @param dbContainer The container running the MongoDB instance.
*/
@ParameterizedTest
@MethodSource("org.openhab.persistence.mongodb.internal.DataCreationHelper#provideDatabaseBackends")
public void testQueryStringWithAlias(DatabaseTestContainer dbContainer) {
try {
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
MongoDBPersistenceService service = setupResult.service;
MongoDatabase database = setupResult.database;
service.activate(setupResult.bundleContext, setupResult.config);
MongoCollection<Document> collection = database.getCollection("testCollection");
StringItem item = DataCreationHelper.createStringItem("TestItem", "TestValue");
try {
Mockito.when(setupResult.itemRegistry.getItem("TestItem")).thenReturn(item);
} catch (ItemNotFoundException e) {
}
String alias = "AliasName";
Document obj = new Document();
obj.put(MongoDBFields.FIELD_ID, new ObjectId());
obj.put(MongoDBFields.FIELD_ITEM, alias);
obj.put(MongoDBFields.FIELD_REALNAME, "TestItem");
obj.put(MongoDBFields.FIELD_TIMESTAMP, new Date());
obj.put(MongoDBFields.FIELD_VALUE, "TestValue");
collection.insertOne(obj);
// Execution
FilterCriteria filter = DataCreationHelper.createFilterCriteria("TestItem");
@NonNull
Iterable<@NonNull HistoricItem> result = service.query(filter, alias);
VerificationHelper.verifyQueryResult(result, new StringType("TestValue"));
} finally {
dbContainer.stop();
}
}
/**
* Tests the query method of MongoDBPersistenceService with NumberItems in a single collection.
*
@ -607,7 +650,7 @@ public class MongoDBPersistenceServiceTest {
// Verification
MongoCollection<Document> collection = database.getCollection("testCollection");
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
List<Document> documents = collection.find().into(new ArrayList<>());
assertEquals(1, documents.size()); // Assert that there is only one document
@ -735,7 +778,7 @@ public class MongoDBPersistenceServiceTest {
service.store(item, null);
// Verification
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
List<Document> documents = collection.find().into(new ArrayList<>());
assertEquals(1, documents.size()); // Assert that there is only one document
@ -793,7 +836,6 @@ public class MongoDBPersistenceServiceTest {
/**
* Tests the toString of a MongoDBItem
*
*
* @param item The item to store in the database.
*/
@ -840,7 +882,7 @@ public class MongoDBPersistenceServiceTest {
// Verification
MongoCollection<Document> collection = database.getCollection("TestItem");
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
List<Document> documents = collection.find().into(new ArrayList<>());
assertEquals(1, documents.size()); // Assert that there is only one document
@ -880,8 +922,8 @@ public class MongoDBPersistenceServiceTest {
service.store(item, now, historicState, "AliasName");
// Verification
MongoCollection<Document> collection = database.getCollection("TestItem");
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
MongoCollection<Document> collection = database.getCollection("AliasName");
List<Document> documents = collection.find().into(new ArrayList<>());
assertEquals(1, documents.size()); // Assert that there is only one document
@ -919,7 +961,7 @@ public class MongoDBPersistenceServiceTest {
// Verification
MongoCollection<Document> collection = database.getCollection("testcollection");
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
List<Document> documents = collection.find().into(new ArrayList<>());
assertEquals(1, documents.size()); // Assert that there is the other document
@ -959,7 +1001,7 @@ public class MongoDBPersistenceServiceTest {
MongoCollection<Document> collection = database.getCollection("testcollection");
// Query the database for all data points
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());
List<Document> documents = collection.find().into(new ArrayList<>());
// Create a set of the returned data points
Set<PersistenceTestItem> returnedData = documents.stream()

View File

@ -333,7 +333,7 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
if (oldValue != null && !oldValue.equals(value)) {
logger.debug(
"Discarding value {} for item {} with timestamp {} because a new value ({}) arrived with the same timestamp.",
oldValue, name, now, value);
oldValue, item.getName(), now, value);
}
}
@ -411,6 +411,11 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
@Override
public Iterable<HistoricItem> query(FilterCriteria filter) {
return query(filter, null);
}
@Override
public Iterable<HistoricItem> query(FilterCriteria filter, @Nullable String alias) {
ZonedDateTime filterBeginDate = filter.getBeginDate();
ZonedDateTime filterEndDate = filter.getEndDate();
if (filterBeginDate != null && filterEndDate != null && filterBeginDate.isAfter(filterEndDate)) {
@ -424,9 +429,10 @@ public class RRD4jPersistenceService implements QueryablePersistenceService {
}
logger.trace("Querying rrd4j database for item '{}'", itemName);
String localAlias = alias != null ? alias : itemName;
RrdDb db = null;
try {
db = getDB(itemName, false);
db = getDB(localAlias, false);
} catch (Exception e) {
logger.warn("Failed to open rrd4j database '{}' for querying ({})", itemName, e.toString());
return List.of();

View File

@ -38,6 +38,8 @@ import org.openhab.core.items.GroupItem;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.persistence.registry.PersistenceServiceConfiguration;
import org.openhab.core.persistence.registry.PersistenceServiceConfigurationRegistry;
import org.openhab.core.ui.chart.ChartProvider;
import org.openhab.core.ui.items.ItemUIRegistry;
import org.openhab.persistence.rrd4j.internal.RRD4jPersistenceService;
@ -106,13 +108,16 @@ public class RRD4jChartServlet implements Servlet, ChartProvider {
private final HttpService httpService;
private final ItemUIRegistry itemUIRegistry;
private final TimeZoneProvider timeZoneProvider;
private final PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistry;
@Activate
public RRD4jChartServlet(final @Reference HttpService httpService, final @Reference ItemUIRegistry itemUIRegistry,
final @Reference TimeZoneProvider timeZoneProvider) {
final @Reference TimeZoneProvider timeZoneProvider,
final @Reference PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistry) {
this.httpService = httpService;
this.itemUIRegistry = itemUIRegistry;
this.timeZoneProvider = timeZoneProvider;
this.persistenceServiceConfigurationRegistry = persistenceServiceConfigurationRegistry;
}
@Activate
@ -179,10 +184,10 @@ public class RRD4jChartServlet implements Servlet, ChartProvider {
* @param item the item to add a line for
* @param counter defines the number of the datasource and is used to determine the line color
*/
protected void addLine(RrdGraphDef graphDef, Item item, int counter) {
protected void addLine(RrdGraphDef graphDef, Item item, @Nullable String alias, int counter) {
Color color = LINECOLORS[counter % LINECOLORS.length];
String label = itemUIRegistry.getLabel(item.getName());
String rrdName = RRD4jPersistenceService.getDatabasePath(item.getName()).toString();
String rrdName = RRD4jPersistenceService.getDatabasePath(alias != null ? alias : item.getName()).toString();
ConsolFun consolFun;
if (label != null && label.contains("[") && label.contains("]")) {
label = label.substring(0, label.indexOf('['));
@ -251,14 +256,18 @@ public class RRD4jChartServlet implements Servlet, ChartProvider {
graphDef.setFont(FontTag.TITLE, new Font("SansSerif", Font.PLAIN, 15));
graphDef.setFont(FontTag.DEFAULT, new Font("SansSerif", Font.PLAIN, 11));
PersistenceServiceConfiguration config = persistenceServiceConfigurationRegistry
.get(RRD4jPersistenceService.SERVICE_ID);
int seriesCounter = 0;
// Loop through all the items
if (items != null) {
String[] itemNames = items.split(",");
for (String itemName : itemNames) {
String alias = config != null ? config.getAliases().get(itemName) : null;
Item item = itemUIRegistry.getItem(itemName);
addLine(graphDef, item, seriesCounter++);
addLine(graphDef, item, alias, seriesCounter++);
}
}
@ -269,7 +278,8 @@ public class RRD4jChartServlet implements Servlet, ChartProvider {
Item item = itemUIRegistry.getItem(groupName);
if (item instanceof GroupItem groupItem) {
for (Item member : groupItem.getMembers()) {
addLine(graphDef, member, seriesCounter++);
String alias = config != null ? config.getAliases().get(member.getName()) : null;
addLine(graphDef, member, alias, seriesCounter++);
}
} else {
throw new ItemNotFoundException("Item '" + item.getName() + "' defined in groups is not a group.");

View File

@ -17,7 +17,10 @@ import java.nio.file.Path;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@ -30,6 +33,8 @@ import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.persistence.PersistenceService;
import org.openhab.core.persistence.PersistenceServiceRegistry;
import org.openhab.core.persistence.registry.PersistenceServiceConfiguration;
import org.openhab.core.persistence.registry.PersistenceServiceConfigurationRegistry;
import org.openhab.persistence.rrd4j.internal.RRD4jPersistenceService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
@ -51,13 +56,16 @@ public class RRD4jCommandExtension extends AbstractConsoleCommandExtension imple
false);
private final PersistenceServiceRegistry persistenceServiceRegistry;
private final PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistry;
private final ItemRegistry itemRegistry;
@Activate
public RRD4jCommandExtension(final @Reference PersistenceServiceRegistry persistenceServiceRegistry,
final @Reference ItemRegistry itemRegistry) {
final @Reference ItemRegistry itemRegistry,
final @Reference PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistry) {
super(RRD4jPersistenceService.SERVICE_ID, "Interact with the RRD4j persistence service.");
this.persistenceServiceRegistry = persistenceServiceRegistry;
this.persistenceServiceConfigurationRegistry = persistenceServiceConfigurationRegistry;
this.itemRegistry = itemRegistry;
}
@ -104,6 +112,11 @@ public class RRD4jCommandExtension extends AbstractConsoleCommandExtension imple
Collections.sort(filenames, Comparator.naturalOrder());
}
PersistenceServiceConfiguration config = persistenceServiceConfigurationRegistry
.get(RRD4jPersistenceService.SERVICE_ID);
Stream<Entry<String, String>> aliases = config != null ? config.getAliases().entrySet().stream()
: Stream.empty();
console.println((checkOnly ? "Checking" : "Cleaning") + " RRD files...");
int nb = 0;
for (String filename : filenames) {
@ -114,7 +127,10 @@ public class RRD4jCommandExtension extends AbstractConsoleCommandExtension imple
} else {
boolean itemFound;
try {
itemRegistry.getItem(name);
// Map alias back to item
String item = Objects.requireNonNull(
aliases.filter(e -> name.equals(e.getValue())).findAny().map(e -> e.getKey()).orElse(name));
itemRegistry.getItem(item);
itemFound = true;
} catch (ItemNotFoundException e) {
itemFound = false;