]> git.basschouten.com Git - openhab-addons.git/commitdiff
[jdbc] Implement 'ModifiablePersistenceService' interface (#11922)
authorChristoph Weitkamp <github@christophweitkamp.de>
Sat, 15 Jan 2022 13:29:06 +0000 (14:29 +0100)
committerGitHub <noreply@github.com>
Sat, 15 Jan 2022 13:29:06 +0000 (14:29 +0100)
Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcBaseDAO.java
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcDerbyDAO.java
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcH2DAO.java
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcHsqldbDAO.java
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcPostgresqlDAO.java
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcSqliteDAO.java
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/ItemVO.java
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcMapper.java
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java
bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/db/JdbcBaseDAOTest.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceServiceTest.java [new file with mode: 0644]

index b53988293b6dc09a0ceacd817dc33f0738392f66..9f8087066045c6c2bbe97d17116861c09b83c661 100644 (file)
@@ -13,7 +13,6 @@
 package org.openhab.persistence.jdbc.db;
 
 import java.math.BigDecimal;
-import java.sql.Timestamp;
 import java.time.Instant;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
@@ -327,8 +326,8 @@ public class JdbcBaseDAO {
         Yank.execute(sql, null);
     }
 
-    public void doStoreItemValue(Item item, ItemVO vo) {
-        ItemVO storedVO = storeItemValueProvider(item, vo);
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo) {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
         String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
                 new String[] { "#tableName#", "#tablePrimaryValue#" },
                 new String[] { storedVO.getTableName(), sqlTypes.get("tablePrimaryValue") });
@@ -337,6 +336,16 @@ public class JdbcBaseDAO {
         Yank.execute(sql, params);
     }
 
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo, ZonedDateTime date) {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
+        String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
+                new String[] { "#tableName#", "#tablePrimaryValue#" }, new String[] { storedVO.getTableName(), "?" });
+        java.sql.Timestamp timestamp = new java.sql.Timestamp(date.toInstant().toEpochMilli());
+        Object[] params = new Object[] { storedVO.getValue(), timestamp, storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} timestamp={} value='{}'", sql, timestamp, storedVO.getValue());
+        Yank.execute(sql, params);
+    }
+
     public List<HistoricItem> doGetHistItemFilterQuery(Item item, FilterCriteria filter, int numberDecimalcount,
             String table, String name, ZoneId timeZone) {
         String sql = histItemFilterQueryProvider(filter, numberDecimalcount, table, name, timeZone);
@@ -353,6 +362,12 @@ public class JdbcBaseDAO {
                 .collect(Collectors.<HistoricItem> toList());
     }
 
+    public void doDeleteItemValues(Item item, FilterCriteria filter, String table, ZoneId timeZone) {
+        String sql = histItemFilterDeleteProvider(filter, table, timeZone);
+        logger.debug("JDBC::doDeleteItemValues sql={}", sql);
+        Yank.execute(sql, null);
+    }
+
     /*************
      * Providers *
      *************/
@@ -364,19 +379,9 @@ public class JdbcBaseDAO {
                 "JDBC::getHistItemFilterQueryProvider filter = {}, numberDecimalcount = {}, table = {}, simpleName = {}",
                 filter, numberDecimalcount, table, simpleName);
 
-        String filterString = "";
-        if (filter.getBeginDate() != null) {
-            filterString += filterString.isEmpty() ? " WHERE" : " AND";
-            filterString += " TIME>'" + JDBC_DATE_FORMAT.format(filter.getBeginDate().withZoneSameInstant(timeZone))
-                    + "'";
-        }
-        if (filter.getEndDate() != null) {
-            filterString += filterString.isEmpty() ? " WHERE" : " AND";
-            filterString += " TIME<'" + JDBC_DATE_FORMAT.format(filter.getEndDate().withZoneSameInstant(timeZone))
-                    + "'";
-        }
-        filterString += (filter.getOrdering() == Ordering.ASCENDING) ? " ORDER BY time ASC" : " ORDER BY time DESC ";
-        if (filter.getPageSize() != 0x7fffffff) {
+        String filterString = resolveTimeFilter(filter, timeZone);
+        filterString += (filter.getOrdering() == Ordering.ASCENDING) ? " ORDER BY time ASC" : " ORDER BY time DESC";
+        if (filter.getPageSize() != Integer.MAX_VALUE) {
             filterString += " LIMIT " + filter.getPageNumber() * filter.getPageSize() + "," + filter.getPageSize();
         }
         // SELECT time, ROUND(value,3) FROM number_item_0114 ORDER BY time DESC LIMIT 0,1
@@ -391,6 +396,33 @@ public class JdbcBaseDAO {
         return queryString;
     }
 
+    protected String histItemFilterDeleteProvider(FilterCriteria filter, String table, ZoneId timeZone) {
+        logger.debug("JDBC::histItemFilterDeleteProvider filter = {}, table = {}", filter, table);
+
+        String filterString = resolveTimeFilter(filter, timeZone);
+        String deleteString = "DELETE FROM " + table;
+        if (!filterString.isEmpty()) {
+            deleteString += filterString;
+        }
+        logger.debug("JDBC::delete deleteString = {}", deleteString);
+        return deleteString;
+    }
+
+    protected String resolveTimeFilter(FilterCriteria filter, ZoneId timeZone) {
+        String filterString = "";
+        if (filter.getBeginDate() != null) {
+            filterString += filterString.isEmpty() ? " WHERE" : " AND";
+            filterString += " TIME>'" + JDBC_DATE_FORMAT.format(filter.getBeginDate().withZoneSameInstant(timeZone))
+                    + "'";
+        }
+        if (filter.getEndDate() != null) {
+            filterString += filterString.isEmpty() ? " WHERE" : " AND";
+            filterString += " TIME<'" + JDBC_DATE_FORMAT.format(filter.getEndDate().withZoneSameInstant(timeZone))
+                    + "'";
+        }
+        return filterString;
+    }
+
     private String updateItemTableNamesProvider(List<ItemVO> namesList) {
         logger.debug("JDBC::updateItemTableNamesProvider namesList.size = {}", namesList.size());
         String queryString = "";
@@ -402,14 +434,14 @@ public class JdbcBaseDAO {
         return queryString;
     }
 
-    protected ItemVO storeItemValueProvider(Item item, ItemVO vo) {
+    protected ItemVO storeItemValueProvider(Item item, State itemState, ItemVO vo) {
         String itemType = getItemType(item);
 
         logger.debug("JDBC::storeItemValueProvider: item '{}' as Type '{}' in '{}' with state '{}'", item.getName(),
-                itemType, vo.getTableName(), item.getState());
+                itemType, vo.getTableName(), itemState);
 
         // insertItemValue
-        logger.debug("JDBC::storeItemValueProvider: getState: '{}'", item.getState());
+        logger.debug("JDBC::storeItemValueProvider: itemState: '{}'", itemState);
         /*
          * !!ATTENTION!!
          *
@@ -427,20 +459,19 @@ public class JdbcBaseDAO {
         switch (itemType) {
             case "COLORITEM":
                 vo.setValueTypes(getSqlTypes().get(itemType), java.lang.String.class);
-                vo.setValue(item.getState().toString());
+                vo.setValue(itemState.toString());
                 break;
             case "NUMBERITEM":
-                State state = item.getState();
-                State convertedState = state;
-                if (item instanceof NumberItem && state instanceof QuantityType) {
+                State convertedState = itemState;
+                if (item instanceof NumberItem && itemState instanceof QuantityType) {
                     Unit<? extends Quantity<?>> unit = ((NumberItem) item).getUnit();
                     if (unit != null && !Units.ONE.equals(unit)) {
-                        convertedState = ((QuantityType<?>) state).toUnit(unit);
+                        convertedState = ((QuantityType<?>) itemState).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;
+                                    itemState, unit);
+                            convertedState = itemState;
                         }
                     }
                 }
@@ -462,21 +493,21 @@ public class JdbcBaseDAO {
                     vo.setValue(value);
                 } else {// fall back to String
                     vo.setValueTypes(it, java.lang.String.class);
-                    logger.warn("JDBC::storeItemValueProvider: item.getState().toString(): '{}'", convertedState);
+                    logger.warn("JDBC::storeItemValueProvider: itemState: '{}'", 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();
+                int value = ((DecimalType) itemState).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());
+                        ((DateTimeType) itemState).getZonedDateTime().toInstant().toEpochMilli());
                 logger.debug("JDBC::storeItemValueProvider: DateTimeItem: '{}'", d);
                 vo.setValue(d);
                 break;
@@ -489,8 +520,8 @@ public class JdbcBaseDAO {
             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());
+                logger.debug("JDBC::storeItemValueProvider: other: itemState: '{}'", itemState);
+                vo.setValue(itemState.toString());
                 break;
         }
         return vo;
@@ -533,9 +564,10 @@ public class JdbcBaseDAO {
 
     protected ZonedDateTime objectAsDate(Object v) {
         if (v instanceof java.lang.String) {
-            return ZonedDateTime.ofInstant(Timestamp.valueOf(v.toString()).toInstant(), ZoneId.systemDefault());
+            return ZonedDateTime.ofInstant(java.sql.Timestamp.valueOf(v.toString()).toInstant(),
+                    ZoneId.systemDefault());
         }
-        return ZonedDateTime.ofInstant(((Timestamp) v).toInstant(), ZoneId.systemDefault());
+        return ZonedDateTime.ofInstant(((java.sql.Timestamp) v).toInstant(), ZoneId.systemDefault());
     }
 
     protected Long objectAsLong(Object v) {
index 148ee2414315008fcfedda2ca72d81fdf757d90d..6e18c38b0d0c47711053f629fb0b0ef5576eac4a 100644 (file)
@@ -25,6 +25,7 @@ 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;
+import org.openhab.core.types.State;
 import org.openhab.persistence.jdbc.dto.ItemVO;
 import org.openhab.persistence.jdbc.dto.ItemsVO;
 import org.openhab.persistence.jdbc.dto.JdbcHistoricItem;
@@ -148,13 +149,14 @@ public class JdbcDerbyDAO extends JdbcBaseDAO {
     }
 
     @Override
-    public void doStoreItemValue(Item item, ItemVO vo) {
-        vo = storeItemValueProvider(item, vo);
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo) {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
         String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
                 new String[] { "#tableName#", "#dbType#", "#tablePrimaryValue#" },
-                new String[] { vo.getTableName().toUpperCase(), vo.getDbType(), sqlTypes.get("tablePrimaryValue") });
-        Object[] params = new Object[] { vo.getValue() };
-        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, vo.getValue());
+                new String[] { storedVO.getTableName().toUpperCase(), storedVO.getDbType(),
+                        sqlTypes.get("tablePrimaryValue") });
+        Object[] params = new Object[] { storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
         Yank.execute(sql, params);
     }
 
index 8957f1b455af00b2ca026d51f9c8520ad1da0692..6fa73951fdecd576b940f0535e2cf598cfe781e5 100644 (file)
@@ -14,6 +14,7 @@ package org.openhab.persistence.jdbc.db;
 
 import org.knowm.yank.Yank;
 import org.openhab.core.items.Item;
+import org.openhab.core.types.State;
 import org.openhab.persistence.jdbc.dto.ItemVO;
 import org.openhab.persistence.jdbc.utils.StringUtilsExt;
 import org.slf4j.Logger;
@@ -71,13 +72,13 @@ public class JdbcH2DAO extends JdbcBaseDAO {
      * ITEM DAOs *
      *************/
     @Override
-    public void doStoreItemValue(Item item, ItemVO vo) {
-        vo = storeItemValueProvider(item, vo);
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo) {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
         String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
                 new String[] { "#tableName#", "#dbType#", "#tablePrimaryValue#" },
-                new String[] { vo.getTableName(), vo.getDbType(), sqlTypes.get("tablePrimaryValue") });
-        Object[] params = new Object[] { vo.getValue() };
-        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, vo.getValue());
+                new String[] { storedVO.getTableName(), storedVO.getDbType(), sqlTypes.get("tablePrimaryValue") });
+        Object[] params = new Object[] { storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
         Yank.execute(sql, params);
     }
 
index 5e92d3c7e5c5c0f6aa9f97d30f8f2a45735bd554..dbc9486f68c75654ddf5cce503cf840c9846685c 100644 (file)
@@ -14,6 +14,7 @@ package org.openhab.persistence.jdbc.db;
 
 import org.knowm.yank.Yank;
 import org.openhab.core.items.Item;
+import org.openhab.core.types.State;
 import org.openhab.persistence.jdbc.dto.ItemVO;
 import org.openhab.persistence.jdbc.dto.ItemsVO;
 import org.openhab.persistence.jdbc.utils.StringUtilsExt;
@@ -101,13 +102,14 @@ public class JdbcHsqldbDAO extends JdbcBaseDAO {
      * ITEM DAOs *
      *************/
     @Override
-    public void doStoreItemValue(Item item, ItemVO vo) {
-        vo = storeItemValueProvider(item, vo);
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo) {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
         String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
-                new String[] { "#tableName#", "#dbType#", "#tableName#", "#tablePrimaryValue#" }, new String[] {
-                        vo.getTableName(), vo.getDbType(), vo.getTableName(), sqlTypes.get("tablePrimaryValue") });
-        Object[] params = new Object[] { vo.getValue() };
-        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, vo.getValue());
+                new String[] { "#tableName#", "#dbType#", "#tableName#", "#tablePrimaryValue#" },
+                new String[] { storedVO.getTableName(), storedVO.getDbType(), storedVO.getTableName(),
+                        sqlTypes.get("tablePrimaryValue") });
+        Object[] params = new Object[] { storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
         Yank.execute(sql, params);
     }
 
index d8329578307c0b62d341b2860eac800bc9edaf37..53a1c4ba645115619bb69489411ed3f8814efac1 100644 (file)
@@ -19,6 +19,7 @@ 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.types.State;
 import org.openhab.persistence.jdbc.dto.ItemVO;
 import org.openhab.persistence.jdbc.dto.ItemsVO;
 import org.openhab.persistence.jdbc.utils.StringUtilsExt;
@@ -133,13 +134,13 @@ public class JdbcPostgresqlDAO extends JdbcBaseDAO {
      * ITEM DAOs *
      *************/
     @Override
-    public void doStoreItemValue(Item item, ItemVO vo) {
-        vo = storeItemValueProvider(item, vo);
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo) {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
         String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
                 new String[] { "#tableName#", "#dbType#", "#tablePrimaryValue#" },
-                new String[] { vo.getTableName(), vo.getDbType(), sqlTypes.get("tablePrimaryValue") });
-        Object[] params = new Object[] { vo.getValue() };
-        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, vo.getValue());
+                new String[] { storedVO.getTableName(), storedVO.getDbType(), sqlTypes.get("tablePrimaryValue") });
+        Object[] params = new Object[] { storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
         Yank.execute(sql, params);
     }
 
