[jdbc] Return QuantityTypes for number items with dimension (#9426)

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
This commit is contained in:
Christoph Weitkamp 2021-01-03 17:34:53 +01:00 committed by GitHub
parent 3616c58abe
commit 9394dc4676
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 140 additions and 130 deletions

View File

@ -18,12 +18,16 @@ import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import javax.measure.Quantity;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.Nullable;
import org.knowm.yank.Yank;
import org.openhab.core.items.GroupItem;
import org.openhab.core.items.Item;
@ -41,7 +45,9 @@ import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.persistence.FilterCriteria.Ordering;
import org.openhab.core.persistence.HistoricItem;
@ -334,12 +340,11 @@ public class JdbcBaseDAO {
String sql = histItemFilterQueryProvider(filter, numberDecimalcount, table, name, timeZone);
logger.debug("JDBC::doGetHistItemFilterQuery sql={}", sql);
List<Object[]> m = Yank.queryObjectArrays(sql, null);
List<HistoricItem> items = new ArrayList<>();
for (int i = 0; i < m.size(); i++) {
items.add(new JdbcHistoricItem(item.getName(), getState(item, m.get(i)[1]), objectAsDate(m.get(i)[0])));
}
return items;
// we already retrieve the unit here once as it is a very costly operation
String itemName = item.getName();
Unit<? extends Quantity<?>> unit = item instanceof NumberItem ? ((NumberItem) item).getUnit() : null;
return m.stream().map(o -> new JdbcHistoricItem(itemName, getState(item, unit, o[1]), objectAsDate(o[0])))
.collect(Collectors.<HistoricItem> toList());
}
/*************
@ -347,11 +352,11 @@ public class JdbcBaseDAO {
*************/
static final DateTimeFormatter JDBC_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
protected String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
String simpleName, ZoneId timeZone) {
logger.debug(
"JDBC::getHistItemFilterQueryProvider filter = {}, numberDecimalcount = {}, table = {}, simpleName = {}",
filter.toString(), numberDecimalcount, table, simpleName);
filter, numberDecimalcount, table, simpleName);
String filterString = "";
if (filter.getBeginDate() != null) {
@ -399,61 +404,82 @@ public class JdbcBaseDAO {
// insertItemValue
logger.debug("JDBC::storeItemValueProvider: getState: '{}'", item.getState());
if ("COLORITEM".equals(itemType)) {
vo.setValueTypes(getSqlTypes().get(itemType), java.lang.String.class);
vo.setValue(item.getState().toString());
} else if ("NUMBERITEM".equals(itemType)) {
String it = getSqlTypes().get(itemType);
if (it.toUpperCase().contains("DOUBLE")) {
vo.setValueTypes(it, java.lang.Double.class);
Number newVal = (Number) item.getState();
logger.debug("JDBC::storeItemValueProvider: newVal.doubleValue: '{}'", newVal.doubleValue());
vo.setValue(newVal.doubleValue());
} else if (it.toUpperCase().contains("DECIMAL") || it.toUpperCase().contains("NUMERIC")) {
vo.setValueTypes(it, java.math.BigDecimal.class);
BigDecimal newVal = BigDecimal.valueOf(((Number) item.getState()).doubleValue());
logger.debug("JDBC::storeItemValueProvider: newVal.toBigDecimal: '{}'", newVal);
vo.setValue(newVal);
} else if (it.toUpperCase().contains("INT")) {
vo.setValueTypes(it, java.lang.Integer.class);
Number newVal = (Number) item.getState();
logger.debug("JDBC::storeItemValueProvider: newVal.intValue: '{}'", newVal.intValue());
vo.setValue(newVal.intValue());
} else {// fall back to String
vo.setValueTypes(it, java.lang.String.class);
logger.warn("JDBC::storeItemValueProvider: item.getState().toString(): '{}'", item.getState());
/*
* !!ATTENTION!!
*
* 1. DimmerItem.getStateAs(PercentType.class).toString() always
* returns 0
* RollershutterItem.getStateAs(PercentType.class).toString() works
* as expected
*
* 2. (item instanceof ColorItem) == (item instanceof DimmerItem) =
* true Therefore for instance tests ColorItem always has to be
* tested before DimmerItem
*
* !!ATTENTION!!
*/
switch (itemType) {
case "COLORITEM":
vo.setValueTypes(getSqlTypes().get(itemType), java.lang.String.class);
vo.setValue(item.getState().toString());
}
} else if ("ROLLERSHUTTERITEM".equals(itemType) || "DIMMERITEM".equals(itemType)) {
vo.setValueTypes(getSqlTypes().get(itemType), java.lang.Integer.class);
Number newVal = (DecimalType) item.getState();
logger.debug("JDBC::storeItemValueProvider: newVal.intValue: '{}'", newVal.intValue());
vo.setValue(newVal.intValue());
} else if ("DATETIMEITEM".equals(itemType)) {
vo.setValueTypes(getSqlTypes().get(itemType), java.sql.Timestamp.class);
java.sql.Timestamp d = new java.sql.Timestamp(
((DateTimeType) item.getState()).getZonedDateTime().toInstant().toEpochMilli());
logger.debug("JDBC::storeItemValueProvider: DateTimeItem: '{}'", d);
vo.setValue(d);
} else {
/*
* !!ATTENTION!!
*
* 1. DimmerItem.getStateAs(PercentType.class).toString() always
* returns 0
* RollershutterItem.getStateAs(PercentType.class).toString() works
* as expected
*
* 2. (item instanceof ColorItem) == (item instanceof DimmerItem) =
* true Therefore for instance tests ColorItem always has to be
* tested before DimmerItem
*
* !!ATTENTION!!
*/
// All other items should return the best format by default
vo.setValueTypes(getSqlTypes().get(itemType), java.lang.String.class);
logger.debug("JDBC::storeItemValueProvider: other: item.getState().toString(): '{}'", item.getState());
vo.setValue(item.getState().toString());
break;
case "NUMBERITEM":
State state = item.getState();
State convertedState = state;
if (item instanceof NumberItem && state instanceof QuantityType) {
Unit<? extends Quantity<?>> unit = ((NumberItem) item).getUnit();
if (unit != null && !Units.ONE.equals(unit)) {
convertedState = ((QuantityType<?>) state).toUnit(unit);
if (convertedState == null) {
logger.warn(
"JDBC::storeItemValueProvider: Failed to convert state '{}' to unit '{}'. Please check your item definition for correctness.",
state, unit);
convertedState = state;
}
}
}
String it = getSqlTypes().get(itemType);
if (it.toUpperCase().contains("DOUBLE")) {
vo.setValueTypes(it, java.lang.Double.class);
double value = ((Number) convertedState).doubleValue();
logger.debug("JDBC::storeItemValueProvider: newVal.doubleValue: '{}'", value);
vo.setValue(value);
} else if (it.toUpperCase().contains("DECIMAL") || it.toUpperCase().contains("NUMERIC")) {
vo.setValueTypes(it, java.math.BigDecimal.class);
BigDecimal value = BigDecimal.valueOf(((Number) convertedState).doubleValue());
logger.debug("JDBC::storeItemValueProvider: newVal.toBigDecimal: '{}'", value);
vo.setValue(value);
} else if (it.toUpperCase().contains("INT")) {
vo.setValueTypes(it, java.lang.Integer.class);
int value = ((Number) convertedState).intValue();
logger.debug("JDBC::storeItemValueProvider: newVal.intValue: '{}'", value);
vo.setValue(value);
} else {// fall back to String
vo.setValueTypes(it, java.lang.String.class);
logger.warn("JDBC::storeItemValueProvider: item.getState().toString(): '{}'", convertedState);
vo.setValue(convertedState.toString());
}
break;
case "ROLLERSHUTTERITEM":
case "DIMMERITEM":
vo.setValueTypes(getSqlTypes().get(itemType), java.lang.Integer.class);
int value = ((DecimalType) item.getState()).intValue();
logger.debug("JDBC::storeItemValueProvider: newVal.intValue: '{}'", value);
vo.setValue(value);
break;
case "DATETIMEITEM":
vo.setValueTypes(getSqlTypes().get(itemType), java.sql.Timestamp.class);
java.sql.Timestamp d = new java.sql.Timestamp(
((DateTimeType) item.getState()).getZonedDateTime().toInstant().toEpochMilli());
logger.debug("JDBC::storeItemValueProvider: DateTimeItem: '{}'", d);
vo.setValue(d);
break;
default:
// All other items should return the best format by default
vo.setValueTypes(getSqlTypes().get(itemType), java.lang.String.class);
logger.debug("JDBC::storeItemValueProvider: other: item.getState().toString(): '{}'", item.getState());
vo.setValue(item.getState().toString());
break;
}
return vo;
}
@ -461,20 +487,24 @@ public class JdbcBaseDAO {
/*****************
* H E L P E R S *
*****************/
protected State getState(Item item, Object v) {
String clazz = v.getClass().getSimpleName();
logger.debug("JDBC::ItemResultHandler::handleResult getState value = '{}', getClass = '{}', clazz = '{}'",
v.toString(), v.getClass(), clazz);
protected State getState(Item item, @Nullable Unit<? extends Quantity<?>> unit, Object v) {
logger.debug(
"JDBC::ItemResultHandler::handleResult getState value = '{}', unit = '{}', getClass = '{}', clazz = '{}'",
v, unit, v.getClass(), v.getClass().getSimpleName());
if (item instanceof NumberItem) {
String it = getSqlTypes().get("NUMBERITEM");
if (it.toUpperCase().contains("DOUBLE")) {
return new DecimalType(((Number) v).doubleValue());
return unit == null ? new DecimalType(((Number) v).doubleValue())
: QuantityType.valueOf(((Number) v).doubleValue(), unit);
} else if (it.toUpperCase().contains("DECIMAL") || it.toUpperCase().contains("NUMERIC")) {
return new DecimalType((BigDecimal) v);
return unit == null ? new DecimalType((BigDecimal) v)
: QuantityType.valueOf(((BigDecimal) v).doubleValue(), unit);
} else if (it.toUpperCase().contains("INT")) {
return new DecimalType(((Integer) v).intValue());
return unit == null ? new DecimalType(((Integer) v).intValue())
: QuantityType.valueOf(((Integer) v).doubleValue(), unit);
}
return DecimalType.valueOf(((String) v).toString());
return unit == null ? DecimalType.valueOf(((String) v).toString())
: QuantityType.valueOf(((String) v).toString());
} else if (item instanceof ColorItem) {
return HSBType.valueOf(((String) v).toString());
} else if (item instanceof DimmerItem) {
@ -524,11 +554,7 @@ public class JdbcBaseDAO {
if (i instanceof GroupItem) {
item = ((GroupItem) i).getBaseItem();
if (item == null) {
// if GroupItem:<ItemType> is not defined in
// *.items using StringType
// logger.debug("JDBC: BaseItem GroupItem:<ItemType> is not
// defined in *.items searching for first Member and try to use
// as ItemType");
// if GroupItem:<ItemType> is not defined in *.items using StringType
logger.debug(
"JDBC::getItemType: Cannot detect ItemType for {} because the GroupItems' base type isn't set in *.items File.",
i.getName());

View File

@ -13,12 +13,15 @@
package org.openhab.persistence.jdbc.db;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.measure.Quantity;
import javax.measure.Unit;
import org.knowm.yank.Yank;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.persistence.FilterCriteria.Ordering;
import org.openhab.core.persistence.HistoricItem;
@ -160,23 +163,22 @@ public class JdbcDerbyDAO extends JdbcBaseDAO {
String table, String name, ZoneId timeZone) {
String sql = histItemFilterQueryProvider(filter, numberDecimalcount, table, name, timeZone);
List<Object[]> m = Yank.queryObjectArrays(sql, null);
logger.debug("JDBC::doGetHistItemFilterQuery got Array length={}", m.size());
List<HistoricItem> items = new ArrayList<>();
for (int i = 0; i < m.size(); i++) {
logger.debug("JDBC::doGetHistItemFilterQuery 0='{}' 1='{}'", m.get(i)[0], m.get(i)[1]);
items.add(new JdbcHistoricItem(item.getName(), getState(item, m.get(i)[1]), objectAsDate(m.get(i)[0])));
}
return items;
// we already retrieve the unit here once as it is a very costly operation
String itemName = item.getName();
Unit<? extends Quantity<?>> unit = item instanceof NumberItem ? ((NumberItem) item).getUnit() : null;
return m.stream().map(o -> {
logger.debug("JDBC::doGetHistItemFilterQuery 0='{}' 1='{}'", o[0], o[1]);
return new JdbcHistoricItem(itemName, getState(item, unit, o[1]), objectAsDate(o[0]));
}).collect(Collectors.<HistoricItem> toList());
}
/****************************
* SQL generation Providers *
****************************/
static final DateTimeFormatter JDBC_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
@Override
protected String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
String simpleName, ZoneId timeZone) {
logger.debug(
"JDBC::getHistItemFilterQueryProvider filter = {}, numberDecimalcount = {}, table = {}, simpleName = {}",

View File

@ -12,6 +12,7 @@
*/
package org.openhab.persistence.jdbc.db;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.knowm.yank.Yank;
import org.openhab.persistence.jdbc.utils.DbMetaData;
import org.slf4j.Logger;
@ -24,6 +25,7 @@ import org.slf4j.LoggerFactory;
*
* @author Helmut Lehmeyer - Initial contribution
*/
@NonNullByDefault
public class JdbcMariadbDAO extends JdbcBaseDAO {
private final Logger logger = LoggerFactory.getLogger(JdbcMariadbDAO.class);

View File

@ -12,6 +12,7 @@
*/
package org.openhab.persistence.jdbc.db;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.knowm.yank.Yank;
import org.openhab.persistence.jdbc.utils.DbMetaData;
import org.slf4j.Logger;
@ -28,6 +29,7 @@ import org.slf4j.LoggerFactory;
*
* @author Helmut Lehmeyer - Initial contribution
*/
@NonNullByDefault
public class JdbcMysqlDAO extends JdbcBaseDAO {
private final Logger logger = LoggerFactory.getLogger(JdbcMysqlDAO.class);

View File

@ -13,18 +13,14 @@
package org.openhab.persistence.jdbc.db;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import org.knowm.yank.Yank;
import org.openhab.core.items.Item;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.persistence.FilterCriteria.Ordering;
import org.openhab.core.persistence.HistoricItem;
import org.openhab.persistence.jdbc.model.ItemVO;
import org.openhab.persistence.jdbc.model.ItemsVO;
import org.openhab.persistence.jdbc.model.JdbcHistoricItem;
import org.openhab.persistence.jdbc.utils.StringUtilsExt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -145,26 +141,12 @@ public class JdbcPostgresqlDAO extends JdbcBaseDAO {
Yank.execute(sql, params);
}
@Override
public List<HistoricItem> doGetHistItemFilterQuery(Item item, FilterCriteria filter, int numberDecimalcount,
String table, String name, ZoneId timeZone) {
String sql = histItemFilterQueryProvider(filter, numberDecimalcount, table, name, timeZone);
logger.debug("JDBC::doGetHistItemFilterQuery sql={}", sql);
List<Object[]> m = Yank.queryObjectArrays(sql, null);
List<HistoricItem> items = new ArrayList<>();
for (int i = 0; i < m.size(); i++) {
items.add(new JdbcHistoricItem(item.getName(), getState(item, m.get(i)[1]), objectAsDate(m.get(i)[0])));
}
return items;
}
/****************************
* SQL generation Providers *
****************************/
static final DateTimeFormatter JDBC_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
@Override
protected String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
String simpleName, ZoneId timeZone) {
logger.debug(
"JDBC::getHistItemFilterQueryProvider filter = {}, numberDecimalcount = {}, table = {}, simpleName = {}",

View File

@ -146,7 +146,7 @@ public class JdbcMapper {
}
public Item storeItemValue(Item item) {
logger.debug("JDBC::storeItemValue: item={}", item.toString());
logger.debug("JDBC::storeItemValue: item={}", item);
String tableName = getTable(item);
if (tableName == null) {
logger.error("JDBC::store: Unable to store item '{}'.", item.getName());
@ -166,10 +166,11 @@ public class JdbcMapper {
(filter != null), numberDecimalcount, table, item, item.getName());
if (table != null) {
long timerStart = System.currentTimeMillis();
List<HistoricItem> r = conf.getDBDAO().doGetHistItemFilterQuery(item, filter, numberDecimalcount, table,
item.getName(), timeZoneProvider.getTimeZone());
logTime("insertItemValue", timerStart, System.currentTimeMillis());
return r;
List<HistoricItem> result = conf.getDBDAO().doGetHistItemFilterQuery(item, filter, numberDecimalcount,
table, item.getName(), timeZoneProvider.getTimeZone());
logTime("getHistItemFilterQuery", timerStart, System.currentTimeMillis());
errCnt = 0;
return result;
} else {
logger.error("JDBC::getHistItemFilterQuery: TABLE is NULL; cannot get data from non-existent table.");
}
@ -230,13 +231,10 @@ public class JdbcMapper {
logger.info(
"JDBC::checkDBSchema: Rebuild complete, configure the 'rebuildTableNames' setting to 'false' to stop rebuilds on startup");
} else {
List<ItemsVO> al;
// Reset the error counter
errCnt = 0;
al = getItemIDTableNames();
for (int i = 0; i < al.size(); i++) {
String t = getTableName(al.get(i).getItemid(), al.get(i).getItemname());
sqlTables.put(al.get(i).getItemname(), t);
for (ItemsVO vo : getItemIDTableNames()) {
sqlTables.put(vo.getItemname(), getTableName(vo.getItemid(), vo.getItemname()));
}
}
}
@ -303,19 +301,17 @@ public class JdbcMapper {
initialized = false;
}
List<ItemsVO> al;
Map<Integer, String> tableIds = new HashMap<>();
//
al = getItemIDTableNames();
for (int i = 0; i < al.size(); i++) {
String t = getTableName(al.get(i).getItemid(), al.get(i).getItemname());
sqlTables.put(al.get(i).getItemname(), t);
tableIds.put(al.get(i).getItemid(), t);
for (ItemsVO vo : getItemIDTableNames()) {
String t = getTableName(vo.getItemid(), vo.getItemname());
sqlTables.put(vo.getItemname(), t);
tableIds.put(vo.getItemid(), t);
}
//
al = getItemTables();
List<ItemsVO> al = getItemTables();
String oldName = "";
String newName = "";
@ -375,7 +371,7 @@ public class JdbcMapper {
// TODO: in general it would be possible to query the count, earliest and latest values for each item too but it
// would be a very costly operation
return sqlTables.keySet().stream().map(itemName -> new JdbcPersistenceItemInfo(itemName))
.collect(Collectors.<PersistenceItemInfo> toUnmodifiableSet());
.collect(Collectors.<PersistenceItemInfo> toSet());
}
private static String formatRight(final Object value, final int len) {

View File

@ -128,7 +128,7 @@ public class JdbcPersistenceService extends JdbcMapper implements QueryablePersi
*/
@Override
public void store(Item item, @Nullable String alias) {
// Don not store undefined/uninitialised data
// Do not store undefined/uninitialized data
if (item.getState() instanceof UnDefType) {
logger.debug("JDBC::store: ignore Item '{}' because it is UnDefType", item.getName());
return;
@ -141,8 +141,8 @@ public class JdbcPersistenceService extends JdbcMapper implements QueryablePersi
}
long timerStart = System.currentTimeMillis();
storeItemValue(item);
logger.debug("JDBC: Stored item '{}' as '{}' in SQL database at {} in {} ms.", item.getName(),
item.getState().toString(), (new java.util.Date()).toString(), System.currentTimeMillis() - timerStart);
logger.debug("JDBC: Stored item '{}' as '{}' in SQL database at {} in {} ms.", item.getName(), item.getState(),
new java.util.Date(), System.currentTimeMillis() - timerStart);
}
@Override
@ -204,7 +204,7 @@ public class JdbcPersistenceService extends JdbcMapper implements QueryablePersi
long timerStart = System.currentTimeMillis();
List<HistoricItem> items = getHistItemFilterQuery(filter, conf.getNumberDecimalcount(), table, item);
logger.debug("JDBC::query: query for {} returned {} rows in {} ms", item.getName(), items.size(),
logger.debug("JDBC::query: query for {} returned {} rows in {} ms", itemName, items.size(),
System.currentTimeMillis() - timerStart);
// Success