index 5cf5c0ec212a4d0f36e4b2545153216f52dd1c15..e5665e2c019881f2fe5a1e6304edfeb251ef29e4 100644 (file)
@@ -14,6 +14,7 @@ package org.openhab.persistence.jdbc.db;
 
 import org.knowm.yank.Yank;
 import org.openhab.core.items.Item;
+import org.openhab.core.types.State;
 import org.openhab.persistence.jdbc.dto.ItemVO;
 import org.openhab.persistence.jdbc.dto.ItemsVO;
 import org.openhab.persistence.jdbc.utils.StringUtilsExt;
@@ -90,13 +91,13 @@ public class JdbcSqliteDAO extends JdbcBaseDAO {
      * ITEM DAOs *
      *************/
     @Override
-    public void doStoreItemValue(Item item, ItemVO vo) {
-        vo = storeItemValueProvider(item, vo);
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo) {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
         String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
                 new String[] { "#tableName#", "#dbType#", "#tablePrimaryValue#" },
-                new String[] { vo.getTableName(), vo.getDbType(), sqlTypes.get("tablePrimaryValue") });
-        Object[] params = new Object[] { vo.getValue() };
-        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, vo.getValue());
+                new String[] { storedVO.getTableName(), storedVO.getDbType(), sqlTypes.get("tablePrimaryValue") });
+        Object[] params = new Object[] { storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
         Yank.execute(sql, params);
     }
 
index 9c7f91ca9fdc769700fb20de12ed3ead7ac4c856..af4a03ba390d9959b6d28fb30aeb40e810e5061a 100644 (file)
@@ -16,6 +16,7 @@ import java.io.Serializable;
 import java.util.Date;
 import java.util.Objects;
 
+import org.eclipse.jdt.annotation.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,7 +31,7 @@ public class ItemVO implements Serializable {
     private static final long serialVersionUID = 1871441039821454890L;
 
     private String tableName;
-    private String newTableName;
+    private @Nullable String newTableName;
     private String dbType;
     private String jdbcType;
     private String itemType;
@@ -38,7 +39,7 @@ public class ItemVO implements Serializable {
     private Date time;
     private Object value;
 
-    public ItemVO(String tableName, String newTableName) {
+    public ItemVO(String tableName, @Nullable String newTableName) {
         logger.debug("JDBC:ItemVO tableName={}; newTableName={}; ", tableName, newTableName);
         this.tableName = tableName;
         this.newTableName = newTableName;
@@ -61,7 +62,7 @@ public class ItemVO implements Serializable {
         this.tableName = tableName;
     }
 
-    public String getNewTableName() {
+    public @Nullable String getNewTableName() {
         return newTableName;
     }
 
@@ -117,11 +118,6 @@ public class ItemVO implements Serializable {
         this.value = value;
     }
 
-    /**
-     * (non-Javadoc)
-     *
-     * @see java.lang.Object#equals(java.lang.Object)
-     */
     @Override
     public boolean equals(Object obj) {
         if (this == obj) {
@@ -146,20 +142,8 @@ public class ItemVO implements Serializable {
 
     @Override
     public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append("ItemVO [tableName=");
-        builder.append(tableName);
-        builder.append(", newTableName=");
-        builder.append(newTableName);
-        builder.append(", dbType=");
-        builder.append(dbType);
-        builder.append(", javaType=");
-        builder.append(javaType);
-        builder.append(", time=");
-        builder.append(time);
-        builder.append(", value=");
-        builder.append(value);
-        builder.append("]");
-        return builder.toString();
+        return new StringBuilder("ItemVO [tableName=").append(tableName).append(", newTableName=").append(newTableName)
+                .append(", dbType=").append(dbType).append(", javaType=").append(javaType).append(", time=")
+                .append(time).append(", value=").append(value).append("]").toString();
     }
 }
index 1bf53f1fbc5cab855d273a70d1be18f844c341f9..9476ba5427aaa97178c56fde70e72884a22ab9c5 100644 (file)
@@ -12,6 +12,7 @@
  */
 package org.openhab.persistence.jdbc.internal;
 
+import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -19,12 +20,14 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import org.eclipse.jdt.annotation.Nullable;
 import org.knowm.yank.Yank;
 import org.openhab.core.i18n.TimeZoneProvider;
 import org.openhab.core.items.Item;
 import org.openhab.core.persistence.FilterCriteria;
 import org.openhab.core.persistence.HistoricItem;
 import org.openhab.core.persistence.PersistenceItemInfo;
+import org.openhab.core.types.State;
 import org.openhab.persistence.jdbc.dto.ItemVO;
 import org.openhab.persistence.jdbc.dto.ItemsVO;
 import org.openhab.persistence.jdbc.dto.JdbcPersistenceItemInfo;
@@ -145,15 +148,19 @@ public class JdbcMapper {
         return vo;
     }
 
-    public Item storeItemValue(Item item) {
-        logger.debug("JDBC::storeItemValue: item={}", item);
+    public Item storeItemValue(Item item, State itemState, @Nullable ZonedDateTime date) {
+        logger.debug("JDBC::storeItemValue: item={} state={} date={}", item, itemState, date);
         String tableName = getTable(item);
         if (tableName == null) {
             logger.error("JDBC::store: Unable to store item '{}'.", item.getName());
             return item;
         }
         long timerStart = System.currentTimeMillis();
-        conf.getDBDAO().doStoreItemValue(item, new ItemVO(tableName, null));
+        if (date == null) {
+            conf.getDBDAO().doStoreItemValue(item, itemState, new ItemVO(tableName, null));
+        } else {
+            conf.getDBDAO().doStoreItemValue(item, itemState, new ItemVO(tableName, null), date);
+        }
         logTime("storeItemValue", timerStart, System.currentTimeMillis());
         errCnt = 0;
         return item;
@@ -177,6 +184,21 @@ public class JdbcMapper {
         return null;
     }
 
+    public boolean deleteItemValues(FilterCriteria filter, String table, Item item) {
+        logger.debug("JDBC::deleteItemValues filter='{}' table='{}' item='{}' itemName='{}'", (filter != null), table,
+                item, item.getName());
+        if (table != null) {
+            long timerStart = System.currentTimeMillis();
+            conf.getDBDAO().doDeleteItemValues(item, filter, table, timeZoneProvider.getTimeZone());
+            logTime("deleteItemValues", timerStart, System.currentTimeMillis());
+            errCnt = 0;
+            return true;
+        } else {
+            logger.error("JDBC::deleteItemValues: TABLE is NULL; cannot delete data from non-existent table.");
+            return false;
+        }
+    }
+
     /***********************
      * DATABASE CONNECTION *
      ***********************/
index 4feb40b6fc5b81050fb66021808c99fcbea0b95c..c00e178a8ebb2259b28b655c762d90651aa3f5ad 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.persistence.jdbc.internal;
 
+import java.time.ZonedDateTime;
+import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -27,10 +29,12 @@ import org.openhab.core.items.ItemNotFoundException;
 import org.openhab.core.items.ItemRegistry;
 import org.openhab.core.persistence.FilterCriteria;
 import org.openhab.core.persistence.HistoricItem;
+import org.openhab.core.persistence.ModifiablePersistenceService;
 import org.openhab.core.persistence.PersistenceItemInfo;
 import org.openhab.core.persistence.PersistenceService;
 import org.openhab.core.persistence.QueryablePersistenceService;
 import org.openhab.core.persistence.strategy.PersistenceStrategy;
+import org.openhab.core.types.State;
 import org.openhab.core.types.UnDefType;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
@@ -52,8 +56,10 @@ import org.slf4j.LoggerFactory;
         QueryablePersistenceService.class }, configurationPid = "org.openhab.jdbc", //
         property = Constants.SERVICE_PID + "=org.openhab.jdbc")
 @ConfigurableService(category = "persistence", label = "JDBC Persistence Service", description_uri = JdbcPersistenceService.CONFIG_URI)
-public class JdbcPersistenceService extends JdbcMapper implements QueryablePersistenceService {
+public class JdbcPersistenceService extends JdbcMapper implements ModifiablePersistenceService {
 
+    private static final String SERVICE_ID = "jdbc";
+    private static final String SERVICE_LABEL = "JDBC";
     protected static final String CONFIG_URI = "persistence:jdbc";
 
     private final Logger logger = LoggerFactory.getLogger(JdbcPersistenceService.class);
@@ -110,39 +116,48 @@ public class JdbcPersistenceService extends JdbcMapper implements QueryablePersi
     @Override
     public String getId() {
         logger.debug("JDBC::getName: returning name 'jdbc' for queryable persistence service.");
-        return "jdbc";
+        return SERVICE_ID;
     }
 
     @Override
     public String getLabel(@Nullable Locale locale) {
-        return "JDBC";
+        return SERVICE_LABEL;
     }
 
     @Override
     public void store(Item item) {
-        store(item, null);
+        internalStore(item, null, item.getState());
     }
 
-    /**
-     * @{inheritDoc
-     */
     @Override
     public void store(Item item, @Nullable String alias) {
+        // alias is not supported
+        internalStore(item, null, item.getState());
+    }
+
+    @Override
+    public void store(Item item, ZonedDateTime date, State state) {
+        internalStore(item, date, state);
+    }
+
+    private void internalStore(Item item, @Nullable ZonedDateTime date, State state) {
         // Do not store undefined/uninitialized data
-        if (item.getState() instanceof UnDefType) {
+        if (state instanceof UnDefType) {
             logger.debug("JDBC::store: ignore Item '{}' because it is UnDefType", item.getName());
             return;
         }
         if (!checkDBAccessability()) {
             logger.warn(
-                    "JDBC::store:  No connection to database. Cannot persist item '{}'! Will retry connecting to database when error count:{} equals errReconnectThreshold:{}",
-                    item, errCnt, conf.getErrReconnectThreshold());
+                    "JDBC::store: No connection to database. Cannot persist state '{}' for item '{}'! Will retry connecting to database when error count:{} equals errReconnectThreshold:{}",
+                    state, item, errCnt, conf.getErrReconnectThreshold());
             return;
         }
         long timerStart = System.currentTimeMillis();
-        storeItemValue(item);
-        logger.debug("JDBC: Stored item '{}' as '{}' in SQL database at {} in {} ms.", item.getName(), item.getState(),
-                new java.util.Date(), System.currentTimeMillis() - timerStart);
+        storeItemValue(item, state, date);
+        if (logger.isDebugEnabled()) {
+            logger.debug("JDBC: Stored item '{}' as '{}' in SQL database at {} in {} ms.", item.getName(), state,
+                    new Date(), System.currentTimeMillis() - timerStart);
+        }
     }
 
     @Override
@@ -228,4 +243,42 @@ public class JdbcPersistenceService extends JdbcMapper implements QueryablePersi
     public List<PersistenceStrategy> getDefaultStrategies() {
         return List.of(PersistenceStrategy.Globals.CHANGE);
     }
+
+    @Override
+    public boolean remove(FilterCriteria filter) throws IllegalArgumentException {
+        if (!checkDBAccessability()) {
+            logger.warn("JDBC::remove: database not connected, remove aborted for item '{}'", filter.getItemName());
+            return false;
+        }
+
+        // Get the item name from the filter
+        // Also get the Item object so we can determine the type
+        Item item = null;
+        String itemName = filter.getItemName();
+        logger.debug("JDBC::remove: item is {}", itemName);
+        if (itemName == null) {
+            throw new IllegalArgumentException("Item name must not be null");
+        }
+        try {
+            item = itemRegistry.getItem(itemName);
+        } catch (ItemNotFoundException e) {
+            logger.error("JDBC::remove: unable to get item for itemName: '{}'. Ignore and give up!", itemName);
+            return false;
+        }
+
+        String table = sqlTables.get(itemName);
+        if (table == null) {
+            logger.debug("JDBC::remove: unable to find table for item with name: '{}', no data in database.", itemName);
+            return false;
+        }
+
+        long timerStart = System.currentTimeMillis();
+        boolean result = deleteItemValues(filter, table, item);
+        if (logger.isDebugEnabled()) {
+            logger.debug("JDBC: Deleted values for item '{}' in SQL database at {} in {} ms.", item.getName(),
+                    new Date(), System.currentTimeMillis() - timerStart);
+        }
+
+        return result;
+    }
 }
diff --git a/bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/db/JdbcBaseDAOTest.java b/bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/db/JdbcBaseDAOTest.java
new file mode 100644 (file)
index 0000000..1b58e62
--- /dev/null
@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.persistence.jdbc.db;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.openhab.core.persistence.FilterCriteria;
+import org.openhab.core.persistence.FilterCriteria.Ordering;
+
+/**
+ * Tests the {@link JdbcBaseDAO}.
+ *
+ * @author Christoph Weitkamp - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcBaseDAOTest {
+
+    private static final String DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";
+    private static final DateTimeFormatter DATE_PARSER = DateTimeFormatter.ofPattern(DATE_PATTERN);
+    private static final ZoneId UTC_ZONE_ID = ZoneId.of("UTC");
+    private static final String DB_TABLE_NAME = "testitem";
+
+    private final JdbcBaseDAO jdbcBaseDAO = new JdbcBaseDAO();
+    private @NonNullByDefault({}) FilterCriteria filter;
+
+    @BeforeEach
+    public void setup() {
+        filter = new FilterCriteria();
+    }
+
+    @Test
+    public void testHistItemFilterQueryProviderReturnsSelectQueryWithoutWhereClauseDescendingOrder() {
+        String sql = jdbcBaseDAO.histItemFilterQueryProvider(filter, 0, DB_TABLE_NAME, "TEST", UTC_ZONE_ID);
+        assertThat(sql, is("SELECT time, value FROM " + DB_TABLE_NAME + " ORDER BY time DESC"));
+    }
+
+    @Test
+    public void testHistItemFilterQueryProviderReturnsSelectQueryWithoutWhereClauseAscendingOrder() {
+        filter.setOrdering(Ordering.ASCENDING);
+
+        String sql = jdbcBaseDAO.histItemFilterQueryProvider(filter, 0, DB_TABLE_NAME, "TEST", UTC_ZONE_ID);
+        assertThat(sql, is("SELECT time, value FROM " + DB_TABLE_NAME + " ORDER BY time ASC"));
+    }
+
+    @Test
+    public void testHistItemFilterQueryProviderWithStartAndEndDateReturnsDeleteQueryWithWhereClauseDescendingOrder() {
+        filter.setBeginDate(parseDateTimeString("2022-01-10T15:01:44"));
+        filter.setEndDate(parseDateTimeString("2022-01-15T15:01:44"));
+
+        String sql = jdbcBaseDAO.histItemFilterQueryProvider(filter, 0, DB_TABLE_NAME, "TEST", UTC_ZONE_ID);
+        assertThat(sql, is("SELECT time, value FROM " + DB_TABLE_NAME + " WHERE TIME>'" //
+                + JdbcBaseDAO.JDBC_DATE_FORMAT.format(filter.getBeginDate()) + "'" //
+                + " AND TIME<'" + JdbcBaseDAO.JDBC_DATE_FORMAT.format(filter.getEndDate()) + "' ORDER BY time DESC"));
+    }
+
+    @Test
+    public void testHistItemFilterQueryProviderReturnsSelectQueryWithoutWhereClauseDescendingOrderAndLimit() {
+        filter.setPageSize(1);
+
+        String sql = jdbcBaseDAO.histItemFilterQueryProvider(filter, 0, DB_TABLE_NAME, "TEST", UTC_ZONE_ID);
+        assertThat(sql, is("SELECT time, value FROM " + DB_TABLE_NAME + " ORDER BY time DESC LIMIT 0,1"));
+    }
+
+    @Test
+    public void testHistItemFilterDeleteProviderReturnsDeleteQueryWithoutWhereClause() {
+        String sql = jdbcBaseDAO.histItemFilterDeleteProvider(filter, DB_TABLE_NAME, UTC_ZONE_ID);
+        assertThat(sql, is("DELETE FROM " + DB_TABLE_NAME));
+    }
+
+    @Test
+    public void testHistItemFilterDeleteProviderWithStartAndEndDateReturnsDeleteQueryWithWhereClause() {
+        filter.setBeginDate(parseDateTimeString("2022-01-10T15:01:44"));
+        filter.setEndDate(parseDateTimeString("2022-01-15T15:01:44"));
+
+        String sql = jdbcBaseDAO.histItemFilterDeleteProvider(filter, DB_TABLE_NAME, UTC_ZONE_ID);
+        assertThat(sql, is("DELETE FROM " + DB_TABLE_NAME + " WHERE TIME>'" //
+                + JdbcBaseDAO.JDBC_DATE_FORMAT.format(filter.getBeginDate()) + "'" //
+                + " AND TIME<'" + JdbcBaseDAO.JDBC_DATE_FORMAT.format(filter.getEndDate()) + "'"));
+    }
+
+    @Test
+    public void testResolveTimeFilterWithNoDatesReturnsEmptyString() {
+        String sql = jdbcBaseDAO.resolveTimeFilter(filter, UTC_ZONE_ID);
+        assertThat(sql, is(""));
+    }
+
+    @Test
+    public void testResolveTimeFilterWithStartDateOnlyReturnsWhereClause() {
+        filter.setBeginDate(parseDateTimeString("2022-01-10T15:01:44"));
+
+        String sql = jdbcBaseDAO.resolveTimeFilter(filter, UTC_ZONE_ID);
+        assertThat(sql, is(" WHERE TIME>'" + JdbcBaseDAO.JDBC_DATE_FORMAT.format(filter.getBeginDate()) + "'"));
+    }
+
+    @Test
+    public void testResolveTimeFilterWithEndDateOnlyReturnsWhereClause() {
+        filter.setEndDate(parseDateTimeString("2022-01-15T15:01:44"));
+
+        String sql = jdbcBaseDAO.resolveTimeFilter(filter, UTC_ZONE_ID);
+        assertThat(sql, is(" WHERE TIME<'" + JdbcBaseDAO.JDBC_DATE_FORMAT.format(filter.getEndDate()) + "'"));
+    }
+
+    @Test
+    public void testResolveTimeFilterWithStartAndEndDateReturnsWhereClauseWithTwoConditions() {
+        filter.setBeginDate(parseDateTimeString("2022-01-10T15:01:44"));
+        filter.setEndDate(parseDateTimeString("2022-01-15T15:01:44"));
+
+        String sql = jdbcBaseDAO.resolveTimeFilter(filter, UTC_ZONE_ID);
+        assertThat(sql, is(" WHERE TIME>'" + JdbcBaseDAO.JDBC_DATE_FORMAT.format(filter.getBeginDate()) + "'" //
+                + " AND TIME<'" + JdbcBaseDAO.JDBC_DATE_FORMAT.format(filter.getEndDate()) + "'"));
+    }
+
+    private ZonedDateTime parseDateTimeString(String dts) {
+        return ZonedDateTime.of(LocalDateTime.parse(dts, DATE_PARSER), UTC_ZONE_ID);
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceServiceTest.java b/bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceServiceTest.java
new file mode 100644 (file)
index 0000000..27f4941
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.persistence.jdbc.internal;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.openhab.core.i18n.TimeZoneProvider;
+import org.openhab.core.items.ItemRegistry;
+import org.openhab.core.persistence.FilterCriteria;
+
+/**
+ * Tests the {@link JdbcPersistenceService}.
+ *
+ * @author Christoph Weitkamp - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcPersistenceServiceTest {
+
+    private final JdbcPersistenceService jdbcPersistenceService = new JdbcPersistenceService(mock(ItemRegistry.class),
+            mock(TimeZoneProvider.class)) {
+        @Override
+        protected boolean checkDBAccessability() {
+            return true;
+        }
+    };
+    private @NonNullByDefault({}) FilterCriteria filter;
+
+    @BeforeEach
+    public void setup() {
+        filter = new FilterCriteria();
+    }
+
+    @Test
+    void removeThrowsIllegalArgumentExceptionIfItemNameOfFilterIsNull() {
+        assertThrows(IllegalArgumentException.class, () -> jdbcPersistenceService.remove(filter));
+    }
+}