]> git.basschouten.com Git - openhab-addons.git/commitdiff
[jdbc] Improve error handling safety (#13726)
authorJacob Laursen <jacob-github@vindvejr.dk>
Wed, 16 Nov 2022 08:28:46 +0000 (09:28 +0100)
committerGitHub <noreply@github.com>
Wed, 16 Nov 2022 08:28:46 +0000 (09:28 +0100)
* Wrap YankSQLException into checked exception for all Yank calls

* Move files into internal

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
47 files changed:
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/ItemTableCheckEntry.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/ItemTableCheckEntryStatus.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/console/JdbcCommandExtension.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcBaseDAO.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcDerbyDAO.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcH2DAO.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcHsqldbDAO.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcMariadbDAO.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcMysqlDAO.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcPostgresqlDAO.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcSqliteDAO.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcTimescaledbDAO.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/ItemVO.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/ItemsVO.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/JdbcHistoricItem.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/JdbcPersistenceItemInfo.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/ItemTableCheckEntry.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/ItemTableCheckEntryStatus.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcConfiguration.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/main/java/org/openhab/persistence/jdbc/internal/NamingStrategy.java
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/console/JdbcCommandExtension.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcBaseDAO.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcDerbyDAO.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcH2DAO.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcHsqldbDAO.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcMariadbDAO.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcMysqlDAO.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcPostgresqlDAO.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcSqliteDAO.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcTimescaledbDAO.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/ItemVO.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/ItemsVO.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/JdbcHistoricItem.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/JdbcPersistenceItemInfo.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/exceptions/JdbcException.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/exceptions/JdbcSQLException.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/utils/DbMetaData.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/utils/MovingAverage.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/utils/StringUtilsExt.java [new file with mode: 0644]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/DbMetaData.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/MovingAverage.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/StringUtilsExt.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/db/JdbcBaseDAOTest.java [deleted file]
bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/internal/NamingStrategyTest.java
bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/internal/db/JdbcBaseDAOTest.java [new file with mode: 0644]

diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/ItemTableCheckEntry.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/ItemTableCheckEntry.java
deleted file mode 100644 (file)
index e644138..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * 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;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-/**
- * This class represents a checked item/table relation.
- *
- * @author Jacob Laursen - Initial contribution
- */
-@NonNullByDefault
-public class ItemTableCheckEntry {
-    private String itemName;
-    private String tableName;
-    private ItemTableCheckEntryStatus status;
-
-    public ItemTableCheckEntry(String itemName, String tableName, ItemTableCheckEntryStatus status) {
-        this.itemName = itemName;
-        this.tableName = tableName;
-        this.status = status;
-    }
-
-    public String getItemName() {
-        return itemName;
-    }
-
-    public String getTableName() {
-        return tableName;
-    }
-
-    public ItemTableCheckEntryStatus getStatus() {
-        return status;
-    }
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/ItemTableCheckEntryStatus.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/ItemTableCheckEntryStatus.java
deleted file mode 100644 (file)
index 3553aee..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- * 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;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-/**
- * This class represents status for an {@link ItemTableCheckEntry}.
- *
- * @author Jacob Laursen - Initial contribution
- */
-@NonNullByDefault
-public enum ItemTableCheckEntryStatus {
-    /**
-     * Table is consistent.
-     */
-    VALID {
-        @Override
-        public String toString() {
-            return "Valid";
-        }
-    },
-    /**
-     * Table has no corresponding item.
-     */
-    ITEM_MISSING {
-        @Override
-        public String toString() {
-            return "Item missing";
-        }
-    },
-    /**
-     * Referenced table does not exist.
-     */
-    TABLE_MISSING {
-        @Override
-        public String toString() {
-            return "Table missing";
-        }
-    },
-    /**
-     * Referenced table does not exist nor has corresponding item.
-     */
-    ITEM_AND_TABLE_MISSING {
-        @Override
-        public String toString() {
-            return "Item and table missing";
-        }
-    },
-    /**
-     * Mapping for table does not exist in index.
-     */
-    ORPHAN_TABLE {
-        @Override
-        public String toString() {
-            return "Orphan table";
-        }
-    }
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/console/JdbcCommandExtension.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/console/JdbcCommandExtension.java
deleted file mode 100644 (file)
index 6923430..0000000
+++ /dev/null
@@ -1,204 +0,0 @@
-/**
- * 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.console;
-
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-import java.util.stream.Stream;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.knowm.yank.exceptions.YankSQLException;
-import org.openhab.core.io.console.Console;
-import org.openhab.core.io.console.ConsoleCommandCompleter;
-import org.openhab.core.io.console.StringsCompleter;
-import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
-import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
-import org.openhab.core.persistence.PersistenceService;
-import org.openhab.core.persistence.PersistenceServiceRegistry;
-import org.openhab.persistence.jdbc.ItemTableCheckEntry;
-import org.openhab.persistence.jdbc.ItemTableCheckEntryStatus;
-import org.openhab.persistence.jdbc.internal.JdbcPersistenceService;
-import org.openhab.persistence.jdbc.internal.JdbcPersistenceServiceConstants;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-
-/**
- * The {@link JdbcCommandExtension} is responsible for handling console commands
- *
- * @author Jacob Laursen - Initial contribution
- */
-@NonNullByDefault
-@Component(service = ConsoleCommandExtension.class)
-public class JdbcCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
-
-    private static final String CMD_TABLES = "tables";
-    private static final String SUBCMD_TABLES_LIST = "list";
-    private static final String SUBCMD_TABLES_CLEAN = "clean";
-    private static final String PARAMETER_ALL = "all";
-    private static final String PARAMETER_FORCE = "force";
-    private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(List.of(CMD_TABLES), false);
-    private static final StringsCompleter SUBCMD_TABLES_COMPLETER = new StringsCompleter(
-            List.of(SUBCMD_TABLES_LIST, SUBCMD_TABLES_CLEAN), false);
-
-    private final PersistenceServiceRegistry persistenceServiceRegistry;
-
-    @Activate
-    public JdbcCommandExtension(final @Reference PersistenceServiceRegistry persistenceServiceRegistry) {
-        super(JdbcPersistenceServiceConstants.SERVICE_ID, "Interact with the JDBC persistence service.");
-        this.persistenceServiceRegistry = persistenceServiceRegistry;
-    }
-
-    @Override
-    public void execute(String[] args, Console console) {
-        if (args.length < 2 || args.length > 4 || !CMD_TABLES.equals(args[0])) {
-            printUsage(console);
-            return;
-        }
-        JdbcPersistenceService persistenceService = getPersistenceService();
-        if (persistenceService == null) {
-            return;
-        }
-        try {
-            if (!execute(persistenceService, args, console)) {
-                printUsage(console);
-                return;
-            }
-        } catch (YankSQLException e) {
-            console.println(e.toString());
-        }
-    }
-
-    private @Nullable JdbcPersistenceService getPersistenceService() {
-        for (PersistenceService persistenceService : persistenceServiceRegistry.getAll()) {
-            if (persistenceService instanceof JdbcPersistenceService) {
-                return (JdbcPersistenceService) persistenceService;
-            }
-        }
-        return null;
-    }
-
-    private boolean execute(JdbcPersistenceService persistenceService, String[] args, Console console) {
-        if (SUBCMD_TABLES_LIST.equalsIgnoreCase(args[1])) {
-            listTables(persistenceService, console, args.length == 3 && PARAMETER_ALL.equalsIgnoreCase(args[2]));
-            return true;
-        } else if (SUBCMD_TABLES_CLEAN.equalsIgnoreCase(args[1])) {
-            if (args.length == 3) {
-                cleanupItem(persistenceService, console, args[2], false);
-                return true;
-            } else if (args.length == 4 && PARAMETER_FORCE.equalsIgnoreCase(args[3])) {
-                cleanupItem(persistenceService, console, args[2], true);
-                return true;
-            } else {
-                cleanupTables(persistenceService, console);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void listTables(JdbcPersistenceService persistenceService, Console console, Boolean all) {
-        List<ItemTableCheckEntry> entries = persistenceService.getCheckedEntries();
-        if (!all) {
-            entries.removeIf(t -> t.getStatus() == ItemTableCheckEntryStatus.VALID);
-        }
-        entries.sort(Comparator.comparing(ItemTableCheckEntry::getTableName));
-        // FIXME: NoSuchElement when empty table - because of get()
-        int itemNameMaxLength = Math
-                .max(entries.stream().map(t -> t.getItemName().length()).max(Integer::compare).get(), 4);
-        int tableNameMaxLength = Math
-                .max(entries.stream().map(t -> t.getTableName().length()).max(Integer::compare).get(), 5);
-        int statusMaxLength = Stream.of(ItemTableCheckEntryStatus.values()).map(t -> t.toString().length())
-                .max(Integer::compare).get();
-        console.println(String.format(
-                "%1$-" + (tableNameMaxLength + 2) + "sRow Count  %2$-" + (itemNameMaxLength + 2) + "s%3$s", "Table",
-                "Item", "Status"));
-        console.println("-".repeat(tableNameMaxLength) + "  " + "---------  " + "-".repeat(itemNameMaxLength) + "  "
-                + "-".repeat(statusMaxLength));
-        for (ItemTableCheckEntry entry : entries) {
-            String tableName = entry.getTableName();
-            ItemTableCheckEntryStatus status = entry.getStatus();
-            long rowCount = status == ItemTableCheckEntryStatus.VALID
-                    || status == ItemTableCheckEntryStatus.ITEM_MISSING ? persistenceService.getRowCount(tableName) : 0;
-            console.println(String.format(
-                    "%1$-" + (tableNameMaxLength + 2) + "s%2$9d  %3$-" + (itemNameMaxLength + 2) + "s%4$s", tableName,
-                    rowCount, entry.getItemName(), status));
-        }
-    }
-
-    private void cleanupTables(JdbcPersistenceService persistenceService, Console console) {
-        console.println("Cleaning up all inconsistent items...");
-        List<ItemTableCheckEntry> entries = persistenceService.getCheckedEntries();
-        entries.removeIf(t -> t.getStatus() == ItemTableCheckEntryStatus.VALID || t.getItemName().isEmpty());
-        for (ItemTableCheckEntry entry : entries) {
-            console.print(entry.getItemName() + " -> ");
-            if (persistenceService.cleanupItem(entry)) {
-                console.println("done.");
-            } else {
-                console.println("skipped/failed.");
-            }
-        }
-    }
-
-    private void cleanupItem(JdbcPersistenceService persistenceService, Console console, String itemName,
-            boolean force) {
-        console.print("Cleaning up item " + itemName + "... ");
-        if (persistenceService.cleanupItem(itemName, force)) {
-            console.println("done.");
-        } else {
-            console.println("skipped/failed.");
-        }
-    }
-
-    @Override
-    public List<String> getUsages() {
-        return Arrays.asList(
-                buildCommandUsage(CMD_TABLES + " " + SUBCMD_TABLES_LIST + " [" + PARAMETER_ALL + "]",
-                        "list tables (all = include valid)"),
-                buildCommandUsage(
-                        CMD_TABLES + " " + SUBCMD_TABLES_CLEAN + " [<itemName>]" + " [" + PARAMETER_FORCE + "]",
-                        "clean inconsistent items (remove from index and drop tables)"));
-    }
-
-    @Override
-    public @Nullable ConsoleCommandCompleter getCompleter() {
-        return this;
-    }
-
-    @Override
-    public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
-        if (cursorArgumentIndex <= 0) {
-            return CMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
-        } else if (cursorArgumentIndex == 1) {
-            if (CMD_TABLES.equalsIgnoreCase(args[0])) {
-                return SUBCMD_TABLES_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
-            }
-        } else if (cursorArgumentIndex == 2) {
-            if (CMD_TABLES.equalsIgnoreCase(args[0])) {
-                if (SUBCMD_TABLES_CLEAN.equalsIgnoreCase(args[1])) {
-                    JdbcPersistenceService persistenceService = getPersistenceService();
-                    if (persistenceService != null) {
-                        return new StringsCompleter(persistenceService.getItemNames(), true).complete(args,
-                                cursorArgumentIndex, cursorPosition, candidates);
-                    }
-                } else if (SUBCMD_TABLES_LIST.equalsIgnoreCase(args[1])) {
-                    new StringsCompleter(List.of(PARAMETER_ALL), false).complete(args, cursorArgumentIndex,
-                            cursorPosition, candidates);
-                }
-            }
-        }
-        return false;
-    }
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcBaseDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcBaseDAO.java
deleted file mode 100644 (file)
index 393635d..0000000
+++ /dev/null
@@ -1,679 +0,0 @@
-/**
- * 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 java.math.BigDecimal;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Properties;
-import java.util.stream.Collectors;
-
-import javax.measure.Quantity;
-import javax.measure.Unit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.knowm.yank.Yank;
-import org.openhab.core.items.GroupItem;
-import org.openhab.core.items.Item;
-import org.openhab.core.library.items.ColorItem;
-import org.openhab.core.library.items.ContactItem;
-import org.openhab.core.library.items.DateTimeItem;
-import org.openhab.core.library.items.DimmerItem;
-import org.openhab.core.library.items.ImageItem;
-import org.openhab.core.library.items.NumberItem;
-import org.openhab.core.library.items.PlayerItem;
-import org.openhab.core.library.items.RollershutterItem;
-import org.openhab.core.library.items.SwitchItem;
-import org.openhab.core.library.types.DateTimeType;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.library.types.HSBType;
-import org.openhab.core.library.types.PercentType;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.types.RawType;
-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;
-import org.openhab.core.types.State;
-import org.openhab.core.types.TypeParser;
-import org.openhab.persistence.jdbc.dto.ItemVO;
-import org.openhab.persistence.jdbc.dto.ItemsVO;
-import org.openhab.persistence.jdbc.dto.JdbcHistoricItem;
-import org.openhab.persistence.jdbc.utils.DbMetaData;
-import org.openhab.persistence.jdbc.utils.StringUtilsExt;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Default Database Configuration class.
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-@NonNullByDefault
-public class JdbcBaseDAO {
-    private final Logger logger = LoggerFactory.getLogger(JdbcBaseDAO.class);
-
-    public final Properties databaseProps = new Properties();
-    protected String urlSuffix = "";
-    public final Map<String, String> sqlTypes = new HashMap<>();
-
-    // Get Database Meta data
-    protected @Nullable DbMetaData dbMeta;
-
-    protected String sqlPingDB = "SELECT 1";
-    protected String sqlGetDB = "SELECT DATABASE()";
-    protected String sqlIfTableExists = "SHOW TABLES LIKE '#searchTable#'";
-    protected String sqlCreateNewEntryInItemsTable = "INSERT INTO #itemsManageTable# (ItemName) VALUES ('#itemname#')";
-    protected String sqlCreateItemsTableIfNot = "CREATE TABLE IF NOT EXISTS #itemsManageTable# (ItemId INT NOT NULL AUTO_INCREMENT,#colname# #coltype# NOT NULL,PRIMARY KEY (ItemId))";
-    protected String sqlDropItemsTableIfExists = "DROP TABLE IF EXISTS #itemsManageTable#";
-    protected String sqlDropTable = "DROP TABLE #tableName#";
-    protected String sqlDeleteItemsEntry = "DELETE FROM #itemsManageTable# WHERE ItemName='#itemname#'";
-    protected String sqlGetItemIDTableNames = "SELECT ItemId, ItemName FROM #itemsManageTable#";
-    protected String sqlGetItemTables = "SELECT table_name FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_schema='#jdbcUriDatabaseName#' AND NOT table_name='#itemsManageTable#'";
-    protected String sqlCreateItemTable = "CREATE TABLE IF NOT EXISTS #tableName# (time #tablePrimaryKey# NOT NULL, value #dbType#, PRIMARY KEY(time))";
-    protected String sqlInsertItemValue = "INSERT INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, ? ) ON DUPLICATE KEY UPDATE VALUE= ?";
-    protected String sqlGetRowCount = "SELECT COUNT(*) FROM #tableName#";
-
-    /********
-     * INIT *
-     ********/
-    public JdbcBaseDAO() {
-        initSqlTypes();
-        initDbProps();
-    }
-
-    /**
-     * ## Get high precision by fractal seconds, examples ##
-     *
-     * mysql > 5.5 + mariadb > 5.2:
-     * DROP TABLE FractionalSeconds;
-     * CREATE TABLE FractionalSeconds (time TIMESTAMP(3), value TIMESTAMP(3));
-     * INSERT INTO FractionalSeconds (time, value) VALUES( NOW(3), '1999-01-09 20:11:11.126' );
-     * SELECT time FROM FractionalSeconds ORDER BY time DESC LIMIT 1;
-     *
-     * mysql <= 5.5 + mariadb <= 5.2: !!! NO high precision and fractal seconds !!!
-     * DROP TABLE FractionalSeconds;
-     * CREATE TABLE FractionalSeconds (time TIMESTAMP, value TIMESTAMP);
-     * INSERT INTO FractionalSeconds (time, value) VALUES( NOW(), '1999-01-09 20:11:11.126' );
-     * SELECT time FROM FractionalSeconds ORDER BY time DESC LIMIT 1;
-     *
-     * derby:
-     * DROP TABLE FractionalSeconds;
-     * CREATE TABLE FractionalSeconds (time TIMESTAMP, value TIMESTAMP);
-     * INSERT INTO FractionalSeconds (time, value) VALUES( CURRENT_TIMESTAMP, '1999-01-09 20:11:11.126' );
-     * SELECT time, value FROM FractionalSeconds;
-     *
-     * H2 + postgreSQL + hsqldb:
-     * DROP TABLE FractionalSeconds;
-     * CREATE TABLE FractionalSeconds (time TIMESTAMP, value TIMESTAMP);
-     * INSERT INTO FractionalSeconds (time, value) VALUES( NOW(), '1999-01-09 20:11:11.126' );
-     * SELECT time, value FROM FractionalSeconds;
-     *
-     * Sqlite:
-     * DROP TABLE FractionalSeconds;
-     * CREATE TABLE FractionalSeconds (time TIMESTAMP, value TIMESTAMP);
-     * INSERT INTO FractionalSeconds (time, value) VALUES( strftime('%Y-%m-%d %H:%M:%f' , 'now' , 'localtime'),
-     * '1999-01-09 20:11:11.124' );
-     * SELECT time FROM FractionalSeconds ORDER BY time DESC LIMIT 1;
-     *
-     */
-
-    /**
-     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
-     */
-    private void initSqlTypes() {
-        logger.debug("JDBC::initSqlTypes: Initialize the type array");
-        sqlTypes.put("CALLITEM", "VARCHAR(200)");
-        sqlTypes.put("COLORITEM", "VARCHAR(70)");
-        sqlTypes.put("CONTACTITEM", "VARCHAR(6)");
-        sqlTypes.put("DATETIMEITEM", "TIMESTAMP");
-        sqlTypes.put("DIMMERITEM", "TINYINT");
-        sqlTypes.put("IMAGEITEM", "VARCHAR(65500)");// jdbc max 21845
-        sqlTypes.put("LOCATIONITEM", "VARCHAR(50)");
-        sqlTypes.put("NUMBERITEM", "DOUBLE");
-        sqlTypes.put("PLAYERITEM", "VARCHAR(20)");
-        sqlTypes.put("ROLLERSHUTTERITEM", "TINYINT");
-        sqlTypes.put("STRINGITEM", "VARCHAR(65500)");// jdbc max 21845
-        sqlTypes.put("SWITCHITEM", "VARCHAR(6)");
-        sqlTypes.put("tablePrimaryKey", "TIMESTAMP");
-        sqlTypes.put("tablePrimaryValue", "NOW()");
-    }
-
-    /**
-     * INFO: https://github.com/brettwooldridge/HikariCP
-     *
-     * driverClassName (used with jdbcUrl):
-     * Derby: org.apache.derby.jdbc.EmbeddedDriver
-     * H2: org.h2.Driver
-     * HSQLDB: org.hsqldb.jdbcDriver
-     * Jaybird: org.firebirdsql.jdbc.FBDriver
-     * MariaDB: org.mariadb.jdbc.Driver
-     * MySQL: com.mysql.cj.jdbc.Driver
-     * MaxDB: com.sap.dbtech.jdbc.DriverSapDB
-     * PostgreSQL: org.postgresql.Driver
-     * SyBase: com.sybase.jdbc3.jdbc.SybDriver
-     * SqLite: org.sqlite.JDBC
-     *
-     * dataSourceClassName (for alternative Configuration):
-     * Derby: org.apache.derby.jdbc.ClientDataSource
-     * H2: org.h2.jdbcx.JdbcDataSource
-     * HSQLDB: org.hsqldb.jdbc.JDBCDataSource
-     * Jaybird: org.firebirdsql.pool.FBSimpleDataSource
-     * MariaDB, MySQL: org.mariadb.jdbc.MySQLDataSource
-     * MaxDB: com.sap.dbtech.jdbc.DriverSapDB
-     * PostgreSQL: org.postgresql.ds.PGSimpleDataSource
-     * SyBase: com.sybase.jdbc4.jdbc.SybDataSource
-     * SqLite: org.sqlite.SQLiteDataSource
-     *
-     * HikariPool - configuration Example:
-     * allowPoolSuspension.............false
-     * autoCommit......................true
-     * catalog.........................
-     * connectionInitSql...............
-     * connectionTestQuery.............
-     * connectionTimeout...............30000
-     * dataSource......................
-     * dataSourceClassName.............
-     * dataSourceJNDI..................
-     * dataSourceProperties............{password=<masked>}
-     * driverClassName.................
-     * healthCheckProperties...........{}
-     * healthCheckRegistry.............
-     * idleTimeout.....................600000
-     * initializationFailFast..........true
-     * isolateInternalQueries..........false
-     * jdbc4ConnectionTest.............false
-     * jdbcUrl.........................jdbc:mysql://192.168.0.1:3306/test
-     * leakDetectionThreshold..........0
-     * maxLifetime.....................1800000
-     * maximumPoolSize.................10
-     * metricRegistry..................
-     * metricsTrackerFactory...........
-     * minimumIdle.....................10
-     * password........................<masked>
-     * poolName........................HikariPool-0
-     * readOnly........................false
-     * registerMbeans..................false
-     * scheduledExecutorService........
-     * threadFactory...................
-     * transactionIsolation............
-     * username........................xxxx
-     * validationTimeout...............5000
-     */
-    private void initDbProps() {
-        // databaseProps.setProperty("dataSource.url", "jdbc:mysql://192.168.0.1:3306/test");
-        // databaseProps.setProperty("dataSource.user", "test");
-        // databaseProps.setProperty("dataSource.password", "test");
-
-        // Most relevant Performance values
-        // maximumPoolSize to 20, minimumIdle to 5, and idleTimeout to 2 minutes.
-        // databaseProps.setProperty("maximumPoolSize", ""+maximumPoolSize);
-        // databaseProps.setProperty("minimumIdle", ""+minimumIdle);
-        // databaseProps.setProperty("idleTimeout", ""+idleTimeout);
-        // databaseProps.setProperty("connectionTimeout",""+connectionTimeout);
-        // databaseProps.setProperty("idleTimeout", ""+idleTimeout);
-        // databaseProps.setProperty("maxLifetime", ""+maxLifetime);
-        // databaseProps.setProperty("validationTimeout",""+validationTimeout);
-    }
-
-    public void initAfterFirstDbConnection() {
-        logger.debug("JDBC::initAfterFirstDbConnection: Initializing step, after db is connected.");
-        // Initialize sqlTypes, depending on DB version for example
-        dbMeta = new DbMetaData();// get DB information
-    }
-
-    public Properties getConnectionProperties() {
-        return new Properties(this.databaseProps);
-    }
-
-    /**************
-     * ITEMS DAOs *
-     **************/
-    public @Nullable Integer doPingDB() {
-        final @Nullable Integer result = Yank.queryScalar(sqlPingDB, Integer.class, null);
-        return result;
-    }
-
-    public @Nullable String doGetDB() {
-        final @Nullable String result = Yank.queryScalar(sqlGetDB, String.class, null);
-        return result;
-    }
-
-    public boolean doIfTableExists(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlIfTableExists, new String[] { "#searchTable#" },
-                new String[] { vo.getItemsManageTable() });
-        logger.debug("JDBC::doIfTableExists sql={}", sql);
-        final @Nullable String result = Yank.queryScalar(sql, String.class, null);
-        return Objects.nonNull(result);
-    }
-
-    public boolean doIfTableExists(String tableName) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlIfTableExists, new String[] { "#searchTable#" },
-                new String[] { tableName });
-        logger.debug("JDBC::doIfTableExists sql={}", sql);
-        final @Nullable String result = Yank.queryScalar(sql, String.class, null);
-        return Objects.nonNull(result);
-    }
-
-    public Long doCreateNewEntryInItemsTable(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
-                new String[] { "#itemsManageTable#", "#itemname#" },
-                new String[] { vo.getItemsManageTable(), vo.getItemName() });
-        logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
-        return Yank.insert(sql, null);
-    }
-
-    public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
-                new String[] { "#itemsManageTable#", "#colname#", "#coltype#" },
-                new String[] { vo.getItemsManageTable(), vo.getColname(), vo.getColtype() });
-        logger.debug("JDBC::doCreateItemsTableIfNot sql={}", sql);
-        Yank.execute(sql, null);
-        return vo;
-    }
-
-    public ItemsVO doDropItemsTableIfExists(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlDropItemsTableIfExists, new String[] { "#itemsManageTable#" },
-                new String[] { vo.getItemsManageTable() });
-        logger.debug("JDBC::doDropItemsTableIfExists sql={}", sql);
-        Yank.execute(sql, null);
-        return vo;
-    }
-
-    public void doDropTable(String tableName) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlDropTable, new String[] { "#tableName#" },
-                new String[] { tableName });
-        logger.debug("JDBC::doDropTable sql={}", sql);
-        Yank.execute(sql, null);
-    }
-
-    public void doDeleteItemsEntry(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlDeleteItemsEntry,
-                new String[] { "#itemsManageTable#", "#itemname#" },
-                new String[] { vo.getItemsManageTable(), vo.getItemName() });
-        logger.debug("JDBC::doDeleteItemsEntry sql={}", sql);
-        Yank.execute(sql, null);
-    }
-
-    public List<ItemsVO> doGetItemIDTableNames(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlGetItemIDTableNames, new String[] { "#itemsManageTable#" },
-                new String[] { vo.getItemsManageTable() });
-        logger.debug("JDBC::doGetItemIDTableNames sql={}", sql);
-        return Yank.queryBeanList(sql, ItemsVO.class, null);
-    }
-
-    public List<ItemsVO> doGetItemTables(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlGetItemTables,
-                new String[] { "#jdbcUriDatabaseName#", "#itemsManageTable#" },
-                new String[] { vo.getJdbcUriDatabaseName(), vo.getItemsManageTable() });
-        logger.debug("JDBC::doGetItemTables sql={}", sql);
-        return Yank.queryBeanList(sql, ItemsVO.class, null);
-    }
-
-    /*************
-     * ITEM DAOs *
-     *************/
-    public void doUpdateItemTableNames(List<ItemVO> vol) {
-        logger.debug("JDBC::doUpdateItemTableNames vol.size = {}", vol.size());
-        for (ItemVO itemTable : vol) {
-            String sql = updateItemTableNamesProvider(itemTable);
-            Yank.execute(sql, null);
-        }
-    }
-
-    public void doCreateItemTable(ItemVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemTable,
-                new String[] { "#tableName#", "#dbType#", "#tablePrimaryKey#" },
-                new String[] { vo.getTableName(), vo.getDbType(), sqlTypes.get("tablePrimaryKey") });
-        logger.debug("JDBC::doCreateItemTable sql={}", sql);
-        Yank.execute(sql, null);
-    }
-
-    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") });
-        Object[] params = { storedVO.getValue(), storedVO.getValue() };
-        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
-        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 = { timestamp, storedVO.getValue(), 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);
-        logger.debug("JDBC::doGetHistItemFilterQuery sql={}", sql);
-        List<Object[]> m = Yank.queryObjectArrays(sql, null);
-        if (m == null) {
-            logger.debug("JDBC::doGetHistItemFilterQuery Query failed. Returning an empty list.");
-            return List.of();
-        }
-        // 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, objectAsState(item, unit, o[1]), objectAsZonedDateTime(o[0])))
-                .collect(Collectors.<HistoricItem> toList());
-    }
-
-    public void doDeleteItemValues(FilterCriteria filter, String table, ZoneId timeZone) {
-        String sql = histItemFilterDeleteProvider(filter, table, timeZone);
-        logger.debug("JDBC::doDeleteItemValues sql={}", sql);
-        Yank.execute(sql, null);
-    }
-
-    public long doGetRowCount(String tableName) {
-        final String sql = StringUtilsExt.replaceArrayMerge(sqlGetRowCount, new String[] { "#tableName#" },
-                new String[] { tableName });
-        logger.debug("JDBC::doGetRowCount sql={}", sql);
-        final @Nullable Long result = Yank.queryScalar(sql, Long.class, null);
-        return Objects.requireNonNullElse(result, 0L);
-    }
-
-    /*************
-     * Providers *
-     *************/
-    static final DateTimeFormatter JDBC_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
-
-    protected String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
-            String simpleName, ZoneId timeZone) {
-        logger.debug(
-                "JDBC::getHistItemFilterQueryProvider filter = {}, numberDecimalcount = {}, table = {}, simpleName = {}",
-                filter, numberDecimalcount, table, simpleName);
-
-        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
-        // rounding HALF UP
-        String queryString = "NUMBERITEM".equalsIgnoreCase(simpleName) && numberDecimalcount > -1
-                ? "SELECT time, ROUND(value," + numberDecimalcount + ") FROM " + table
-                : "SELECT time, value FROM " + table;
-        if (!filterString.isEmpty()) {
-            queryString += filterString;
-        }
-        logger.debug("JDBC::query queryString = {}", queryString);
-        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 = filterString.isEmpty() ? "TRUNCATE TABLE " + table
-                : "DELETE FROM " + table + 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(ItemVO itemTable) {
-        String queryString = "ALTER TABLE " + itemTable.getTableName() + " RENAME TO " + itemTable.getNewTableName();
-        logger.debug("JDBC::query queryString = {}", queryString);
-        return queryString;
-    }
-
-    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(), itemState);
-
-        // insertItemValue
-        logger.debug("JDBC::storeItemValueProvider: itemState: '{}'", itemState);
-        /*
-         * !!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(itemState.toString());
-                break;
-            case "NUMBERITEM":
-                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<?>) itemState).toUnit(unit);
-                        if (convertedState == null) {
-                            logger.warn(
-                                    "JDBC::storeItemValueProvider: Failed to convert state '{}' to unit '{}'. Please check your item definition for correctness.",
-                                    itemState, unit);
-                            convertedState = itemState;
-                        }
-                    }
-                }
-                String it = getSqlTypes().get(itemType);
-                if (it == null) {
-                    logger.warn("JDBC::storeItemValueProvider: No SQL type defined for item type {}", itemType);
-                } else 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: itemState: '{}'", convertedState);
-                    vo.setValue(convertedState.toString());
-                }
-                break;
-            case "ROLLERSHUTTERITEM":
-            case "DIMMERITEM":
-                vo.setValueTypes(getSqlTypes().get(itemType), java.lang.Integer.class);
-                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) itemState).getZonedDateTime().toInstant().toEpochMilli());
-                logger.debug("JDBC::storeItemValueProvider: DateTimeItem: '{}'", d);
-                vo.setValue(d);
-                break;
-            case "IMAGEITEM":
-                vo.setValueTypes(getSqlTypes().get(itemType), java.lang.String.class);
-                String encodedString = item.getState().toFullString();
-                logger.debug("JDBC::storeItemValueProvider: ImageItem: '{}'", encodedString);
-                vo.setValue(encodedString);
-                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: itemState: '{}'", itemState);
-                vo.setValue(itemState.toString());
-                break;
-        }
-        return vo;
-    }
-
-    /*****************
-     * H E L P E R S *
-     *****************/
-    protected State objectAsState(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 == null) {
-                throw new UnsupportedOperationException("No SQL type defined for item type NUMBERITEM");
-            }
-            if (it.toUpperCase().contains("DOUBLE")) {
-                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 unit == null ? new DecimalType((BigDecimal) v)
-                        : QuantityType.valueOf(((BigDecimal) v).doubleValue(), unit);
-            } else if (it.toUpperCase().contains("INT")) {
-                return unit == null ? new DecimalType(objectAsInteger(v))
-                        : QuantityType.valueOf(((Integer) v).doubleValue(), unit);
-            }
-            return unit == null ? DecimalType.valueOf(objectAsString(v)) : QuantityType.valueOf(objectAsString(v));
-        } else if (item instanceof DateTimeItem) {
-            return new DateTimeType(objectAsZonedDateTime(v));
-        } else if (item instanceof ColorItem) {
-            return HSBType.valueOf(objectAsString(v));
-        } else if (item instanceof DimmerItem || item instanceof RollershutterItem) {
-            return new PercentType(objectAsInteger(v));
-        } else if (item instanceof ImageItem) {
-            return RawType.valueOf(objectAsString(v));
-        } else if (item instanceof ContactItem || item instanceof PlayerItem || item instanceof SwitchItem) {
-            State state = TypeParser.parseState(item.getAcceptedDataTypes(), ((String) v).toString().trim());
-            if (state == null) {
-                throw new UnsupportedOperationException("Unable to parse state for item " + item.toString());
-            }
-            return state;
-        } else {
-            State state = TypeParser.parseState(item.getAcceptedDataTypes(), ((String) v).toString());
-            if (state == null) {
-                throw new UnsupportedOperationException("Unable to parse state for item " + item.toString());
-            }
-            return state;
-        }
-    }
-
-    protected ZonedDateTime objectAsZonedDateTime(Object v) {
-        if (v instanceof Long) {
-            return ZonedDateTime.ofInstant(Instant.ofEpochMilli(((Number) v).longValue()), ZoneId.systemDefault());
-        } else if (v instanceof java.sql.Date) {
-            return ZonedDateTime.ofInstant(Instant.ofEpochMilli(((java.sql.Date) v).getTime()), ZoneId.systemDefault());
-        } else if (v instanceof LocalDateTime) {
-            return ((LocalDateTime) v).atZone(ZoneId.systemDefault());
-        } else if (v instanceof Instant) {
-            return ((Instant) v).atZone(ZoneId.systemDefault());
-        } else if (v instanceof java.sql.Timestamp) {
-            return ((java.sql.Timestamp) v).toInstant().atZone(ZoneId.systemDefault());
-        } else if (v instanceof java.lang.String) {
-            return ZonedDateTime.ofInstant(java.sql.Timestamp.valueOf(v.toString()).toInstant(),
-                    ZoneId.systemDefault());
-        }
-        throw new UnsupportedOperationException("Date of type " + v.getClass().getName() + " is not supported");
-    }
-
-    protected Integer objectAsInteger(Object v) {
-        if (v instanceof Byte) {
-            return ((Byte) v).intValue();
-        }
-        return ((Integer) v).intValue();
-    }
-
-    protected String objectAsString(Object v) {
-        if (v instanceof byte[]) {
-            return new String((byte[]) v);
-        }
-        return ((String) v).toString();
-    }
-
-    public String getItemType(Item i) {
-        Item item = i;
-        String def = "STRINGITEM";
-        if (i instanceof GroupItem) {
-            item = ((GroupItem) i).getBaseItem();
-            if (item == null) {
-                // 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());
-                Iterator<Item> iterator = ((GroupItem) i).getMembers().iterator();
-                if (!iterator.hasNext()) {
-                    logger.debug(
-                            "JDBC::getItemType: No Child-Members of GroupItem {}, use ItemType for STRINGITEM as Fallback",
-                            i.getName());
-                    return def;
-                }
-                item = iterator.next();
-            }
-        }
-        String itemType = item.getClass().getSimpleName().toUpperCase();
-        logger.debug("JDBC::getItemType: Try to use ItemType {} for Item {}", itemType, i.getName());
-        if (sqlTypes.get(itemType) == null) {
-            logger.warn(
-                    "JDBC::getItemType: No sqlType found for ItemType {}, use ItemType for STRINGITEM as Fallback for {}",
-                    itemType, i.getName());
-            return def;
-        }
-        return itemType;
-    }
-
-    /******************************
-     * public Getters and Setters *
-     ******************************/
-    public Map<String, String> getSqlTypes() {
-        return sqlTypes;
-    }
-
-    public String getDataType(Item item) {
-        String dataType = sqlTypes.get(getItemType(item));
-        if (dataType == null) {
-            throw new UnsupportedOperationException("No data type found for " + getItemType(item));
-        }
-        return dataType;
-    }
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcDerbyDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcDerbyDAO.java
deleted file mode 100644 (file)
index f0d12bf..0000000
+++ /dev/null
@@ -1,253 +0,0 @@
-/**
- * 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 java.time.ZoneId;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-import javax.measure.Quantity;
-import javax.measure.Unit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-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;
-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;
-import org.openhab.persistence.jdbc.utils.StringUtilsExt;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Extended Database Configuration class. Class represents
- * the extended database-specific configuration. Overrides and supplements the
- * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-@NonNullByDefault
-public class JdbcDerbyDAO extends JdbcBaseDAO {
-    private static final String DRIVER_CLASS_NAME = org.apache.derby.jdbc.EmbeddedDriver.class.getName();
-    @SuppressWarnings("unused")
-    private static final String DATA_SOURCE_CLASS_NAME = org.apache.derby.jdbc.EmbeddedDataSource.class.getName();
-
-    private final Logger logger = LoggerFactory.getLogger(JdbcDerbyDAO.class);
-
-    /********
-     * INIT *
-     ********/
-    public JdbcDerbyDAO() {
-        initSqlTypes();
-        initDbProps();
-        initSqlQueries();
-    }
-
-    private void initSqlQueries() {
-        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
-        sqlPingDB = "values 1";
-        sqlGetDB = "VALUES SYSCS_UTIL.SYSCS_GET_DATABASE_PROPERTY( 'DataDictionaryVersion' )"; // returns version
-        sqlIfTableExists = "SELECT * FROM SYS.SYSTABLES WHERE TABLENAME='#searchTable#'";
-        sqlCreateItemsTableIfNot = "CREATE TABLE #itemsManageTable# ( ItemId INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1), #colname# #coltype# NOT NULL)";
-        sqlCreateItemTable = "CREATE TABLE #tableName# (time #tablePrimaryKey# NOT NULL, value #dbType#, PRIMARY KEY(time))";
-        // Prevent error against duplicate time value (seldom): No powerful Merge found:
-        // http://www.codeproject.com/Questions/162627/how-to-insert-new-record-in-my-table-if-not-exists
-        sqlInsertItemValue = "INSERT INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, CAST( ? as #dbType#) )";
-    }
-
-    private void initSqlTypes() {
-        sqlTypes.put("DATETIMEITEM", "TIMESTAMP");
-        sqlTypes.put("DIMMERITEM", "SMALLINT");
-        sqlTypes.put("IMAGEITEM", "VARCHAR(32000)");
-        sqlTypes.put("ROLLERSHUTTERITEM", "SMALLINT");
-        sqlTypes.put("STRINGITEM", "VARCHAR(32000)");
-        sqlTypes.put("tablePrimaryValue", "CURRENT_TIMESTAMP");
-        logger.debug("JDBC::initSqlTypes: Initialized the type array sqlTypes={}", sqlTypes.values());
-    }
-
-    /**
-     * INFO: https://github.com/brettwooldridge/HikariCP
-     */
-    private void initDbProps() {
-        // Properties for HikariCP
-        // Use driverClassName
-        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
-        // OR dataSourceClassName
-        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
-        databaseProps.setProperty("maximumPoolSize", "1");
-        databaseProps.setProperty("minimumIdle", "1");
-    }
-
-    @Override
-    public void initAfterFirstDbConnection() {
-        logger.debug("JDBC::initAfterFirstDbConnection: Initializing step, after db is connected.");
-        // Initialize sqlTypes, depending on DB version for example
-        // derby does not like this... dbMeta = new DbMetaData();// get DB information
-    }
-
-    /**************
-     * ITEMS DAOs *
-     **************/
-    @Override
-    public @Nullable Integer doPingDB() {
-        return Yank.queryScalar(sqlPingDB, Integer.class, null);
-    }
-
-    @Override
-    public boolean doIfTableExists(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlIfTableExists, new String[] { "#searchTable#" },
-                new String[] { vo.getItemsManageTable().toUpperCase() });
-        logger.debug("JDBC::doIfTableExists sql={}", sql);
-        final @Nullable String result = Yank.queryScalar(sql, String.class, null);
-        return Objects.nonNull(result);
-    }
-
-    @Override
-    public Long doCreateNewEntryInItemsTable(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
-                new String[] { "#itemsManageTable#", "#itemname#" },
-                new String[] { vo.getItemsManageTable().toUpperCase(), vo.getItemName() });
-        logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
-        return Yank.insert(sql, null);
-    }
-
-    @Override
-    public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) {
-        // boolean tableExists = Yank.queryScalar(SQL_IF_TABLE_EXISTS.replace("#searchTable#",
-        // vo.getItemsManageTable().toUpperCase()), String.class, null) == null;
-        boolean tableExists = doIfTableExists(vo);
-        if (!tableExists) {
-            String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
-                    new String[] { "#itemsManageTable#", "#colname#", "#coltype#" },
-                    new String[] { vo.getItemsManageTable().toUpperCase(), vo.getColname(), vo.getColtype() });
-            logger.debug("JDBC::doCreateItemsTableIfNot tableExists={} therefore sql={}", tableExists, sql);
-            Yank.execute(sql, null);
-        } else {
-            logger.debug("JDBC::doCreateItemsTableIfNot tableExists={}, did not CREATE TABLE", tableExists);
-        }
-        return vo;
-    }
-
-    /*************
-     * ITEM DAOs *
-     *************/
-    @Override
-    public void doCreateItemTable(ItemVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemTable,
-                new String[] { "#tableName#", "#dbType#", "#tablePrimaryKey#" },
-                new String[] { vo.getTableName(), vo.getDbType(), sqlTypes.get("tablePrimaryKey") });
-        Yank.execute(sql, null);
-    }
-
-    @Override
-    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[] { storedVO.getTableName().toUpperCase(), storedVO.getDbType(),
-                        sqlTypes.get("tablePrimaryValue") });
-        Object[] params = { storedVO.getValue() };
-        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
-        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);
-        List<Object[]> m = Yank.queryObjectArrays(sql, null);
-        logger.debug("JDBC::doGetHistItemFilterQuery got Array length={}", m.size());
-        // 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, objectAsState(item, unit, o[1]), objectAsZonedDateTime(o[0]));
-        }).collect(Collectors.<HistoricItem> toList());
-    }
-
-    /****************************
-     * SQL generation Providers *
-     ****************************/
-
-    @Override
-    protected String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
-            String simpleName, ZoneId timeZone) {
-        logger.debug(
-                "JDBC::getHistItemFilterQueryProvider filter = {}, numberDecimalcount = {}, table = {}, simpleName = {}",
-                StringUtilsExt.filterToString(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) {
-            // TODO: TESTING!!!
-            // filterString += " LIMIT " + filter.getPageNumber() *
-            // filter.getPageSize() + "," + filter.getPageSize();
-            // SELECT time, value FROM ohscriptfiles_sw_ace_paths_0001 ORDER BY
-            // time DESC OFFSET 1 ROWS FETCH NEXT 0 ROWS ONLY
-            // filterString += " OFFSET " + filter.getPageSize() +" ROWS FETCH
-            // FIRST||NEXT " + filter.getPageNumber() * filter.getPageSize() + "
-            // ROWS ONLY";
-            filterString += " OFFSET " + filter.getPageSize() + " ROWS FETCH FIRST "
-                    + (filter.getPageNumber() * filter.getPageSize() + 1) + " ROWS ONLY";
-        }
-
-        // http://www.seemoredata.com/en/showthread.php?132-Round-function-in-Apache-Derby
-        // simulated round function in Derby: CAST(value 0.0005 AS DECIMAL(15,3))
-        // simulated round function in Derby: "CAST(value 0.0005 AS DECIMAL(15,"+numberDecimalcount+"))"
-
-        String queryString = "SELECT time,";
-        if ("NUMBERITEM".equalsIgnoreCase(simpleName) && numberDecimalcount > -1) {
-            // rounding HALF UP
-            queryString += "CAST(value 0.";
-            for (int i = 0; i < numberDecimalcount; i++) {
-                queryString += "0";
-            }
-            queryString += "5 AS DECIMAL(31," + numberDecimalcount + "))"; // 31 is DECIMAL max precision
-                                                                           // https://db.apache.org/derby/docs/10.0/manuals/develop/develop151.html
-        } else {
-            queryString += " value FROM " + table.toUpperCase();
-        }
-
-        if (!filterString.isEmpty()) {
-            queryString += filterString;
-        }
-        logger.debug("JDBC::query queryString = {}", queryString);
-        return queryString;
-    }
-
-    /*****************
-     * H E L P E R S *
-     *****************/
-
-    /******************************
-     * public Getters and Setters *
-     ******************************/
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcH2DAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcH2DAO.java
deleted file mode 100644 (file)
index 140f536..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * 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 org.eclipse.jdt.annotation.NonNullByDefault;
-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;
-import org.slf4j.LoggerFactory;
-
-/**
- * Extended Database Configuration class. Class represents
- * the extended database-specific configuration. Overrides and supplements the
- * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-@NonNullByDefault
-public class JdbcH2DAO extends JdbcBaseDAO {
-    private static final String DRIVER_CLASS_NAME = org.h2.Driver.class.getName();
-    @SuppressWarnings("unused")
-    private static final String DATA_SOURCE_CLASS_NAME = org.h2.jdbcx.JdbcDataSource.class.getName();
-
-    private final Logger logger = LoggerFactory.getLogger(JdbcH2DAO.class);
-
-    /********
-     * INIT *
-     ********/
-    public JdbcH2DAO() {
-        initSqlQueries();
-        initSqlTypes();
-        initDbProps();
-    }
-
-    private void initSqlQueries() {
-        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
-        sqlIfTableExists = "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='#searchTable#'";
-        // SQL_INSERT_ITEM_VALUE = "INSERT INTO #tableName# (TIME, VALUE) VALUES( NOW(), CAST( ? as #dbType#) )";
-        // http://stackoverflow.com/questions/19768051/h2-sql-database-insert-if-the-record-does-not-exist
-        sqlInsertItemValue = "MERGE INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, CAST( ? as #dbType#) )";
-    }
-
-    /**
-     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
-     */
-    private void initSqlTypes() {
-    }
-
-    /**
-     * INFO: https://github.com/brettwooldridge/HikariCP
-     */
-    private void initDbProps() {
-        // Properties for HikariCP
-        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
-        // driverClassName OR BETTER USE dataSourceClassName
-        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
-    }
-
-    /**************
-     * ITEMS DAOs *
-     **************/
-
-    /*************
-     * ITEM DAOs *
-     *************/
-    @Override
-    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[] { storedVO.getTableName(), storedVO.getDbType(), sqlTypes.get("tablePrimaryValue") });
-        Object[] params = { storedVO.getValue() };
-        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
-        Yank.execute(sql, params);
-    }
-
-    /****************************
-     * SQL generation Providers *
-     ****************************/
-
-    /*****************
-     * H E L P E R S *
-     *****************/
-
-    /******************************
-     * public Getters and Setters *
-     ******************************/
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcHsqldbDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcHsqldbDAO.java
deleted file mode 100644 (file)
index fe9eb8f..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-/**
- * 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 org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-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;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Extended Database Configuration class. Class represents
- * the extended database-specific configuration. Overrides and supplements the
- * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-@NonNullByDefault
-public class JdbcHsqldbDAO extends JdbcBaseDAO {
-    private static final String DRIVER_CLASS_NAME = org.hsqldb.jdbcDriver.class.getName();
-    @SuppressWarnings("unused")
-    private static final String DATA_SOURCE_CLASS_NAME = org.hsqldb.jdbc.JDBCDataSource.class.getName();
-
-    private final Logger logger = LoggerFactory.getLogger(JdbcHsqldbDAO.class);
-
-    /********
-     * INIT *
-     ********/
-    public JdbcHsqldbDAO() {
-        initSqlQueries();
-        initSqlTypes();
-        initDbProps();
-    }
-
-    private void initSqlQueries() {
-        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
-        // http://hsqldb.org/doc/guide/builtinfunctions-chapt.html
-        sqlPingDB = "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
-        sqlGetDB = "SELECT DATABASE () FROM INFORMATION_SCHEMA.SYSTEM_USERS";
-        sqlIfTableExists = "SELECT * FROM INFORMATION_SCHEMA.SYSTEM_TABLES WHERE TABLE_NAME='#searchTable#'";
-        sqlCreateItemsTableIfNot = "CREATE TABLE IF NOT EXISTS #itemsManageTable# ( ItemId INT GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) NOT NULL, #colname# #coltype# NOT NULL)";
-        sqlCreateNewEntryInItemsTable = "INSERT INTO #itemsManageTable# (ItemName) VALUES ('#itemname#')";
-        // Prevent error against duplicate time value
-        // http://hsqldb.org/doc/guide/dataaccess-chapt.html#dac_merge_statement
-        // SQL_INSERT_ITEM_VALUE = "INSERT INTO #tableName# (TIME, VALUE) VALUES( NOW(), CAST( ? as #dbType#) )";
-        sqlInsertItemValue = "MERGE INTO #tableName# "
-                + "USING (VALUES #tablePrimaryValue#, CAST( ? as #dbType#)) temp (TIME, VALUE) ON (#tableName#.TIME=temp.TIME) "
-                + "WHEN NOT MATCHED THEN INSERT (TIME, VALUE) VALUES (temp.TIME, temp.VALUE)";
-    }
-
-    /**
-     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
-     */
-    private void initSqlTypes() {
-    }
-
-    /**
-     * INFO: https://github.com/brettwooldridge/HikariCP
-     */
-    private void initDbProps() {
-        // Properties for HikariCP
-        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
-        // driverClassName OR BETTER USE dataSourceClassName
-        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
-    }
-
-    /**************
-     * ITEMS DAOs *
-     **************/
-    @Override
-    public @Nullable Integer doPingDB() {
-        return Yank.queryScalar(sqlPingDB, Integer.class, null);
-    }
-
-    @Override
-    public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
-                new String[] { "#itemsManageTable#", "#colname#", "#coltype#", "#itemsManageTable#" },
-                new String[] { vo.getItemsManageTable(), vo.getColname(), vo.getColtype(), vo.getItemsManageTable() });
-        logger.debug("JDBC::doCreateItemsTableIfNot sql={}", sql);
-        Yank.execute(sql, null);
-        return vo;
-    }
-
-    @Override
-    public Long doCreateNewEntryInItemsTable(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
-                new String[] { "#itemsManageTable#", "#itemname#" },
-                new String[] { vo.getItemsManageTable(), vo.getItemName() });
-        logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
-        return Yank.insert(sql, null);
-    }
-
-    /*************
-     * ITEM DAOs *
-     *************/
-    @Override
-    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[] { storedVO.getTableName(), storedVO.getDbType(), storedVO.getTableName(),
-                        sqlTypes.get("tablePrimaryValue") });
-        Object[] params = { storedVO.getValue() };
-        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
-        Yank.execute(sql, params);
-    }
-
-    /****************************
-     * SQL generation Providers *
-     ****************************/
-
-    /*****************
-     * H E L P E R S *
-     *****************/
-
-    /******************************
-     * public Getters and Setters *
-     ******************************/
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcMariadbDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcMariadbDAO.java
deleted file mode 100644 (file)
index 740cc0f..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/**
- * 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 java.util.Objects;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.knowm.yank.Yank;
-import org.openhab.persistence.jdbc.utils.DbMetaData;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Extended Database Configuration class. Class represents
- * the extended database-specific configuration. Overrides and supplements the
- * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-@NonNullByDefault
-public class JdbcMariadbDAO extends JdbcBaseDAO {
-    private static final String DRIVER_CLASS_NAME = org.mariadb.jdbc.Driver.class.getName();
-    @SuppressWarnings("unused")
-    private static final String DATA_SOURCE_CLASS_NAME = org.mariadb.jdbc.MariaDbDataSource.class.getName();
-
-    private final Logger logger = LoggerFactory.getLogger(JdbcMariadbDAO.class);
-
-    /********
-     * INIT *
-     ********/
-    public JdbcMariadbDAO() {
-        initSqlTypes();
-        initDbProps();
-        initSqlQueries();
-    }
-
-    private void initSqlQueries() {
-        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
-    }
-
-    /**
-     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
-     */
-    private void initSqlTypes() {
-        logger.debug("JDBC::initSqlTypes: Initialize the type array");
-        sqlTypes.put("IMAGEITEM", "VARCHAR(16255)");
-        sqlTypes.put("STRINGITEM", "VARCHAR(16255)"); // MariaDB using utf-8 max = 16383, using 16383-128 = 16255
-    }
-
-    /**
-     * INFO: https://github.com/brettwooldridge/HikariCP
-     */
-    private void initDbProps() {
-        // Performancetuning
-        databaseProps.setProperty("dataSource.cachePrepStmts", "true");
-        databaseProps.setProperty("dataSource.prepStmtCacheSize", "250");
-        databaseProps.setProperty("dataSource.prepStmtCacheSqlLimit", "2048");
-        databaseProps.setProperty("dataSource.jdbcCompliantTruncation", "false");// jdbc standard max varchar max length
-        // of 21845
-
-        // Properties for HikariCP
-        // Use driverClassName
-        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
-        // driverClassName OR BETTER USE dataSourceClassName
-        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
-        databaseProps.setProperty("maximumPoolSize", "3");
-        databaseProps.setProperty("minimumIdle", "2");
-    }
-
-    @Override
-    public void initAfterFirstDbConnection() {
-        logger.debug("JDBC::initAfterFirstDbConnection: Initializing step, after db is connected.");
-        DbMetaData dbMeta = new DbMetaData();
-        this.dbMeta = dbMeta;
-        // Initialize sqlTypes, depending on DB version for example
-        if (dbMeta.isDbVersionGreater(5, 1)) {
-            sqlTypes.put("DATETIMEITEM", "TIMESTAMP(3)");
-            sqlTypes.put("tablePrimaryKey", "TIMESTAMP(3)");
-            sqlTypes.put("tablePrimaryValue", "NOW(3)");
-        }
-    }
-
-    /**************
-     * ITEMS DAOs *
-     **************/
-    @Override
-    public @Nullable Integer doPingDB() {
-        final @Nullable Long result = Yank.queryScalar(sqlPingDB, Long.class, null);
-        return Objects.nonNull(result) ? result.intValue() : null;
-    }
-
-    /*************
-     * ITEM DAOs *
-     *************/
-
-    /****************************
-     * SQL generation Providers *
-     ****************************/
-
-    /*****************
-     * H E L P E R S *
-     *****************/
-
-    /******************************
-     * public Getters and Setters *
-     ******************************/
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcMysqlDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcMysqlDAO.java
deleted file mode 100644 (file)
index 361b177..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-/**
- * 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 java.util.Objects;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.knowm.yank.Yank;
-import org.openhab.persistence.jdbc.utils.DbMetaData;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Extended Database Configuration class. Class represents
- * the extended database-specific configuration. Overrides and supplements the
- * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
- *
- * since driver version >= 6.0 sometimes timezone conversation is needed: ?serverTimezone=UTC
- * example: dbProps.setProperty("jdbcUrl", "jdbc:mysql://192.168.0.181:3306/ItemTypeTest3?serverTimezone=UTC");//mysql
- * 5.7
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-@NonNullByDefault
-public class JdbcMysqlDAO extends JdbcBaseDAO {
-    private static final String DRIVER_CLASS_NAME = com.mysql.cj.jdbc.Driver.class.getName();
-    @SuppressWarnings("unused")
-    private static final String DATA_SOURCE_CLASS_NAME = com.mysql.cj.jdbc.MysqlDataSource.class.getName();
-
-    private final Logger logger = LoggerFactory.getLogger(JdbcMysqlDAO.class);
-
-    /********
-     * INIT *
-     ********/
-    public JdbcMysqlDAO() {
-        initSqlTypes();
-        initDbProps();
-        initSqlQueries();
-    }
-
-    private void initSqlQueries() {
-        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
-    }
-
-    /**
-     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
-     */
-    private void initSqlTypes() {
-        logger.debug("JDBC::initSqlTypes: Initialize the type array");
-        sqlTypes.put("STRINGITEM", "VARCHAR(21717)");// mysql using utf-8 max 65535/3 = 21845, using 21845-128 = 21717
-    }
-
-    /**
-     * INFO: https://github.com/brettwooldridge/HikariCP
-     */
-    private void initDbProps() {
-        // Performancetuning
-        databaseProps.setProperty("dataSource.cachePrepStmts", "true");
-        databaseProps.setProperty("dataSource.prepStmtCacheSize", "250");
-        databaseProps.setProperty("dataSource.prepStmtCacheSqlLimit", "2048");
-        databaseProps.setProperty("dataSource.jdbcCompliantTruncation", "false");// jdbc standard max varchar max length
-                                                                                 // of 21845
-
-        // Properties for HikariCP
-        // Use driverClassName
-        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
-        // OR dataSourceClassName
-        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
-        databaseProps.setProperty("maximumPoolSize", "3");
-        databaseProps.setProperty("minimumIdle", "2");
-    }
-
-    @Override
-    public void initAfterFirstDbConnection() {
-        logger.debug("JDBC::initAfterFirstDbConnection: Initializing step, after db is connected.");
-        DbMetaData dbMeta = new DbMetaData();
-        this.dbMeta = dbMeta;
-        // Initialize sqlTypes, depending on DB version for example
-        if (dbMeta.isDbVersionGreater(5, 5)) {
-            sqlTypes.put("DATETIMEITEM", "TIMESTAMP(3)");
-            sqlTypes.put("tablePrimaryKey", "TIMESTAMP(3)");
-            sqlTypes.put("tablePrimaryValue", "NOW(3)");
-        }
-    }
-
-    /**************
-     * ITEMS DAOs *
-     **************/
-    @Override
-    public @Nullable Integer doPingDB() {
-        final @Nullable Long result = Yank.queryScalar(sqlPingDB, Long.class, null);
-        return Objects.nonNull(result) ? result.intValue() : null;
-    }
-
-    /*************
-     * ITEM DAOs *
-     *************/
-
-    /****************************
-     * SQL generation Providers *
-     ****************************/
-
-    /*****************
-     * H E L P E R S *
-     *****************/
-
-    /******************************
-     * public Getters and Setters *
-     ******************************/
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcPostgresqlDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcPostgresqlDAO.java
deleted file mode 100644 (file)
index 974fd8f..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-/**
- * 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 java.time.ZoneId;
-import java.util.List;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-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;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Extended Database Configuration class. Class represents
- * the extended database-specific configuration. Overrides and supplements the
- * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-@NonNullByDefault
-public class JdbcPostgresqlDAO extends JdbcBaseDAO {
-    private static final String DRIVER_CLASS_NAME = org.postgresql.Driver.class.getName();
-    @SuppressWarnings("unused")
-    private static final String DATA_SOURCE_CLASS_NAME = org.postgresql.ds.PGSimpleDataSource.class.getName();
-
-    private final Logger logger = LoggerFactory.getLogger(JdbcPostgresqlDAO.class);
-
-    /********
-     * INIT *
-     ********/
-    public JdbcPostgresqlDAO() {
-        initSqlQueries();
-        initSqlTypes();
-        initDbProps();
-    }
-
-    private void initSqlQueries() {
-        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
-        // System Information Functions: https://www.postgresql.org/docs/9.2/static/functions-info.html
-        sqlGetDB = "SELECT CURRENT_DATABASE()";
-        sqlIfTableExists = "SELECT * FROM PG_TABLES WHERE TABLENAME='#searchTable#'";
-        sqlCreateItemsTableIfNot = "CREATE TABLE IF NOT EXISTS #itemsManageTable# (itemid SERIAL NOT NULL, #colname# #coltype# NOT NULL, CONSTRAINT #itemsManageTable#_pkey PRIMARY KEY (itemid))";
-        sqlCreateNewEntryInItemsTable = "INSERT INTO items (itemname) SELECT itemname FROM #itemsManageTable# UNION VALUES ('#itemname#') EXCEPT SELECT itemname FROM items";
-        sqlGetItemTables = "SELECT table_name FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_schema=(SELECT table_schema "
-                + "FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_name='#itemsManageTable#') AND NOT table_name='#itemsManageTable#'";
-        // http://stackoverflow.com/questions/17267417/how-do-i-do-an-upsert-merge-insert-on-duplicate-update-in-postgresql
-        // for later use, PostgreSql > 9.5 to prevent PRIMARY key violation use:
-        // SQL_INSERT_ITEM_VALUE = "INSERT INTO #tableName# (TIME, VALUE) VALUES( NOW(), CAST( ? as #dbType#) ) ON
-        // CONFLICT DO NOTHING";
-        sqlInsertItemValue = "INSERT INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, CAST( ? as #dbType#) )";
-    }
-
-    /**
-     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
-     */
-    private void initSqlTypes() {
-        // Initialize the type array
-        sqlTypes.put("CALLITEM", "VARCHAR");
-        sqlTypes.put("COLORITEM", "VARCHAR");
-        sqlTypes.put("CONTACTITEM", "VARCHAR");
-        sqlTypes.put("DATETIMEITEM", "TIMESTAMP");
-        sqlTypes.put("DIMMERITEM", "SMALLINT");
-        sqlTypes.put("IMAGEITEM", "VARCHAR");
-        sqlTypes.put("LOCATIONITEM", "VARCHAR");
-        sqlTypes.put("NUMBERITEM", "DOUBLE PRECISION");
-        sqlTypes.put("PLAYERITEM", "VARCHAR");
-        sqlTypes.put("ROLLERSHUTTERITEM", "SMALLINT");
-        sqlTypes.put("STRINGITEM", "VARCHAR");
-        sqlTypes.put("SWITCHITEM", "VARCHAR");
-        logger.debug("JDBC::initSqlTypes: Initialized the type array sqlTypes={}", sqlTypes.values());
-    }
-
-    /**
-     * INFO: https://github.com/brettwooldridge/HikariCP
-     */
-    private void initDbProps() {
-        // Performance:
-        // databaseProps.setProperty("dataSource.cachePrepStmts", "true");
-        // databaseProps.setProperty("dataSource.prepStmtCacheSize", "250");
-        // databaseProps.setProperty("dataSource.prepStmtCacheSqlLimit", "2048");
-
-        // Properties for HikariCP
-        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
-        // driverClassName OR BETTER USE dataSourceClassName
-        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
-        // databaseProps.setProperty("maximumPoolSize", "3");
-        // databaseProps.setProperty("minimumIdle", "2");
-    }
-
-    /**************
-     * ITEMS DAOs *
-     **************/
-    @Override
-    public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
-                new String[] { "#itemsManageTable#", "#colname#", "#coltype#", "#itemsManageTable#" },
-                new String[] { vo.getItemsManageTable(), vo.getColname(), vo.getColtype(), vo.getItemsManageTable() });
-        logger.debug("JDBC::doCreateItemsTableIfNot sql={}", sql);
-        Yank.execute(sql, null);
-        return vo;
-    }
-
-    @Override
-    public Long doCreateNewEntryInItemsTable(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
-                new String[] { "#itemsManageTable#", "#itemname#" },
-                new String[] { vo.getItemsManageTable(), vo.getItemName() });
-        logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
-        return Yank.insert(sql, null);
-    }
-
-    @Override
-    public List<ItemsVO> doGetItemTables(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(this.sqlGetItemTables,
-                new String[] { "#itemsManageTable#", "#itemsManageTable#" },
-                new String[] { vo.getItemsManageTable(), vo.getItemsManageTable() });
-        this.logger.debug("JDBC::doGetItemTables sql={}", sql);
-        return Yank.queryBeanList(sql, ItemsVO.class, null);
-    }
-
-    /*************
-     * ITEM DAOs *
-     *************/
-    @Override
-    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[] { storedVO.getTableName(), storedVO.getDbType(), sqlTypes.get("tablePrimaryValue") });
-        Object[] params = { storedVO.getValue() };
-        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
-        Yank.execute(sql, params);
-    }
-
-    /****************************
-     * SQL generation Providers *
-     ****************************/
-
-    @Override
-    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);
-
-        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) {
-            // see:
-            // http://www.jooq.org/doc/3.5/manual/sql-building/sql-statements/select-statement/limit-clause/
-            filterString += " OFFSET " + filter.getPageNumber() * filter.getPageSize() + " LIMIT "
-                    + filter.getPageSize();
-        }
-        String queryString = "NUMBERITEM".equalsIgnoreCase(simpleName) && numberDecimalcount > -1
-                ? "SELECT time, ROUND(CAST (value AS numeric)," + numberDecimalcount + ") FROM " + table
-                : "SELECT time, value FROM " + table;
-        if (!filterString.isEmpty()) {
-            queryString += filterString;
-        }
-        logger.debug("JDBC::query queryString = {}", queryString);
-        return queryString;
-    }
-
-    /*****************
-     * H E L P E R S *
-     *****************/
-
-    /******************************
-     * public Getters and Setters *
-     ******************************/
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcSqliteDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcSqliteDAO.java
deleted file mode 100644 (file)
index 4559e1b..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-/**
- * 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 org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-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;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Extended Database Configuration class. Class represents
- * the extended database-specific configuration. Overrides and supplements the
- * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-@NonNullByDefault
-public class JdbcSqliteDAO extends JdbcBaseDAO {
-    private static final String DRIVER_CLASS_NAME = org.sqlite.JDBC.class.getName();
-    @SuppressWarnings("unused")
-    private static final String DATA_SOURCE_CLASS_NAME = org.sqlite.SQLiteDataSource.class.getName();
-
-    private final Logger logger = LoggerFactory.getLogger(JdbcSqliteDAO.class);
-
-    /********
-     * INIT *
-     ********/
-    public JdbcSqliteDAO() {
-        initSqlQueries();
-        initSqlTypes();
-        initDbProps();
-    }
-
-    private void initSqlQueries() {
-        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
-        sqlGetDB = "PRAGMA DATABASE_LIST"; // "SELECT SQLITE_VERSION()"; // "PRAGMA DATABASE_LIST"->db Path/Name
-                                           // "PRAGMA SCHEMA_VERSION";
-        sqlIfTableExists = "SELECT name FROM sqlite_master WHERE type='table' AND name='#searchTable#'";
-        sqlCreateItemsTableIfNot = "CREATE TABLE IF NOT EXISTS #itemsManageTable# (ItemId INTEGER PRIMARY KEY AUTOINCREMENT, #colname# #coltype# NOT NULL)";
-        sqlInsertItemValue = "INSERT OR IGNORE INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, CAST( ? as #dbType#) )";
-    }
-
-    /**
-     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
-     */
-    private void initSqlTypes() {
-        logger.debug("JDBC::initSqlTypes: Initialize the type array");
-        sqlTypes.put("tablePrimaryValue", "strftime('%Y-%m-%d %H:%M:%f' , 'now' , 'localtime')");
-    }
-
-    /**
-     * INFO: https://github.com/brettwooldridge/HikariCP
-     */
-    private void initDbProps() {
-        // Properties for HikariCP
-        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
-        // driverClassName OR BETTER USE dataSourceClassName
-        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
-    }
-
-    /**************
-     * ITEMS DAOs *
-     **************/
-
-    @Override
-    public @Nullable String doGetDB() {
-        return Yank.queryColumn(sqlGetDB, "file", String.class, null).get(0);
-    }
-
-    @Override
-    public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) {
-        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
-                new String[] { "#itemsManageTable#", "#colname#", "#coltype#" },
-                new String[] { vo.getItemsManageTable(), vo.getColname(), vo.getColtype() });
-        logger.debug("JDBC::doCreateItemsTableIfNot sql={}", sql);
-        Yank.execute(sql, null);
-        return vo;
-    }
-
-    /*************
-     * ITEM DAOs *
-     *************/
-    @Override
-    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[] { storedVO.getTableName(), storedVO.getDbType(), sqlTypes.get("tablePrimaryValue") });
-        Object[] params = { storedVO.getValue() };
-        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
-        Yank.execute(sql, params);
-    }
-
-    /****************************
-     * SQL generation Providers *
-     ****************************/
-
-    /*****************
-     * H E L P E R S *
-     *****************/
-
-    /******************************
-     * public Getters and Setters *
-     ******************************/
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcTimescaledbDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcTimescaledbDAO.java
deleted file mode 100644 (file)
index c275032..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * 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 java.util.Properties;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.knowm.yank.Yank;
-import org.openhab.persistence.jdbc.dto.ItemVO;
-import org.openhab.persistence.jdbc.utils.StringUtilsExt;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Extended Database Configuration class. Class represents the extended database-specific configuration. Overrides and
- * supplements the default settings from JdbcBaseDAO and JdbcPostgresqlDAO.
- *
- * @author Riccardo Nimser-Joseph - Initial contribution
- * @author Dan Cunningham - Fixes and refactoring
- */
-@NonNullByDefault
-public class JdbcTimescaledbDAO extends JdbcPostgresqlDAO {
-    private final Logger logger = LoggerFactory.getLogger(JdbcTimescaledbDAO.class);
-
-    private final String sqlCreateHypertable = "SELECT created from create_hypertable('#tableName#', 'time')";
-
-    @Override
-    public Properties getConnectionProperties() {
-        Properties properties = (Properties) this.databaseProps.clone();
-        // Adjust the jdbc url since the service name 'timescaledb' is only used to differentiate the DAOs
-        if (properties.containsKey("jdbcUrl")) {
-            properties.put("jdbcUrl", properties.getProperty("jdbcUrl").replace("timescaledb", "postgresql"));
-        }
-        return properties;
-    }
-
-    @Override
-    public void doCreateItemTable(ItemVO vo) {
-        super.doCreateItemTable(vo);
-        String sql = StringUtilsExt.replaceArrayMerge(this.sqlCreateHypertable, new String[] { "#tableName#" },
-                new String[] { vo.getTableName() });
-        this.logger.debug("JDBC::doCreateItemTable sql={}", sql);
-        Yank.queryScalar(sql, Boolean.class, null);
-    }
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/ItemVO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/ItemVO.java
deleted file mode 100644 (file)
index af4a03b..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-/**
- * 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.dto;
-
-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;
-
-/**
- * Represents the Item-data on the part of MyBatis/database.
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-public class ItemVO implements Serializable {
-    private final Logger logger = LoggerFactory.getLogger(ItemVO.class);
-
-    private static final long serialVersionUID = 1871441039821454890L;
-
-    private String tableName;
-    private @Nullable String newTableName;
-    private String dbType;
-    private String jdbcType;
-    private String itemType;
-    private Class<?> javaType;
-    private Date time;
-    private Object value;
-
-    public ItemVO(String tableName, @Nullable String newTableName) {
-        logger.debug("JDBC:ItemVO tableName={}; newTableName={}; ", tableName, newTableName);
-        this.tableName = tableName;
-        this.newTableName = newTableName;
-    }
-
-    public ItemVO() {
-    }
-
-    public void setValueTypes(String dbType, Class<?> javaType) {
-        logger.debug("JDBC:ItemVO setValueTypes dbType={}; javaType={};", dbType, javaType);
-        this.dbType = dbType;
-        this.javaType = javaType;
-    }
-
-    public String getTableName() {
-        return tableName;
-    }
-
-    public void setTableName(String tableName) {
-        this.tableName = tableName;
-    }
-
-    public @Nullable String getNewTableName() {
-        return newTableName;
-    }
-
-    public void setNewTableName(String newTableName) {
-        this.newTableName = newTableName;
-    }
-
-    public String getDbType() {
-        return dbType;
-    }
-
-    public void setDbType(String dbType) {
-        this.dbType = dbType;
-    }
-
-    public String getJdbcType() {
-        return jdbcType;
-    }
-
-    public void setJdbcType(String jdbcType) {
-        this.jdbcType = jdbcType;
-    }
-
-    public String getItemType() {
-        return itemType;
-    }
-
-    public void setItemType(String itemType) {
-        this.itemType = itemType;
-    }
-
-    public String getJavaType() {
-        return javaType.getName();
-    }
-
-    public void setJavaType(Class<?> javaType) {
-        this.javaType = javaType;
-    }
-
-    public Date getTime() {
-        return time;
-    }
-
-    public void setTime(Date time) {
-        this.time = time;
-    }
-
-    public Object getValue() {
-        return value;
-    }
-
-    public void setValue(Object value) {
-        this.value = value;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        ItemVO other = (ItemVO) obj;
-        if (value == null) {
-            if (other.value != null) {
-                return false;
-            }
-        } else if (!value.equals(other.value)) {
-            return false;
-        }
-        return Objects.equals(time, other.time);
-    }
-
-    @Override
-    public String 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();
-    }
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/ItemsVO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/ItemsVO.java
deleted file mode 100644 (file)
index 9f8f7da..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-/**
- * 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.dto;
-
-import java.io.Serializable;
-import java.util.Objects;
-
-/**
- * Represents the table naming data.
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-public class ItemsVO implements Serializable {
-
-    private static final long serialVersionUID = 2871961811177601520L;
-
-    private static final String STR_FILTER = "[^a-zA-Z0-9]";
-
-    private String coltype = "VARCHAR(500)";
-    private String colname = "itemname";
-    private String itemsManageTable = "items";
-    private int itemId;
-    private String itemName;
-    private String tableName;
-    private String jdbcUriDatabaseName;
-
-    public String getColtype() {
-        return coltype;
-    }
-
-    public void setColtype(String coltype) {
-        this.coltype = coltype.replaceAll(STR_FILTER, "");
-    }
-
-    public String getColname() {
-        return colname;
-    }
-
-    public void setColname(String colname) {
-        this.colname = colname.replaceAll(STR_FILTER, "");
-    }
-
-    public String getItemsManageTable() {
-        return itemsManageTable;
-    }
-
-    public void setItemsManageTable(String itemsManageTable) {
-        this.itemsManageTable = itemsManageTable.replaceAll(STR_FILTER, "");
-    }
-
-    public int getItemId() {
-        return itemId;
-    }
-
-    public void setItemId(int itemId) {
-        this.itemId = itemId;
-    }
-
-    public String getItemName() {
-        return itemName;
-    }
-
-    public void setItemName(String itemName) {
-        this.itemName = itemName;
-    }
-
-    public String getTableName() {
-        return tableName;
-    }
-
-    public void setTableName(String tableName) {
-        this.tableName = tableName;
-    }
-
-    public String getJdbcUriDatabaseName() {
-        return jdbcUriDatabaseName;
-    }
-
-    public void setJdbcUriDatabaseName(String jdbcUriDatabaseName) {
-        this.jdbcUriDatabaseName = jdbcUriDatabaseName;
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see java.lang.Object#hashCode()
-     */
-    @Override
-    public int hashCode() {
-        return Objects.hash(itemName, itemId);
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see java.lang.Object#equals(java.lang.Object)
-     */
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        ItemsVO other = (ItemsVO) obj;
-        if (itemName == null) {
-            if (other.itemName != null) {
-                return false;
-            }
-        } else if (!itemName.equals(other.itemName)) {
-            return false;
-        }
-        return itemId == other.itemId;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append("ItemsVO [coltype=");
-        builder.append(coltype);
-        builder.append(", colname=");
-        builder.append(colname);
-        builder.append(", itemsManageTable=");
-        builder.append(itemsManageTable);
-        builder.append(", itemid=");
-        builder.append(itemId);
-        builder.append(", itemname=");
-        builder.append(itemName);
-        builder.append(", table_name=");
-        builder.append(tableName);
-        builder.append("]");
-        return builder.toString();
-    }
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/JdbcHistoricItem.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/JdbcHistoricItem.java
deleted file mode 100644 (file)
index be1a2e3..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/**
- * 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.dto;
-
-import java.time.ZonedDateTime;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.core.persistence.HistoricItem;
-import org.openhab.core.types.State;
-
-/**
- * Represents the data on the part of openHAB.
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-@NonNullByDefault
-public class JdbcHistoricItem implements HistoricItem {
-
-    private final String name;
-    private final State state;
-    private final ZonedDateTime timestamp;
-
-    public JdbcHistoricItem(String name, State state, ZonedDateTime timestamp) {
-        this.name = name;
-        this.state = state;
-        this.timestamp = timestamp;
-    }
-
-    @Override
-    public String getName() {
-        return name;
-    }
-
-    @Override
-    public State getState() {
-        return state;
-    }
-
-    @Override
-    public ZonedDateTime getTimestamp() {
-        return timestamp;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append("JdbcItem [name=");
-        builder.append(name);
-        builder.append(", state=");
-        builder.append(state);
-        builder.append(", timestamp=");
-        builder.append(timestamp);
-        builder.append("]");
-        return builder.toString();
-    }
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/JdbcPersistenceItemInfo.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/JdbcPersistenceItemInfo.java
deleted file mode 100644 (file)
index 0e91c66..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * 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.dto;
-
-import java.util.Date;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.persistence.PersistenceItemInfo;
-
-/**
- * Represents the item info for openHAB.
- *
- * @author Christoph Weitkamp - Initial contribution
- */
-@NonNullByDefault
-public class JdbcPersistenceItemInfo implements PersistenceItemInfo {
-
-    private final String name;
-    private final @Nullable Integer count;
-    private final @Nullable Date earliest;
-    private final @Nullable Date latest;
-
-    public JdbcPersistenceItemInfo(String name) {
-        this(name, null, null, null);
-    }
-
-    public JdbcPersistenceItemInfo(String name, @Nullable Integer count, @Nullable Date earliest,
-            @Nullable Date latest) {
-        this.name = name;
-        this.count = count;
-        this.earliest = earliest;
-        this.latest = latest;
-    }
-
-    @Override
-    public String getName() {
-        return name;
-    }
-
-    @Override
-    public @Nullable Integer getCount() {
-        return count;
-    }
-
-    @Override
-    public @Nullable Date getEarliest() {
-        return earliest;
-    }
-
-    @Override
-    public @Nullable Date getLatest() {
-        return latest;
-    }
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/ItemTableCheckEntry.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/ItemTableCheckEntry.java
new file mode 100644 (file)
index 0000000..707ae0e
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * 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 org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This class represents a checked item/table relation.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class ItemTableCheckEntry {
+    private String itemName;
+    private String tableName;
+    private ItemTableCheckEntryStatus status;
+
+    public ItemTableCheckEntry(String itemName, String tableName, ItemTableCheckEntryStatus status) {
+        this.itemName = itemName;
+        this.tableName = tableName;
+        this.status = status;
+    }
+
+    public String getItemName() {
+        return itemName;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public ItemTableCheckEntryStatus getStatus() {
+        return status;
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/ItemTableCheckEntryStatus.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/ItemTableCheckEntryStatus.java
new file mode 100644 (file)
index 0000000..ce70ed6
--- /dev/null
@@ -0,0 +1,69 @@
+/**
+ * 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 org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This class represents status for an {@link ItemTableCheckEntry}.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public enum ItemTableCheckEntryStatus {
+    /**
+     * Table is consistent.
+     */
+    VALID {
+        @Override
+        public String toString() {
+            return "Valid";
+        }
+    },
+    /**
+     * Table has no corresponding item.
+     */
+    ITEM_MISSING {
+        @Override
+        public String toString() {
+            return "Item missing";
+        }
+    },
+    /**
+     * Referenced table does not exist.
+     */
+    TABLE_MISSING {
+        @Override
+        public String toString() {
+            return "Table missing";
+        }
+    },
+    /**
+     * Referenced table does not exist nor has corresponding item.
+     */
+    ITEM_AND_TABLE_MISSING {
+        @Override
+        public String toString() {
+            return "Item and table missing";
+        }
+    },
+    /**
+     * Mapping for table does not exist in index.
+     */
+    ORPHAN_TABLE {
+        @Override
+        public String toString() {
+            return "Orphan table";
+        }
+    }
+}
index b67fb9a31a23893b21927fd6b6f395a8cbb557d7..5ce07056f3a76230ef4376c1e9f2a0980d6b43cf 100644 (file)
@@ -23,9 +23,9 @@ import java.util.regex.Pattern;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.persistence.jdbc.db.JdbcBaseDAO;
-import org.openhab.persistence.jdbc.utils.MovingAverage;
-import org.openhab.persistence.jdbc.utils.StringUtilsExt;
+import org.openhab.persistence.jdbc.internal.db.JdbcBaseDAO;
+import org.openhab.persistence.jdbc.internal.utils.MovingAverage;
+import org.openhab.persistence.jdbc.internal.utils.StringUtilsExt;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,7 +39,7 @@ public class JdbcConfiguration {
     private final Logger logger = LoggerFactory.getLogger(JdbcConfiguration.class);
 
     private static final Pattern EXTRACT_CONFIG_PATTERN = Pattern.compile("^(.*?)\\.([0-9.a-zA-Z]+)$");
-    private static final String DB_DAO_PACKAGE = "org.openhab.persistence.jdbc.db.Jdbc";
+    private static final String DB_DAO_PACKAGE = "org.openhab.persistence.jdbc.internal.db.Jdbc";
 
     private Map<Object, Object> configuration;
 
index cae0a5346dc4dd89b2d9de2d613bfe2cbf4acaed..27422eedbb30c8cd009f3e98a4b0a8db3240d59a 100644 (file)
@@ -25,16 +25,16 @@ import java.util.stream.Collectors;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.knowm.yank.Yank;
-import org.knowm.yank.exceptions.YankSQLException;
 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;
+import org.openhab.persistence.jdbc.internal.dto.ItemVO;
+import org.openhab.persistence.jdbc.internal.dto.ItemsVO;
+import org.openhab.persistence.jdbc.internal.dto.JdbcPersistenceItemInfo;
+import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -64,10 +64,10 @@ public class JdbcMapper {
         this.timeZoneProvider = timeZoneProvider;
     }
 
-    /*****************
+    /****************
      * MAPPER ITEMS *
-     *****************/
-    private boolean pingDB() {
+     ****************/
+    private boolean pingDB() throws JdbcSQLException {
         logger.debug("JDBC::pingDB");
         boolean ret = false;
         long timerStart = System.currentTimeMillis();
@@ -91,7 +91,7 @@ public class JdbcMapper {
         return ret;
     }
 
-    private boolean ifItemsTableExists() {
+    private boolean ifItemsTableExists() throws JdbcSQLException {
         logger.debug("JDBC::ifItemsTableExists");
         long timerStart = System.currentTimeMillis();
         boolean res = conf.getDBDAO().doIfTableExists(new ItemsVO());
@@ -99,7 +99,7 @@ public class JdbcMapper {
         return res;
     }
 
-    protected boolean ifTableExists(String tableName) {
+    protected boolean ifTableExists(String tableName) throws JdbcSQLException {
         logger.debug("JDBC::ifTableExists");
         long timerStart = System.currentTimeMillis();
         boolean res = conf.getDBDAO().doIfTableExists(tableName);
@@ -107,7 +107,7 @@ public class JdbcMapper {
         return res;
     }
 
-    private ItemsVO createNewEntryInItemsTable(ItemsVO vo) {
+    private ItemsVO createNewEntryInItemsTable(ItemsVO vo) throws JdbcSQLException {
         logger.debug("JDBC::createNewEntryInItemsTable");
         long timerStart = System.currentTimeMillis();
         Long i = conf.getDBDAO().doCreateNewEntryInItemsTable(vo);
@@ -116,7 +116,7 @@ public class JdbcMapper {
         return vo;
     }
 
-    private boolean createItemsTableIfNot(ItemsVO vo) {
+    private boolean createItemsTableIfNot(ItemsVO vo) throws JdbcSQLException {
         logger.debug("JDBC::createItemsTableIfNot");
         long timerStart = System.currentTimeMillis();
         conf.getDBDAO().doCreateItemsTableIfNot(vo);
@@ -124,7 +124,7 @@ public class JdbcMapper {
         return true;
     }
 
-    private boolean dropItemsTableIfExists(ItemsVO vo) {
+    private boolean dropItemsTableIfExists(ItemsVO vo) throws JdbcSQLException {
         logger.debug("JDBC::dropItemsTableIfExists");
         long timerStart = System.currentTimeMillis();
         conf.getDBDAO().doDropItemsTableIfExists(vo);
@@ -132,14 +132,14 @@ public class JdbcMapper {
         return true;
     }
 
-    protected void dropTable(String tableName) {
+    protected void dropTable(String tableName) throws JdbcSQLException {
         logger.debug("JDBC::dropTable");
         long timerStart = System.currentTimeMillis();
         conf.getDBDAO().doDropTable(tableName);
         logTime("doDropTable", timerStart, System.currentTimeMillis());
     }
 
-    protected ItemsVO deleteItemsEntry(ItemsVO vo) {
+    protected ItemsVO deleteItemsEntry(ItemsVO vo) throws JdbcSQLException {
         logger.debug("JDBC::deleteItemsEntry");
         long timerStart = System.currentTimeMillis();
         conf.getDBDAO().doDeleteItemsEntry(vo);
@@ -147,7 +147,7 @@ public class JdbcMapper {
         return vo;
     }
 
-    private List<ItemsVO> getItemIDTableNames() {
+    private List<ItemsVO> getItemIDTableNames() throws JdbcSQLException {
         logger.debug("JDBC::getItemIDTableNames");
         long timerStart = System.currentTimeMillis();
         List<ItemsVO> vo = conf.getDBDAO().doGetItemIDTableNames(new ItemsVO());
@@ -155,7 +155,7 @@ public class JdbcMapper {
         return vo;
     }
 
-    protected List<ItemsVO> getItemTables() {
+    protected List<ItemsVO> getItemTables() throws JdbcSQLException {
         logger.debug("JDBC::getItemTables");
         long timerStart = System.currentTimeMillis();
         ItemsVO vo = new ItemsVO();
@@ -168,14 +168,14 @@ public class JdbcMapper {
     /****************
      * MAPPERS ITEM *
      ****************/
-    private void updateItemTableNames(List<ItemVO> vol) {
+    private void updateItemTableNames(List<ItemVO> vol) throws JdbcSQLException {
         logger.debug("JDBC::updateItemTableNames");
         long timerStart = System.currentTimeMillis();
         conf.getDBDAO().doUpdateItemTableNames(vol);
         logTime("updateItemTableNames", timerStart, System.currentTimeMillis());
     }
 
-    private ItemVO createItemTable(ItemVO vo) {
+    private ItemVO createItemTable(ItemVO vo) throws JdbcSQLException {
         logger.debug("JDBC::createItemTable");
         long timerStart = System.currentTimeMillis();
         conf.getDBDAO().doCreateItemTable(vo);
@@ -183,7 +183,7 @@ public class JdbcMapper {
         return vo;
     }
 
-    protected Item storeItemValue(Item item, State itemState, @Nullable ZonedDateTime date) {
+    protected void storeItemValue(Item item, State itemState, @Nullable ZonedDateTime date) throws JdbcSQLException {
         logger.debug("JDBC::storeItemValue: item={} state={} date={}", item, itemState, date);
         String tableName = getTable(item);
         long timerStart = System.currentTimeMillis();
@@ -194,15 +194,14 @@ public class JdbcMapper {
         }
         logTime("storeItemValue", timerStart, System.currentTimeMillis());
         errCnt = 0;
-        return item;
     }
 
-    public long getRowCount(String tableName) {
+    public long getRowCount(String tableName) throws JdbcSQLException {
         return conf.getDBDAO().doGetRowCount(tableName);
     }
 
     protected List<HistoricItem> getHistItemFilterQuery(FilterCriteria filter, int numberDecimalcount, String table,
-            Item item) {
+            Item item) throws JdbcSQLException {
         logger.debug(
                 "JDBC::getHistItemFilterQuery filter='{}' numberDecimalcount='{}' table='{}' item='{}' itemName='{}'",
                 true, numberDecimalcount, table, item, item.getName());
@@ -214,7 +213,7 @@ public class JdbcMapper {
         return result;
     }
 
-    protected void deleteItemValues(FilterCriteria filter, String table) {
+    protected void deleteItemValues(FilterCriteria filter, String table) throws JdbcSQLException {
         logger.debug("JDBC::deleteItemValues filter='{}' table='{}' itemName='{}'", true, table, filter.getItemName());
         long timerStart = System.currentTimeMillis();
         conf.getDBDAO().doDeleteItemValues(filter, table, timeZoneProvider.getTimeZone());
@@ -276,7 +275,7 @@ public class JdbcMapper {
                 logger.debug("JDBC::checkDBAcessability, second try connection: {}", p);
                 return (p && !(conf.getErrReconnectThreshold() > 0 && errCnt <= conf.getErrReconnectThreshold()));
             }
-        } catch (YankSQLException e) {
+        } catch (JdbcSQLException e) {
             logger.warn("Unable to ping database", e);
             return false;
         }
@@ -285,7 +284,7 @@ public class JdbcMapper {
     /**************************
      * DATABASE TABLEHANDLING *
      **************************/
-    protected void checkDBSchema() {
+    protected void checkDBSchema() throws JdbcSQLException {
         if (!conf.getTableUseRealCaseSensitiveItemNames()) {
             createItemsTableIfNot(new ItemsVO());
         }
@@ -303,7 +302,7 @@ public class JdbcMapper {
         populateItemNameToTableNameMap();
     }
 
-    private void populateItemNameToTableNameMap() {
+    private void populateItemNameToTableNameMap() throws JdbcSQLException {
         itemNameToTableNameMap.clear();
         if (conf.getTableUseRealCaseSensitiveItemNames()) {
             for (String itemName : getItemTables().stream().map(t -> t.getTableName()).collect(Collectors.toList())) {
@@ -317,7 +316,7 @@ public class JdbcMapper {
         }
     }
 
-    protected String getTable(Item item) {
+    protected String getTable(Item item) throws JdbcSQLException {
         int itemId = 0;
         ItemsVO isvo;
         ItemVO ivo;
@@ -360,7 +359,7 @@ public class JdbcMapper {
         return tableName;
     }
 
-    private void formatTableNames() {
+    private void formatTableNames() throws JdbcSQLException {
         boolean tmpinit = initialized;
         if (tmpinit) {
             initialized = false;
index e0fbf11552b607518bb3c733cf99d5ec4c00656e..3e4783f32b9f8845c1b715ce371ae66b21f60541 100644 (file)
@@ -25,7 +25,6 @@ import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.knowm.yank.exceptions.YankSQLException;
 import org.openhab.core.config.core.ConfigurableService;
 import org.openhab.core.i18n.TimeZoneProvider;
 import org.openhab.core.items.GroupItem;
@@ -41,9 +40,8 @@ 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.openhab.persistence.jdbc.ItemTableCheckEntry;
-import org.openhab.persistence.jdbc.ItemTableCheckEntryStatus;
-import org.openhab.persistence.jdbc.dto.ItemsVO;
+import org.openhab.persistence.jdbc.internal.dto.ItemsVO;
+import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.service.component.annotations.Activate;
@@ -163,7 +161,7 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
                 logger.debug("JDBC: Stored item '{}' as '{}' in SQL database at {} in {} ms.", item.getName(), state,
                         new Date(), System.currentTimeMillis() - timerStart);
             }
-        } catch (YankSQLException e) {
+        } catch (JdbcSQLException e) {
             logger.warn("JDBC::store: Unable to store item", e);
         }
     }
@@ -230,7 +228,7 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
             // Success
             errCnt = 0;
             return items;
-        } catch (YankSQLException e) {
+        } catch (JdbcSQLException e) {
             logger.warn("JDBC::query: Unable to query item", e);
             return List.of();
         }
@@ -246,7 +244,7 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
                 checkDBSchema();
                 // connection has been established ... initialization completed!
                 initialized = true;
-            } catch (YankSQLException e) {
+            } catch (JdbcSQLException e) {
                 logger.error("Failed to check database schema", e);
                 initialized = false;
             }
@@ -291,7 +289,7 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
                         System.currentTimeMillis() - timerStart);
             }
             return true;
-        } catch (YankSQLException e) {
+        } catch (JdbcSQLException e) {
             logger.debug("JDBC::remove: Unable to remove values for item", e);
             return false;
         }
@@ -310,7 +308,7 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
      *
      * @return list of {@link ItemTableCheckEntry}
      */
-    public List<ItemTableCheckEntry> getCheckedEntries() {
+    public List<ItemTableCheckEntry> getCheckedEntries() throws JdbcSQLException {
         List<ItemTableCheckEntry> entries = new ArrayList<>();
 
         if (!checkDBAccessability()) {
@@ -362,8 +360,9 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
      * @param itemName Name of item to clean
      * @param force If true, non-empty tables will be dropped too
      * @return true if item was cleaned up
+     * @throws JdbcSQLException
      */
-    public boolean cleanupItem(String itemName, boolean force) {
+    public boolean cleanupItem(String itemName, boolean force) throws JdbcSQLException {
         String tableName = itemNameToTableNameMap.get(itemName);
         if (tableName == null) {
             return false;
@@ -378,12 +377,13 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
      *
      * @param entry
      * @return true if item was cleaned up
+     * @throws JdbcSQLException
      */
-    public boolean cleanupItem(ItemTableCheckEntry entry) {
+    public boolean cleanupItem(ItemTableCheckEntry entry) throws JdbcSQLException {
         return cleanupItem(entry, false);
     }
 
-    private boolean cleanupItem(ItemTableCheckEntry entry, boolean force) {
+    private boolean cleanupItem(ItemTableCheckEntry entry, boolean force) throws JdbcSQLException {
         if (!checkDBAccessability()) {
             logger.warn("JDBC::cleanupItem: database not connected");
             return false;
index 3fcf9f065fa1c45fca37735ce0622227f6eb779b..8d4e41500e0c587fe269cb94195473f6521a01c5 100644 (file)
@@ -21,7 +21,7 @@ import java.util.Objects;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.core.items.ItemUtil;
-import org.openhab.persistence.jdbc.dto.ItemVO;
+import org.openhab.persistence.jdbc.internal.dto.ItemVO;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/console/JdbcCommandExtension.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/console/JdbcCommandExtension.java
new file mode 100644 (file)
index 0000000..c4d1f1f
--- /dev/null
@@ -0,0 +1,206 @@
+/**
+ * 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.console;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.io.console.Console;
+import org.openhab.core.io.console.ConsoleCommandCompleter;
+import org.openhab.core.io.console.StringsCompleter;
+import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
+import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
+import org.openhab.core.persistence.PersistenceService;
+import org.openhab.core.persistence.PersistenceServiceRegistry;
+import org.openhab.persistence.jdbc.internal.ItemTableCheckEntry;
+import org.openhab.persistence.jdbc.internal.ItemTableCheckEntryStatus;
+import org.openhab.persistence.jdbc.internal.JdbcPersistenceService;
+import org.openhab.persistence.jdbc.internal.JdbcPersistenceServiceConstants;
+import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * The {@link JdbcCommandExtension} is responsible for handling console commands
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = ConsoleCommandExtension.class)
+public class JdbcCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
+
+    private static final String CMD_TABLES = "tables";
+    private static final String SUBCMD_TABLES_LIST = "list";
+    private static final String SUBCMD_TABLES_CLEAN = "clean";
+    private static final String PARAMETER_ALL = "all";
+    private static final String PARAMETER_FORCE = "force";
+    private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(List.of(CMD_TABLES), false);
+    private static final StringsCompleter SUBCMD_TABLES_COMPLETER = new StringsCompleter(
+            List.of(SUBCMD_TABLES_LIST, SUBCMD_TABLES_CLEAN), false);
+
+    private final PersistenceServiceRegistry persistenceServiceRegistry;
+
+    @Activate
+    public JdbcCommandExtension(final @Reference PersistenceServiceRegistry persistenceServiceRegistry) {
+        super(JdbcPersistenceServiceConstants.SERVICE_ID, "Interact with the JDBC persistence service.");
+        this.persistenceServiceRegistry = persistenceServiceRegistry;
+    }
+
+    @Override
+    public void execute(String[] args, Console console) {
+        if (args.length < 2 || args.length > 4 || !CMD_TABLES.equals(args[0])) {
+            printUsage(console);
+            return;
+        }
+        JdbcPersistenceService persistenceService = getPersistenceService();
+        if (persistenceService == null) {
+            return;
+        }
+        try {
+            if (!execute(persistenceService, args, console)) {
+                printUsage(console);
+                return;
+            }
+        } catch (JdbcSQLException e) {
+            console.println(e.toString());
+        }
+    }
+
+    private @Nullable JdbcPersistenceService getPersistenceService() {
+        for (PersistenceService persistenceService : persistenceServiceRegistry.getAll()) {
+            if (persistenceService instanceof JdbcPersistenceService) {
+                return (JdbcPersistenceService) persistenceService;
+            }
+        }
+        return null;
+    }
+
+    private boolean execute(JdbcPersistenceService persistenceService, String[] args, Console console)
+            throws JdbcSQLException {
+        if (SUBCMD_TABLES_LIST.equalsIgnoreCase(args[1])) {
+            listTables(persistenceService, console, args.length == 3 && PARAMETER_ALL.equalsIgnoreCase(args[2]));
+            return true;
+        } else if (SUBCMD_TABLES_CLEAN.equalsIgnoreCase(args[1])) {
+            if (args.length == 3) {
+                cleanupItem(persistenceService, console, args[2], false);
+                return true;
+            } else if (args.length == 4 && PARAMETER_FORCE.equalsIgnoreCase(args[3])) {
+                cleanupItem(persistenceService, console, args[2], true);
+                return true;
+            } else {
+                cleanupTables(persistenceService, console);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void listTables(JdbcPersistenceService persistenceService, Console console, Boolean all)
+            throws JdbcSQLException {
+        List<ItemTableCheckEntry> entries = persistenceService.getCheckedEntries();
+        if (!all) {
+            entries.removeIf(t -> t.getStatus() == ItemTableCheckEntryStatus.VALID);
+        }
+        entries.sort(Comparator.comparing(ItemTableCheckEntry::getTableName));
+        // FIXME: NoSuchElement when empty table - because of get()
+        int itemNameMaxLength = Math
+                .max(entries.stream().map(t -> t.getItemName().length()).max(Integer::compare).get(), 4);
+        int tableNameMaxLength = Math
+                .max(entries.stream().map(t -> t.getTableName().length()).max(Integer::compare).get(), 5);
+        int statusMaxLength = Stream.of(ItemTableCheckEntryStatus.values()).map(t -> t.toString().length())
+                .max(Integer::compare).get();
+        console.println(String.format(
+                "%1$-" + (tableNameMaxLength + 2) + "sRow Count  %2$-" + (itemNameMaxLength + 2) + "s%3$s", "Table",
+                "Item", "Status"));
+        console.println("-".repeat(tableNameMaxLength) + "  " + "---------  " + "-".repeat(itemNameMaxLength) + "  "
+                + "-".repeat(statusMaxLength));
+        for (ItemTableCheckEntry entry : entries) {
+            String tableName = entry.getTableName();
+            ItemTableCheckEntryStatus status = entry.getStatus();
+            long rowCount = status == ItemTableCheckEntryStatus.VALID
+                    || status == ItemTableCheckEntryStatus.ITEM_MISSING ? persistenceService.getRowCount(tableName) : 0;
+            console.println(String.format(
+                    "%1$-" + (tableNameMaxLength + 2) + "s%2$9d  %3$-" + (itemNameMaxLength + 2) + "s%4$s", tableName,
+                    rowCount, entry.getItemName(), status));
+        }
+    }
+
+    private void cleanupTables(JdbcPersistenceService persistenceService, Console console) throws JdbcSQLException {
+        console.println("Cleaning up all inconsistent items...");
+        List<ItemTableCheckEntry> entries = persistenceService.getCheckedEntries();
+        entries.removeIf(t -> t.getStatus() == ItemTableCheckEntryStatus.VALID || t.getItemName().isEmpty());
+        for (ItemTableCheckEntry entry : entries) {
+            console.print(entry.getItemName() + " -> ");
+            if (persistenceService.cleanupItem(entry)) {
+                console.println("done.");
+            } else {
+                console.println("skipped/failed.");
+            }
+        }
+    }
+
+    private void cleanupItem(JdbcPersistenceService persistenceService, Console console, String itemName, boolean force)
+            throws JdbcSQLException {
+        console.print("Cleaning up item " + itemName + "... ");
+        if (persistenceService.cleanupItem(itemName, force)) {
+            console.println("done.");
+        } else {
+            console.println("skipped/failed.");
+        }
+    }
+
+    @Override
+    public List<String> getUsages() {
+        return Arrays.asList(
+                buildCommandUsage(CMD_TABLES + " " + SUBCMD_TABLES_LIST + " [" + PARAMETER_ALL + "]",
+                        "list tables (all = include valid)"),
+                buildCommandUsage(
+                        CMD_TABLES + " " + SUBCMD_TABLES_CLEAN + " [<itemName>]" + " [" + PARAMETER_FORCE + "]",
+                        "clean inconsistent items (remove from index and drop tables)"));
+    }
+
+    @Override
+    public @Nullable ConsoleCommandCompleter getCompleter() {
+        return this;
+    }
+
+    @Override
+    public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
+        if (cursorArgumentIndex <= 0) {
+            return CMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
+        } else if (cursorArgumentIndex == 1) {
+            if (CMD_TABLES.equalsIgnoreCase(args[0])) {
+                return SUBCMD_TABLES_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
+            }
+        } else if (cursorArgumentIndex == 2) {
+            if (CMD_TABLES.equalsIgnoreCase(args[0])) {
+                if (SUBCMD_TABLES_CLEAN.equalsIgnoreCase(args[1])) {
+                    JdbcPersistenceService persistenceService = getPersistenceService();
+                    if (persistenceService != null) {
+                        return new StringsCompleter(persistenceService.getItemNames(), true).complete(args,
+                                cursorArgumentIndex, cursorPosition, candidates);
+                    }
+                } else if (SUBCMD_TABLES_LIST.equalsIgnoreCase(args[1])) {
+                    new StringsCompleter(List.of(PARAMETER_ALL), false).complete(args, cursorArgumentIndex,
+                            cursorPosition, candidates);
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcBaseDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcBaseDAO.java
new file mode 100644 (file)
index 0000000..b8a68a2
--- /dev/null
@@ -0,0 +1,754 @@
+/**
+ * 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.db;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.stream.Collectors;
+
+import javax.measure.Quantity;
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.knowm.yank.Yank;
+import org.knowm.yank.exceptions.YankSQLException;
+import org.openhab.core.items.GroupItem;
+import org.openhab.core.items.Item;
+import org.openhab.core.library.items.ColorItem;
+import org.openhab.core.library.items.ContactItem;
+import org.openhab.core.library.items.DateTimeItem;
+import org.openhab.core.library.items.DimmerItem;
+import org.openhab.core.library.items.ImageItem;
+import org.openhab.core.library.items.NumberItem;
+import org.openhab.core.library.items.PlayerItem;
+import org.openhab.core.library.items.RollershutterItem;
+import org.openhab.core.library.items.SwitchItem;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.HSBType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.RawType;
+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;
+import org.openhab.core.types.State;
+import org.openhab.core.types.TypeParser;
+import org.openhab.persistence.jdbc.internal.dto.ItemVO;
+import org.openhab.persistence.jdbc.internal.dto.ItemsVO;
+import org.openhab.persistence.jdbc.internal.dto.JdbcHistoricItem;
+import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
+import org.openhab.persistence.jdbc.internal.utils.DbMetaData;
+import org.openhab.persistence.jdbc.internal.utils.StringUtilsExt;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Default Database Configuration class.
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcBaseDAO {
+    private final Logger logger = LoggerFactory.getLogger(JdbcBaseDAO.class);
+
+    public final Properties databaseProps = new Properties();
+    protected String urlSuffix = "";
+    public final Map<String, String> sqlTypes = new HashMap<>();
+
+    // Get Database Meta data
+    protected @Nullable DbMetaData dbMeta;
+
+    protected String sqlPingDB = "SELECT 1";
+    protected String sqlGetDB = "SELECT DATABASE()";
+    protected String sqlIfTableExists = "SHOW TABLES LIKE '#searchTable#'";
+    protected String sqlCreateNewEntryInItemsTable = "INSERT INTO #itemsManageTable# (ItemName) VALUES ('#itemname#')";
+    protected String sqlCreateItemsTableIfNot = "CREATE TABLE IF NOT EXISTS #itemsManageTable# (ItemId INT NOT NULL AUTO_INCREMENT,#colname# #coltype# NOT NULL,PRIMARY KEY (ItemId))";
+    protected String sqlDropItemsTableIfExists = "DROP TABLE IF EXISTS #itemsManageTable#";
+    protected String sqlDropTable = "DROP TABLE #tableName#";
+    protected String sqlDeleteItemsEntry = "DELETE FROM #itemsManageTable# WHERE ItemName='#itemname#'";
+    protected String sqlGetItemIDTableNames = "SELECT ItemId, ItemName FROM #itemsManageTable#";
+    protected String sqlGetItemTables = "SELECT table_name FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_schema='#jdbcUriDatabaseName#' AND NOT table_name='#itemsManageTable#'";
+    protected String sqlCreateItemTable = "CREATE TABLE IF NOT EXISTS #tableName# (time #tablePrimaryKey# NOT NULL, value #dbType#, PRIMARY KEY(time))";
+    protected String sqlInsertItemValue = "INSERT INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, ? ) ON DUPLICATE KEY UPDATE VALUE= ?";
+    protected String sqlGetRowCount = "SELECT COUNT(*) FROM #tableName#";
+
+    /********
+     * INIT *
+     ********/
+    public JdbcBaseDAO() {
+        initSqlTypes();
+        initDbProps();
+    }
+
+    /**
+     * ## Get high precision by fractal seconds, examples ##
+     *
+     * mysql > 5.5 + mariadb > 5.2:
+     * DROP TABLE FractionalSeconds;
+     * CREATE TABLE FractionalSeconds (time TIMESTAMP(3), value TIMESTAMP(3));
+     * INSERT INTO FractionalSeconds (time, value) VALUES( NOW(3), '1999-01-09 20:11:11.126' );
+     * SELECT time FROM FractionalSeconds ORDER BY time DESC LIMIT 1;
+     *
+     * mysql <= 5.5 + mariadb <= 5.2: !!! NO high precision and fractal seconds !!!
+     * DROP TABLE FractionalSeconds;
+     * CREATE TABLE FractionalSeconds (time TIMESTAMP, value TIMESTAMP);
+     * INSERT INTO FractionalSeconds (time, value) VALUES( NOW(), '1999-01-09 20:11:11.126' );
+     * SELECT time FROM FractionalSeconds ORDER BY time DESC LIMIT 1;
+     *
+     * derby:
+     * DROP TABLE FractionalSeconds;
+     * CREATE TABLE FractionalSeconds (time TIMESTAMP, value TIMESTAMP);
+     * INSERT INTO FractionalSeconds (time, value) VALUES( CURRENT_TIMESTAMP, '1999-01-09 20:11:11.126' );
+     * SELECT time, value FROM FractionalSeconds;
+     *
+     * H2 + postgreSQL + hsqldb:
+     * DROP TABLE FractionalSeconds;
+     * CREATE TABLE FractionalSeconds (time TIMESTAMP, value TIMESTAMP);
+     * INSERT INTO FractionalSeconds (time, value) VALUES( NOW(), '1999-01-09 20:11:11.126' );
+     * SELECT time, value FROM FractionalSeconds;
+     *
+     * Sqlite:
+     * DROP TABLE FractionalSeconds;
+     * CREATE TABLE FractionalSeconds (time TIMESTAMP, value TIMESTAMP);
+     * INSERT INTO FractionalSeconds (time, value) VALUES( strftime('%Y-%m-%d %H:%M:%f' , 'now' , 'localtime'),
+     * '1999-01-09 20:11:11.124' );
+     * SELECT time FROM FractionalSeconds ORDER BY time DESC LIMIT 1;
+     *
+     */
+
+    /**
+     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
+     */
+    private void initSqlTypes() {
+        logger.debug("JDBC::initSqlTypes: Initialize the type array");
+        sqlTypes.put("CALLITEM", "VARCHAR(200)");
+        sqlTypes.put("COLORITEM", "VARCHAR(70)");
+        sqlTypes.put("CONTACTITEM", "VARCHAR(6)");
+        sqlTypes.put("DATETIMEITEM", "TIMESTAMP");
+        sqlTypes.put("DIMMERITEM", "TINYINT");
+        sqlTypes.put("IMAGEITEM", "VARCHAR(65500)");// jdbc max 21845
+        sqlTypes.put("LOCATIONITEM", "VARCHAR(50)");
+        sqlTypes.put("NUMBERITEM", "DOUBLE");
+        sqlTypes.put("PLAYERITEM", "VARCHAR(20)");
+        sqlTypes.put("ROLLERSHUTTERITEM", "TINYINT");
+        sqlTypes.put("STRINGITEM", "VARCHAR(65500)");// jdbc max 21845
+        sqlTypes.put("SWITCHITEM", "VARCHAR(6)");
+        sqlTypes.put("tablePrimaryKey", "TIMESTAMP");
+        sqlTypes.put("tablePrimaryValue", "NOW()");
+    }
+
+    /**
+     * INFO: https://github.com/brettwooldridge/HikariCP
+     *
+     * driverClassName (used with jdbcUrl):
+     * Derby: org.apache.derby.jdbc.EmbeddedDriver
+     * H2: org.h2.Driver
+     * HSQLDB: org.hsqldb.jdbcDriver
+     * Jaybird: org.firebirdsql.jdbc.FBDriver
+     * MariaDB: org.mariadb.jdbc.Driver
+     * MySQL: com.mysql.cj.jdbc.Driver
+     * MaxDB: com.sap.dbtech.jdbc.DriverSapDB
+     * PostgreSQL: org.postgresql.Driver
+     * SyBase: com.sybase.jdbc3.jdbc.SybDriver
+     * SqLite: org.sqlite.JDBC
+     *
+     * dataSourceClassName (for alternative Configuration):
+     * Derby: org.apache.derby.jdbc.ClientDataSource
+     * H2: org.h2.jdbcx.JdbcDataSource
+     * HSQLDB: org.hsqldb.jdbc.JDBCDataSource
+     * Jaybird: org.firebirdsql.pool.FBSimpleDataSource
+     * MariaDB, MySQL: org.mariadb.jdbc.MySQLDataSource
+     * MaxDB: com.sap.dbtech.jdbc.DriverSapDB
+     * PostgreSQL: org.postgresql.ds.PGSimpleDataSource
+     * SyBase: com.sybase.jdbc4.jdbc.SybDataSource
+     * SqLite: org.sqlite.SQLiteDataSource
+     *
+     * HikariPool - configuration Example:
+     * allowPoolSuspension.............false
+     * autoCommit......................true
+     * catalog.........................
+     * connectionInitSql...............
+     * connectionTestQuery.............
+     * connectionTimeout...............30000
+     * dataSource......................
+     * dataSourceClassName.............
+     * dataSourceJNDI..................
+     * dataSourceProperties............{password=<masked>}
+     * driverClassName.................
+     * healthCheckProperties...........{}
+     * healthCheckRegistry.............
+     * idleTimeout.....................600000
+     * initializationFailFast..........true
+     * isolateInternalQueries..........false
+     * jdbc4ConnectionTest.............false
+     * jdbcUrl.........................jdbc:mysql://192.168.0.1:3306/test
+     * leakDetectionThreshold..........0
+     * maxLifetime.....................1800000
+     * maximumPoolSize.................10
+     * metricRegistry..................
+     * metricsTrackerFactory...........
+     * minimumIdle.....................10
+     * password........................<masked>
+     * poolName........................HikariPool-0
+     * readOnly........................false
+     * registerMbeans..................false
+     * scheduledExecutorService........
+     * threadFactory...................
+     * transactionIsolation............
+     * username........................xxxx
+     * validationTimeout...............5000
+     */
+    private void initDbProps() {
+        // databaseProps.setProperty("dataSource.url", "jdbc:mysql://192.168.0.1:3306/test");
+        // databaseProps.setProperty("dataSource.user", "test");
+        // databaseProps.setProperty("dataSource.password", "test");
+
+        // Most relevant Performance values
+        // maximumPoolSize to 20, minimumIdle to 5, and idleTimeout to 2 minutes.
+        // databaseProps.setProperty("maximumPoolSize", ""+maximumPoolSize);
+        // databaseProps.setProperty("minimumIdle", ""+minimumIdle);
+        // databaseProps.setProperty("idleTimeout", ""+idleTimeout);
+        // databaseProps.setProperty("connectionTimeout",""+connectionTimeout);
+        // databaseProps.setProperty("idleTimeout", ""+idleTimeout);
+        // databaseProps.setProperty("maxLifetime", ""+maxLifetime);
+        // databaseProps.setProperty("validationTimeout",""+validationTimeout);
+    }
+
+    public void initAfterFirstDbConnection() {
+        logger.debug("JDBC::initAfterFirstDbConnection: Initializing step, after db is connected.");
+        // Initialize sqlTypes, depending on DB version for example
+        dbMeta = new DbMetaData();// get DB information
+    }
+
+    public Properties getConnectionProperties() {
+        return new Properties(this.databaseProps);
+    }
+
+    /**************
+     * ITEMS DAOs *
+     **************/
+    public @Nullable Integer doPingDB() throws JdbcSQLException {
+        try {
+            final @Nullable Integer result = Yank.queryScalar(sqlPingDB, Integer.class, null);
+            return result;
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    public @Nullable String doGetDB() throws JdbcSQLException {
+        try {
+            final @Nullable String result = Yank.queryScalar(sqlGetDB, String.class, null);
+            return result;
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    public boolean doIfTableExists(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlIfTableExists, new String[] { "#searchTable#" },
+                new String[] { vo.getItemsManageTable() });
+        logger.debug("JDBC::doIfTableExists sql={}", sql);
+        try {
+            final @Nullable String result = Yank.queryScalar(sql, String.class, null);
+            return Objects.nonNull(result);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    public boolean doIfTableExists(String tableName) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlIfTableExists, new String[] { "#searchTable#" },
+                new String[] { tableName });
+        logger.debug("JDBC::doIfTableExists sql={}", sql);
+        try {
+            final @Nullable String result = Yank.queryScalar(sql, String.class, null);
+            return Objects.nonNull(result);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    public Long doCreateNewEntryInItemsTable(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
+                new String[] { "#itemsManageTable#", "#itemname#" },
+                new String[] { vo.getItemsManageTable(), vo.getItemName() });
+        logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
+        try {
+            return Yank.insert(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
+                new String[] { "#itemsManageTable#", "#colname#", "#coltype#" },
+                new String[] { vo.getItemsManageTable(), vo.getColname(), vo.getColtype() });
+        logger.debug("JDBC::doCreateItemsTableIfNot sql={}", sql);
+        try {
+            Yank.execute(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+        return vo;
+    }
+
+    public ItemsVO doDropItemsTableIfExists(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlDropItemsTableIfExists, new String[] { "#itemsManageTable#" },
+                new String[] { vo.getItemsManageTable() });
+        logger.debug("JDBC::doDropItemsTableIfExists sql={}", sql);
+        try {
+            Yank.execute(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+        return vo;
+    }
+
+    public void doDropTable(String tableName) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlDropTable, new String[] { "#tableName#" },
+                new String[] { tableName });
+        logger.debug("JDBC::doDropTable sql={}", sql);
+        try {
+            Yank.execute(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    public void doDeleteItemsEntry(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlDeleteItemsEntry,
+                new String[] { "#itemsManageTable#", "#itemname#" },
+                new String[] { vo.getItemsManageTable(), vo.getItemName() });
+        logger.debug("JDBC::doDeleteItemsEntry sql={}", sql);
+        try {
+            Yank.execute(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    public List<ItemsVO> doGetItemIDTableNames(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlGetItemIDTableNames, new String[] { "#itemsManageTable#" },
+                new String[] { vo.getItemsManageTable() });
+        logger.debug("JDBC::doGetItemIDTableNames sql={}", sql);
+        try {
+            return Yank.queryBeanList(sql, ItemsVO.class, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    public List<ItemsVO> doGetItemTables(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlGetItemTables,
+                new String[] { "#jdbcUriDatabaseName#", "#itemsManageTable#" },
+                new String[] { vo.getJdbcUriDatabaseName(), vo.getItemsManageTable() });
+        logger.debug("JDBC::doGetItemTables sql={}", sql);
+        try {
+            return Yank.queryBeanList(sql, ItemsVO.class, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    /*************
+     * ITEM DAOs *
+     *************/
+    public void doUpdateItemTableNames(List<ItemVO> vol) throws JdbcSQLException {
+        logger.debug("JDBC::doUpdateItemTableNames vol.size = {}", vol.size());
+        for (ItemVO itemTable : vol) {
+            String sql = updateItemTableNamesProvider(itemTable);
+            try {
+                Yank.execute(sql, null);
+            } catch (YankSQLException e) {
+                throw new JdbcSQLException(e);
+            }
+        }
+    }
+
+    public void doCreateItemTable(ItemVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemTable,
+                new String[] { "#tableName#", "#dbType#", "#tablePrimaryKey#" },
+                new String[] { vo.getTableName(), vo.getDbType(), sqlTypes.get("tablePrimaryKey") });
+        logger.debug("JDBC::doCreateItemTable sql={}", sql);
+        try {
+            Yank.execute(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo) throws JdbcSQLException {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
+        String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
+                new String[] { "#tableName#", "#tablePrimaryValue#" },
+                new String[] { storedVO.getTableName(), sqlTypes.get("tablePrimaryValue") });
+        Object[] params = { storedVO.getValue(), storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
+        try {
+            Yank.execute(sql, params);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo, ZonedDateTime date) throws JdbcSQLException {
+        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 = { timestamp, storedVO.getValue(), storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} timestamp={} value='{}'", sql, timestamp, storedVO.getValue());
+        try {
+            Yank.execute(sql, params);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    public List<HistoricItem> doGetHistItemFilterQuery(Item item, FilterCriteria filter, int numberDecimalcount,
+            String table, String name, ZoneId timeZone) throws JdbcSQLException {
+        String sql = histItemFilterQueryProvider(filter, numberDecimalcount, table, name, timeZone);
+        logger.debug("JDBC::doGetHistItemFilterQuery sql={}", sql);
+        List<Object[]> m;
+        try {
+            m = Yank.queryObjectArrays(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+        if (m == null) {
+            logger.debug("JDBC::doGetHistItemFilterQuery Query failed. Returning an empty list.");
+            return List.of();
+        }
+        // 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, objectAsState(item, unit, o[1]), objectAsZonedDateTime(o[0])))
+                .collect(Collectors.<HistoricItem> toList());
+    }
+
+    public void doDeleteItemValues(FilterCriteria filter, String table, ZoneId timeZone) throws JdbcSQLException {
+        String sql = histItemFilterDeleteProvider(filter, table, timeZone);
+        logger.debug("JDBC::doDeleteItemValues sql={}", sql);
+        try {
+            Yank.execute(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    public long doGetRowCount(String tableName) throws JdbcSQLException {
+        final String sql = StringUtilsExt.replaceArrayMerge(sqlGetRowCount, new String[] { "#tableName#" },
+                new String[] { tableName });
+        logger.debug("JDBC::doGetRowCount sql={}", sql);
+        try {
+            final @Nullable Long result = Yank.queryScalar(sql, Long.class, null);
+            return Objects.requireNonNullElse(result, 0L);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    /*************
+     * Providers *
+     *************/
+    static final DateTimeFormatter JDBC_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    protected String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
+            String simpleName, ZoneId timeZone) {
+        logger.debug(
+                "JDBC::getHistItemFilterQueryProvider filter = {}, numberDecimalcount = {}, table = {}, simpleName = {}",
+                filter, numberDecimalcount, table, simpleName);
+
+        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
+        // rounding HALF UP
+        String queryString = "NUMBERITEM".equalsIgnoreCase(simpleName) && numberDecimalcount > -1
+                ? "SELECT time, ROUND(value," + numberDecimalcount + ") FROM " + table
+                : "SELECT time, value FROM " + table;
+        if (!filterString.isEmpty()) {
+            queryString += filterString;
+        }
+        logger.debug("JDBC::query queryString = {}", queryString);
+        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 = filterString.isEmpty() ? "TRUNCATE TABLE " + table
+                : "DELETE FROM " + table + 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(ItemVO itemTable) {
+        String queryString = "ALTER TABLE " + itemTable.getTableName() + " RENAME TO " + itemTable.getNewTableName();
+        logger.debug("JDBC::query queryString = {}", queryString);
+        return queryString;
+    }
+
+    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(), itemState);
+
+        // insertItemValue
+        logger.debug("JDBC::storeItemValueProvider: itemState: '{}'", itemState);
+        /*
+         * !!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(itemState.toString());
+                break;
+            case "NUMBERITEM":
+                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<?>) itemState).toUnit(unit);
+                        if (convertedState == null) {
+                            logger.warn(
+                                    "JDBC::storeItemValueProvider: Failed to convert state '{}' to unit '{}'. Please check your item definition for correctness.",
+                                    itemState, unit);
+                            convertedState = itemState;
+                        }
+                    }
+                }
+                String it = getSqlTypes().get(itemType);
+                if (it == null) {
+                    logger.warn("JDBC::storeItemValueProvider: No SQL type defined for item type {}", itemType);
+                } else 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: itemState: '{}'", convertedState);
+                    vo.setValue(convertedState.toString());
+                }
+                break;
+            case "ROLLERSHUTTERITEM":
+            case "DIMMERITEM":
+                vo.setValueTypes(getSqlTypes().get(itemType), java.lang.Integer.class);
+                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) itemState).getZonedDateTime().toInstant().toEpochMilli());
+                logger.debug("JDBC::storeItemValueProvider: DateTimeItem: '{}'", d);
+                vo.setValue(d);
+                break;
+            case "IMAGEITEM":
+                vo.setValueTypes(getSqlTypes().get(itemType), java.lang.String.class);
+                String encodedString = item.getState().toFullString();
+                logger.debug("JDBC::storeItemValueProvider: ImageItem: '{}'", encodedString);
+                vo.setValue(encodedString);
+                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: itemState: '{}'", itemState);
+                vo.setValue(itemState.toString());
+                break;
+        }
+        return vo;
+    }
+
+    /*****************
+     * H E L P E R S *
+     *****************/
+    protected State objectAsState(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 == null) {
+                throw new UnsupportedOperationException("No SQL type defined for item type NUMBERITEM");
+            }
+            if (it.toUpperCase().contains("DOUBLE")) {
+                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 unit == null ? new DecimalType((BigDecimal) v)
+                        : QuantityType.valueOf(((BigDecimal) v).doubleValue(), unit);
+            } else if (it.toUpperCase().contains("INT")) {
+                return unit == null ? new DecimalType(objectAsInteger(v))
+                        : QuantityType.valueOf(((Integer) v).doubleValue(), unit);
+            }
+            return unit == null ? DecimalType.valueOf(objectAsString(v)) : QuantityType.valueOf(objectAsString(v));
+        } else if (item instanceof DateTimeItem) {
+            return new DateTimeType(objectAsZonedDateTime(v));
+        } else if (item instanceof ColorItem) {
+            return HSBType.valueOf(objectAsString(v));
+        } else if (item instanceof DimmerItem || item instanceof RollershutterItem) {
+            return new PercentType(objectAsInteger(v));
+        } else if (item instanceof ImageItem) {
+            return RawType.valueOf(objectAsString(v));
+        } else if (item instanceof ContactItem || item instanceof PlayerItem || item instanceof SwitchItem) {
+            State state = TypeParser.parseState(item.getAcceptedDataTypes(), ((String) v).toString().trim());
+            if (state == null) {
+                throw new UnsupportedOperationException("Unable to parse state for item " + item.toString());
+            }
+            return state;
+        } else {
+            State state = TypeParser.parseState(item.getAcceptedDataTypes(), ((String) v).toString());
+            if (state == null) {
+                throw new UnsupportedOperationException("Unable to parse state for item " + item.toString());
+            }
+            return state;
+        }
+    }
+
+    protected ZonedDateTime objectAsZonedDateTime(Object v) {
+        if (v instanceof Long) {
+            return ZonedDateTime.ofInstant(Instant.ofEpochMilli(((Number) v).longValue()), ZoneId.systemDefault());
+        } else if (v instanceof java.sql.Date) {
+            return ZonedDateTime.ofInstant(Instant.ofEpochMilli(((java.sql.Date) v).getTime()), ZoneId.systemDefault());
+        } else if (v instanceof LocalDateTime) {
+            return ((LocalDateTime) v).atZone(ZoneId.systemDefault());
+        } else if (v instanceof Instant) {
+            return ((Instant) v).atZone(ZoneId.systemDefault());
+        } else if (v instanceof java.sql.Timestamp) {
+            return ((java.sql.Timestamp) v).toInstant().atZone(ZoneId.systemDefault());
+        } else if (v instanceof java.lang.String) {
+            return ZonedDateTime.ofInstant(java.sql.Timestamp.valueOf(v.toString()).toInstant(),
+                    ZoneId.systemDefault());
+        }
+        throw new UnsupportedOperationException("Date of type " + v.getClass().getName() + " is not supported");
+    }
+
+    protected Integer objectAsInteger(Object v) {
+        if (v instanceof Byte) {
+            return ((Byte) v).intValue();
+        }
+        return ((Integer) v).intValue();
+    }
+
+    protected String objectAsString(Object v) {
+        if (v instanceof byte[]) {
+            return new String((byte[]) v);
+        }
+        return ((String) v).toString();
+    }
+
+    public String getItemType(Item i) {
+        Item item = i;
+        String def = "STRINGITEM";
+        if (i instanceof GroupItem) {
+            item = ((GroupItem) i).getBaseItem();
+            if (item == null) {
+                // 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());
+                Iterator<Item> iterator = ((GroupItem) i).getMembers().iterator();
+                if (!iterator.hasNext()) {
+                    logger.debug(
+                            "JDBC::getItemType: No Child-Members of GroupItem {}, use ItemType for STRINGITEM as Fallback",
+                            i.getName());
+                    return def;
+                }
+                item = iterator.next();
+            }
+        }
+        String itemType = item.getClass().getSimpleName().toUpperCase();
+        logger.debug("JDBC::getItemType: Try to use ItemType {} for Item {}", itemType, i.getName());
+        if (sqlTypes.get(itemType) == null) {
+            logger.warn(
+                    "JDBC::getItemType: No sqlType found for ItemType {}, use ItemType for STRINGITEM as Fallback for {}",
+                    itemType, i.getName());
+            return def;
+        }
+        return itemType;
+    }
+
+    /******************************
+     * public Getters and Setters *
+     ******************************/
+    public Map<String, String> getSqlTypes() {
+        return sqlTypes;
+    }
+
+    public String getDataType(Item item) {
+        String dataType = sqlTypes.get(getItemType(item));
+        if (dataType == null) {
+            throw new UnsupportedOperationException("No data type found for " + getItemType(item));
+        }
+        return dataType;
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcDerbyDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcDerbyDAO.java
new file mode 100644 (file)
index 0000000..818fca9
--- /dev/null
@@ -0,0 +1,282 @@
+/**
+ * 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.db;
+
+import java.time.ZoneId;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import javax.measure.Quantity;
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.knowm.yank.Yank;
+import org.knowm.yank.exceptions.YankSQLException;
+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;
+import org.openhab.core.types.State;
+import org.openhab.persistence.jdbc.internal.dto.ItemVO;
+import org.openhab.persistence.jdbc.internal.dto.ItemsVO;
+import org.openhab.persistence.jdbc.internal.dto.JdbcHistoricItem;
+import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
+import org.openhab.persistence.jdbc.internal.utils.StringUtilsExt;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extended Database Configuration class. Class represents
+ * the extended database-specific configuration. Overrides and supplements the
+ * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcDerbyDAO extends JdbcBaseDAO {
+    private static final String DRIVER_CLASS_NAME = org.apache.derby.jdbc.EmbeddedDriver.class.getName();
+    @SuppressWarnings("unused")
+    private static final String DATA_SOURCE_CLASS_NAME = org.apache.derby.jdbc.EmbeddedDataSource.class.getName();
+
+    private final Logger logger = LoggerFactory.getLogger(JdbcDerbyDAO.class);
+
+    /********
+     * INIT *
+     ********/
+    public JdbcDerbyDAO() {
+        initSqlTypes();
+        initDbProps();
+        initSqlQueries();
+    }
+
+    private void initSqlQueries() {
+        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
+        sqlPingDB = "values 1";
+        sqlGetDB = "VALUES SYSCS_UTIL.SYSCS_GET_DATABASE_PROPERTY( 'DataDictionaryVersion' )"; // returns version
+        sqlIfTableExists = "SELECT * FROM SYS.SYSTABLES WHERE TABLENAME='#searchTable#'";
+        sqlCreateItemsTableIfNot = "CREATE TABLE #itemsManageTable# ( ItemId INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1), #colname# #coltype# NOT NULL)";
+        sqlCreateItemTable = "CREATE TABLE #tableName# (time #tablePrimaryKey# NOT NULL, value #dbType#, PRIMARY KEY(time))";
+        // Prevent error against duplicate time value (seldom): No powerful Merge found:
+        // http://www.codeproject.com/Questions/162627/how-to-insert-new-record-in-my-table-if-not-exists
+        sqlInsertItemValue = "INSERT INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, CAST( ? as #dbType#) )";
+    }
+
+    private void initSqlTypes() {
+        sqlTypes.put("DATETIMEITEM", "TIMESTAMP");
+        sqlTypes.put("DIMMERITEM", "SMALLINT");
+        sqlTypes.put("IMAGEITEM", "VARCHAR(32000)");
+        sqlTypes.put("ROLLERSHUTTERITEM", "SMALLINT");
+        sqlTypes.put("STRINGITEM", "VARCHAR(32000)");
+        sqlTypes.put("tablePrimaryValue", "CURRENT_TIMESTAMP");
+        logger.debug("JDBC::initSqlTypes: Initialized the type array sqlTypes={}", sqlTypes.values());
+    }
+
+    /**
+     * INFO: https://github.com/brettwooldridge/HikariCP
+     */
+    private void initDbProps() {
+        // Properties for HikariCP
+        // Use driverClassName
+        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
+        // OR dataSourceClassName
+        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
+        databaseProps.setProperty("maximumPoolSize", "1");
+        databaseProps.setProperty("minimumIdle", "1");
+    }
+
+    @Override
+    public void initAfterFirstDbConnection() {
+        logger.debug("JDBC::initAfterFirstDbConnection: Initializing step, after db is connected.");
+        // Initialize sqlTypes, depending on DB version for example
+        // derby does not like this... dbMeta = new DbMetaData();// get DB information
+    }
+
+    /**************
+     * ITEMS DAOs *
+     **************/
+    @Override
+    public @Nullable Integer doPingDB() throws JdbcSQLException {
+        try {
+            return Yank.queryScalar(sqlPingDB, Integer.class, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    @Override
+    public boolean doIfTableExists(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlIfTableExists, new String[] { "#searchTable#" },
+                new String[] { vo.getItemsManageTable().toUpperCase() });
+        logger.debug("JDBC::doIfTableExists sql={}", sql);
+        try {
+            final @Nullable String result = Yank.queryScalar(sql, String.class, null);
+            return Objects.nonNull(result);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    @Override
+    public Long doCreateNewEntryInItemsTable(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
+                new String[] { "#itemsManageTable#", "#itemname#" },
+                new String[] { vo.getItemsManageTable().toUpperCase(), vo.getItemName() });
+        logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
+        try {
+            return Yank.insert(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    @Override
+    public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) throws JdbcSQLException {
+        boolean tableExists = doIfTableExists(vo);
+        if (!tableExists) {
+            String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
+                    new String[] { "#itemsManageTable#", "#colname#", "#coltype#" },
+                    new String[] { vo.getItemsManageTable().toUpperCase(), vo.getColname(), vo.getColtype() });
+            logger.debug("JDBC::doCreateItemsTableIfNot tableExists={} therefore sql={}", tableExists, sql);
+            try {
+                Yank.execute(sql, null);
+            } catch (YankSQLException e) {
+                throw new JdbcSQLException(e);
+            }
+        } else {
+            logger.debug("JDBC::doCreateItemsTableIfNot tableExists={}, did not CREATE TABLE", tableExists);
+        }
+        return vo;
+    }
+
+    /*************
+     * ITEM DAOs *
+     *************/
+    @Override
+    public void doCreateItemTable(ItemVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemTable,
+                new String[] { "#tableName#", "#dbType#", "#tablePrimaryKey#" },
+                new String[] { vo.getTableName(), vo.getDbType(), sqlTypes.get("tablePrimaryKey") });
+        try {
+            Yank.execute(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    @Override
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo) throws JdbcSQLException {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
+        String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
+                new String[] { "#tableName#", "#dbType#", "#tablePrimaryValue#" },
+                new String[] { storedVO.getTableName().toUpperCase(), storedVO.getDbType(),
+                        sqlTypes.get("tablePrimaryValue") });
+        Object[] params = { storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
+        try {
+            Yank.execute(sql, params);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    @Override
+    public List<HistoricItem> doGetHistItemFilterQuery(Item item, FilterCriteria filter, int numberDecimalcount,
+            String table, String name, ZoneId timeZone) throws JdbcSQLException {
+        String sql = histItemFilterQueryProvider(filter, numberDecimalcount, table, name, timeZone);
+        List<Object[]> m;
+        try {
+            m = Yank.queryObjectArrays(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+        logger.debug("JDBC::doGetHistItemFilterQuery got Array length={}", m.size());
+        // 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, objectAsState(item, unit, o[1]), objectAsZonedDateTime(o[0]));
+        }).collect(Collectors.<HistoricItem> toList());
+    }
+
+    /****************************
+     * SQL generation Providers *
+     ****************************/
+
+    @Override
+    protected String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
+            String simpleName, ZoneId timeZone) {
+        logger.debug(
+                "JDBC::getHistItemFilterQueryProvider filter = {}, numberDecimalcount = {}, table = {}, simpleName = {}",
+                StringUtilsExt.filterToString(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) {
+            // TODO: TESTING!!!
+            // filterString += " LIMIT " + filter.getPageNumber() *
+            // filter.getPageSize() + "," + filter.getPageSize();
+            // SELECT time, value FROM ohscriptfiles_sw_ace_paths_0001 ORDER BY
+            // time DESC OFFSET 1 ROWS FETCH NEXT 0 ROWS ONLY
+            // filterString += " OFFSET " + filter.getPageSize() +" ROWS FETCH
+            // FIRST||NEXT " + filter.getPageNumber() * filter.getPageSize() + "
+            // ROWS ONLY";
+            filterString += " OFFSET " + filter.getPageSize() + " ROWS FETCH FIRST "
+                    + (filter.getPageNumber() * filter.getPageSize() + 1) + " ROWS ONLY";
+        }
+
+        // http://www.seemoredata.com/en/showthread.php?132-Round-function-in-Apache-Derby
+        // simulated round function in Derby: CAST(value 0.0005 AS DECIMAL(15,3))
+        // simulated round function in Derby: "CAST(value 0.0005 AS DECIMAL(15,"+numberDecimalcount+"))"
+
+        String queryString = "SELECT time,";
+        if ("NUMBERITEM".equalsIgnoreCase(simpleName) && numberDecimalcount > -1) {
+            // rounding HALF UP
+            queryString += "CAST(value 0.";
+            for (int i = 0; i < numberDecimalcount; i++) {
+                queryString += "0";
+            }
+            queryString += "5 AS DECIMAL(31," + numberDecimalcount + "))"; // 31 is DECIMAL max precision
+                                                                           // https://db.apache.org/derby/docs/10.0/manuals/develop/develop151.html
+        } else {
+            queryString += " value FROM " + table.toUpperCase();
+        }
+
+        if (!filterString.isEmpty()) {
+            queryString += filterString;
+        }
+        logger.debug("JDBC::query queryString = {}", queryString);
+        return queryString;
+    }
+
+    /*****************
+     * H E L P E R S *
+     *****************/
+
+    /******************************
+     * public Getters and Setters *
+     ******************************/
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcH2DAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcH2DAO.java
new file mode 100644 (file)
index 0000000..2bbdcf7
--- /dev/null
@@ -0,0 +1,107 @@
+/**
+ * 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.db;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.knowm.yank.Yank;
+import org.knowm.yank.exceptions.YankSQLException;
+import org.openhab.core.items.Item;
+import org.openhab.core.types.State;
+import org.openhab.persistence.jdbc.internal.dto.ItemVO;
+import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
+import org.openhab.persistence.jdbc.internal.utils.StringUtilsExt;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extended Database Configuration class. Class represents
+ * the extended database-specific configuration. Overrides and supplements the
+ * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcH2DAO extends JdbcBaseDAO {
+    private static final String DRIVER_CLASS_NAME = org.h2.Driver.class.getName();
+    @SuppressWarnings("unused")
+    private static final String DATA_SOURCE_CLASS_NAME = org.h2.jdbcx.JdbcDataSource.class.getName();
+
+    private final Logger logger = LoggerFactory.getLogger(JdbcH2DAO.class);
+
+    /********
+     * INIT *
+     ********/
+    public JdbcH2DAO() {
+        initSqlQueries();
+        initSqlTypes();
+        initDbProps();
+    }
+
+    private void initSqlQueries() {
+        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
+        sqlIfTableExists = "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='#searchTable#'";
+        // SQL_INSERT_ITEM_VALUE = "INSERT INTO #tableName# (TIME, VALUE) VALUES( NOW(), CAST( ? as #dbType#) )";
+        // http://stackoverflow.com/questions/19768051/h2-sql-database-insert-if-the-record-does-not-exist
+        sqlInsertItemValue = "MERGE INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, CAST( ? as #dbType#) )";
+    }
+
+    /**
+     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
+     */
+    private void initSqlTypes() {
+    }
+
+    /**
+     * INFO: https://github.com/brettwooldridge/HikariCP
+     */
+    private void initDbProps() {
+        // Properties for HikariCP
+        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
+        // driverClassName OR BETTER USE dataSourceClassName
+        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
+    }
+
+    /**************
+     * ITEMS DAOs *
+     **************/
+
+    /*************
+     * ITEM DAOs *
+     *************/
+    @Override
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo) throws JdbcSQLException {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
+        String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
+                new String[] { "#tableName#", "#dbType#", "#tablePrimaryValue#" },
+                new String[] { storedVO.getTableName(), storedVO.getDbType(), sqlTypes.get("tablePrimaryValue") });
+        Object[] params = { storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
+        try {
+            Yank.execute(sql, params);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    /****************************
+     * SQL generation Providers *
+     ****************************/
+
+    /*****************
+     * H E L P E R S *
+     *****************/
+
+    /******************************
+     * public Getters and Setters *
+     ******************************/
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcHsqldbDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcHsqldbDAO.java
new file mode 100644 (file)
index 0000000..3516058
--- /dev/null
@@ -0,0 +1,153 @@
+/**
+ * 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.db;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.knowm.yank.Yank;
+import org.knowm.yank.exceptions.YankSQLException;
+import org.openhab.core.items.Item;
+import org.openhab.core.types.State;
+import org.openhab.persistence.jdbc.internal.dto.ItemVO;
+import org.openhab.persistence.jdbc.internal.dto.ItemsVO;
+import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
+import org.openhab.persistence.jdbc.internal.utils.StringUtilsExt;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extended Database Configuration class. Class represents
+ * the extended database-specific configuration. Overrides and supplements the
+ * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcHsqldbDAO extends JdbcBaseDAO {
+    private static final String DRIVER_CLASS_NAME = org.hsqldb.jdbcDriver.class.getName();
+    @SuppressWarnings("unused")
+    private static final String DATA_SOURCE_CLASS_NAME = org.hsqldb.jdbc.JDBCDataSource.class.getName();
+
+    private final Logger logger = LoggerFactory.getLogger(JdbcHsqldbDAO.class);
+
+    /********
+     * INIT *
+     ********/
+    public JdbcHsqldbDAO() {
+        initSqlQueries();
+        initSqlTypes();
+        initDbProps();
+    }
+
+    private void initSqlQueries() {
+        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
+        // http://hsqldb.org/doc/guide/builtinfunctions-chapt.html
+        sqlPingDB = "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
+        sqlGetDB = "SELECT DATABASE () FROM INFORMATION_SCHEMA.SYSTEM_USERS";
+        sqlIfTableExists = "SELECT * FROM INFORMATION_SCHEMA.SYSTEM_TABLES WHERE TABLE_NAME='#searchTable#'";
+        sqlCreateItemsTableIfNot = "CREATE TABLE IF NOT EXISTS #itemsManageTable# ( ItemId INT GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) NOT NULL, #colname# #coltype# NOT NULL)";
+        sqlCreateNewEntryInItemsTable = "INSERT INTO #itemsManageTable# (ItemName) VALUES ('#itemname#')";
+        // Prevent error against duplicate time value
+        // http://hsqldb.org/doc/guide/dataaccess-chapt.html#dac_merge_statement
+        // SQL_INSERT_ITEM_VALUE = "INSERT INTO #tableName# (TIME, VALUE) VALUES( NOW(), CAST( ? as #dbType#) )";
+        sqlInsertItemValue = "MERGE INTO #tableName# "
+                + "USING (VALUES #tablePrimaryValue#, CAST( ? as #dbType#)) temp (TIME, VALUE) ON (#tableName#.TIME=temp.TIME) "
+                + "WHEN NOT MATCHED THEN INSERT (TIME, VALUE) VALUES (temp.TIME, temp.VALUE)";
+    }
+
+    /**
+     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
+     */
+    private void initSqlTypes() {
+    }
+
+    /**
+     * INFO: https://github.com/brettwooldridge/HikariCP
+     */
+    private void initDbProps() {
+        // Properties for HikariCP
+        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
+        // driverClassName OR BETTER USE dataSourceClassName
+        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
+    }
+
+    /**************
+     * ITEMS DAOs *
+     **************/
+    @Override
+    public @Nullable Integer doPingDB() throws JdbcSQLException {
+        try {
+            return Yank.queryScalar(sqlPingDB, Integer.class, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    @Override
+    public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
+                new String[] { "#itemsManageTable#", "#colname#", "#coltype#", "#itemsManageTable#" },
+                new String[] { vo.getItemsManageTable(), vo.getColname(), vo.getColtype(), vo.getItemsManageTable() });
+        logger.debug("JDBC::doCreateItemsTableIfNot sql={}", sql);
+        try {
+            Yank.execute(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+        return vo;
+    }
+
+    @Override
+    public Long doCreateNewEntryInItemsTable(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
+                new String[] { "#itemsManageTable#", "#itemname#" },
+                new String[] { vo.getItemsManageTable(), vo.getItemName() });
+        logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
+        try {
+            return Yank.insert(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    /*************
+     * ITEM DAOs *
+     *************/
+    @Override
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo) throws JdbcSQLException {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
+        String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
+                new String[] { "#tableName#", "#dbType#", "#tableName#", "#tablePrimaryValue#" },
+                new String[] { storedVO.getTableName(), storedVO.getDbType(), storedVO.getTableName(),
+                        sqlTypes.get("tablePrimaryValue") });
+        Object[] params = { storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
+        try {
+            Yank.execute(sql, params);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    /****************************
+     * SQL generation Providers *
+     ****************************/
+
+    /*****************
+     * H E L P E R S *
+     *****************/
+
+    /******************************
+     * public Getters and Setters *
+     ******************************/
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcMariadbDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcMariadbDAO.java
new file mode 100644 (file)
index 0000000..f793270
--- /dev/null
@@ -0,0 +1,124 @@
+/**
+ * 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.db;
+
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.knowm.yank.Yank;
+import org.knowm.yank.exceptions.YankSQLException;
+import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
+import org.openhab.persistence.jdbc.internal.utils.DbMetaData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extended Database Configuration class. Class represents
+ * the extended database-specific configuration. Overrides and supplements the
+ * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcMariadbDAO extends JdbcBaseDAO {
+    private static final String DRIVER_CLASS_NAME = org.mariadb.jdbc.Driver.class.getName();
+    @SuppressWarnings("unused")
+    private static final String DATA_SOURCE_CLASS_NAME = org.mariadb.jdbc.MariaDbDataSource.class.getName();
+
+    private final Logger logger = LoggerFactory.getLogger(JdbcMariadbDAO.class);
+
+    /********
+     * INIT *
+     ********/
+    public JdbcMariadbDAO() {
+        initSqlTypes();
+        initDbProps();
+        initSqlQueries();
+    }
+
+    private void initSqlQueries() {
+        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
+    }
+
+    /**
+     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
+     */
+    private void initSqlTypes() {
+        logger.debug("JDBC::initSqlTypes: Initialize the type array");
+        sqlTypes.put("IMAGEITEM", "VARCHAR(16255)");
+        sqlTypes.put("STRINGITEM", "VARCHAR(16255)"); // MariaDB using utf-8 max = 16383, using 16383-128 = 16255
+    }
+
+    /**
+     * INFO: https://github.com/brettwooldridge/HikariCP
+     */
+    private void initDbProps() {
+        // Performancetuning
+        databaseProps.setProperty("dataSource.cachePrepStmts", "true");
+        databaseProps.setProperty("dataSource.prepStmtCacheSize", "250");
+        databaseProps.setProperty("dataSource.prepStmtCacheSqlLimit", "2048");
+        databaseProps.setProperty("dataSource.jdbcCompliantTruncation", "false");// jdbc standard max varchar max length
+        // of 21845
+
+        // Properties for HikariCP
+        // Use driverClassName
+        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
+        // driverClassName OR BETTER USE dataSourceClassName
+        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
+        databaseProps.setProperty("maximumPoolSize", "3");
+        databaseProps.setProperty("minimumIdle", "2");
+    }
+
+    @Override
+    public void initAfterFirstDbConnection() {
+        logger.debug("JDBC::initAfterFirstDbConnection: Initializing step, after db is connected.");
+        DbMetaData dbMeta = new DbMetaData();
+        this.dbMeta = dbMeta;
+        // Initialize sqlTypes, depending on DB version for example
+        if (dbMeta.isDbVersionGreater(5, 1)) {
+            sqlTypes.put("DATETIMEITEM", "TIMESTAMP(3)");
+            sqlTypes.put("tablePrimaryKey", "TIMESTAMP(3)");
+            sqlTypes.put("tablePrimaryValue", "NOW(3)");
+        }
+    }
+
+    /**************
+     * ITEMS DAOs *
+     **************/
+    @Override
+    public @Nullable Integer doPingDB() throws JdbcSQLException {
+        try {
+            final @Nullable Long result = Yank.queryScalar(sqlPingDB, Long.class, null);
+            return Objects.nonNull(result) ? result.intValue() : null;
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    /*************
+     * ITEM DAOs *
+     *************/
+
+    /****************************
+     * SQL generation Providers *
+     ****************************/
+
+    /*****************
+     * H E L P E R S *
+     *****************/
+
+    /******************************
+     * public Getters and Setters *
+     ******************************/
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcMysqlDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcMysqlDAO.java
new file mode 100644 (file)
index 0000000..dc83b69
--- /dev/null
@@ -0,0 +1,127 @@
+/**
+ * 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.db;
+
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.knowm.yank.Yank;
+import org.knowm.yank.exceptions.YankSQLException;
+import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
+import org.openhab.persistence.jdbc.internal.utils.DbMetaData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extended Database Configuration class. Class represents
+ * the extended database-specific configuration. Overrides and supplements the
+ * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
+ *
+ * since driver version >= 6.0 sometimes timezone conversation is needed: ?serverTimezone=UTC
+ * example: dbProps.setProperty("jdbcUrl", "jdbc:mysql://192.168.0.181:3306/ItemTypeTest3?serverTimezone=UTC");//mysql
+ * 5.7
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcMysqlDAO extends JdbcBaseDAO {
+    private static final String DRIVER_CLASS_NAME = com.mysql.cj.jdbc.Driver.class.getName();
+    @SuppressWarnings("unused")
+    private static final String DATA_SOURCE_CLASS_NAME = com.mysql.cj.jdbc.MysqlDataSource.class.getName();
+
+    private final Logger logger = LoggerFactory.getLogger(JdbcMysqlDAO.class);
+
+    /********
+     * INIT *
+     ********/
+    public JdbcMysqlDAO() {
+        initSqlTypes();
+        initDbProps();
+        initSqlQueries();
+    }
+
+    private void initSqlQueries() {
+        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
+    }
+
+    /**
+     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
+     */
+    private void initSqlTypes() {
+        logger.debug("JDBC::initSqlTypes: Initialize the type array");
+        sqlTypes.put("STRINGITEM", "VARCHAR(21717)");// mysql using utf-8 max 65535/3 = 21845, using 21845-128 = 21717
+    }
+
+    /**
+     * INFO: https://github.com/brettwooldridge/HikariCP
+     */
+    private void initDbProps() {
+        // Performancetuning
+        databaseProps.setProperty("dataSource.cachePrepStmts", "true");
+        databaseProps.setProperty("dataSource.prepStmtCacheSize", "250");
+        databaseProps.setProperty("dataSource.prepStmtCacheSqlLimit", "2048");
+        databaseProps.setProperty("dataSource.jdbcCompliantTruncation", "false");// jdbc standard max varchar max length
+                                                                                 // of 21845
+
+        // Properties for HikariCP
+        // Use driverClassName
+        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
+        // OR dataSourceClassName
+        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
+        databaseProps.setProperty("maximumPoolSize", "3");
+        databaseProps.setProperty("minimumIdle", "2");
+    }
+
+    @Override
+    public void initAfterFirstDbConnection() {
+        logger.debug("JDBC::initAfterFirstDbConnection: Initializing step, after db is connected.");
+        DbMetaData dbMeta = new DbMetaData();
+        this.dbMeta = dbMeta;
+        // Initialize sqlTypes, depending on DB version for example
+        if (dbMeta.isDbVersionGreater(5, 5)) {
+            sqlTypes.put("DATETIMEITEM", "TIMESTAMP(3)");
+            sqlTypes.put("tablePrimaryKey", "TIMESTAMP(3)");
+            sqlTypes.put("tablePrimaryValue", "NOW(3)");
+        }
+    }
+
+    /**************
+     * ITEMS DAOs *
+     **************/
+    @Override
+    public @Nullable Integer doPingDB() throws JdbcSQLException {
+        try {
+            final @Nullable Long result = Yank.queryScalar(sqlPingDB, Long.class, null);
+            return Objects.nonNull(result) ? result.intValue() : null;
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    /*************
+     * ITEM DAOs *
+     *************/
+
+    /****************************
+     * SQL generation Providers *
+     ****************************/
+
+    /*****************
+     * H E L P E R S *
+     *****************/
+
+    /******************************
+     * public Getters and Setters *
+     ******************************/
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcPostgresqlDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcPostgresqlDAO.java
new file mode 100644 (file)
index 0000000..7a8ab72
--- /dev/null
@@ -0,0 +1,216 @@
+/**
+ * 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.db;
+
+import java.time.ZoneId;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.knowm.yank.Yank;
+import org.knowm.yank.exceptions.YankSQLException;
+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.internal.dto.ItemVO;
+import org.openhab.persistence.jdbc.internal.dto.ItemsVO;
+import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
+import org.openhab.persistence.jdbc.internal.utils.StringUtilsExt;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extended Database Configuration class. Class represents
+ * the extended database-specific configuration. Overrides and supplements the
+ * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcPostgresqlDAO extends JdbcBaseDAO {
+    private static final String DRIVER_CLASS_NAME = org.postgresql.Driver.class.getName();
+    @SuppressWarnings("unused")
+    private static final String DATA_SOURCE_CLASS_NAME = org.postgresql.ds.PGSimpleDataSource.class.getName();
+
+    private final Logger logger = LoggerFactory.getLogger(JdbcPostgresqlDAO.class);
+
+    /********
+     * INIT *
+     ********/
+    public JdbcPostgresqlDAO() {
+        initSqlQueries();
+        initSqlTypes();
+        initDbProps();
+    }
+
+    private void initSqlQueries() {
+        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
+        // System Information Functions: https://www.postgresql.org/docs/9.2/static/functions-info.html
+        sqlGetDB = "SELECT CURRENT_DATABASE()";
+        sqlIfTableExists = "SELECT * FROM PG_TABLES WHERE TABLENAME='#searchTable#'";
+        sqlCreateItemsTableIfNot = "CREATE TABLE IF NOT EXISTS #itemsManageTable# (itemid SERIAL NOT NULL, #colname# #coltype# NOT NULL, CONSTRAINT #itemsManageTable#_pkey PRIMARY KEY (itemid))";
+        sqlCreateNewEntryInItemsTable = "INSERT INTO items (itemname) SELECT itemname FROM #itemsManageTable# UNION VALUES ('#itemname#') EXCEPT SELECT itemname FROM items";
+        sqlGetItemTables = "SELECT table_name FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_schema=(SELECT table_schema "
+                + "FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_name='#itemsManageTable#') AND NOT table_name='#itemsManageTable#'";
+        // http://stackoverflow.com/questions/17267417/how-do-i-do-an-upsert-merge-insert-on-duplicate-update-in-postgresql
+        // for later use, PostgreSql > 9.5 to prevent PRIMARY key violation use:
+        // SQL_INSERT_ITEM_VALUE = "INSERT INTO #tableName# (TIME, VALUE) VALUES( NOW(), CAST( ? as #dbType#) ) ON
+        // CONFLICT DO NOTHING";
+        sqlInsertItemValue = "INSERT INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, CAST( ? as #dbType#) )";
+    }
+
+    /**
+     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
+     */
+    private void initSqlTypes() {
+        // Initialize the type array
+        sqlTypes.put("CALLITEM", "VARCHAR");
+        sqlTypes.put("COLORITEM", "VARCHAR");
+        sqlTypes.put("CONTACTITEM", "VARCHAR");
+        sqlTypes.put("DATETIMEITEM", "TIMESTAMP");
+        sqlTypes.put("DIMMERITEM", "SMALLINT");
+        sqlTypes.put("IMAGEITEM", "VARCHAR");
+        sqlTypes.put("LOCATIONITEM", "VARCHAR");
+        sqlTypes.put("NUMBERITEM", "DOUBLE PRECISION");
+        sqlTypes.put("PLAYERITEM", "VARCHAR");
+        sqlTypes.put("ROLLERSHUTTERITEM", "SMALLINT");
+        sqlTypes.put("STRINGITEM", "VARCHAR");
+        sqlTypes.put("SWITCHITEM", "VARCHAR");
+        logger.debug("JDBC::initSqlTypes: Initialized the type array sqlTypes={}", sqlTypes.values());
+    }
+
+    /**
+     * INFO: https://github.com/brettwooldridge/HikariCP
+     */
+    private void initDbProps() {
+        // Performance:
+        // databaseProps.setProperty("dataSource.cachePrepStmts", "true");
+        // databaseProps.setProperty("dataSource.prepStmtCacheSize", "250");
+        // databaseProps.setProperty("dataSource.prepStmtCacheSqlLimit", "2048");
+
+        // Properties for HikariCP
+        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
+        // driverClassName OR BETTER USE dataSourceClassName
+        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
+        // databaseProps.setProperty("maximumPoolSize", "3");
+        // databaseProps.setProperty("minimumIdle", "2");
+    }
+
+    /**************
+     * ITEMS DAOs *
+     **************/
+    @Override
+    public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
+                new String[] { "#itemsManageTable#", "#colname#", "#coltype#", "#itemsManageTable#" },
+                new String[] { vo.getItemsManageTable(), vo.getColname(), vo.getColtype(), vo.getItemsManageTable() });
+        logger.debug("JDBC::doCreateItemsTableIfNot sql={}", sql);
+        try {
+            Yank.execute(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+        return vo;
+    }
+
+    @Override
+    public Long doCreateNewEntryInItemsTable(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
+                new String[] { "#itemsManageTable#", "#itemname#" },
+                new String[] { vo.getItemsManageTable(), vo.getItemName() });
+        logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
+        try {
+            return Yank.insert(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    @Override
+    public List<ItemsVO> doGetItemTables(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(this.sqlGetItemTables,
+                new String[] { "#itemsManageTable#", "#itemsManageTable#" },
+                new String[] { vo.getItemsManageTable(), vo.getItemsManageTable() });
+        this.logger.debug("JDBC::doGetItemTables sql={}", sql);
+        try {
+            return Yank.queryBeanList(sql, ItemsVO.class, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    /*************
+     * ITEM DAOs *
+     *************/
+    @Override
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo) throws JdbcSQLException {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
+        String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
+                new String[] { "#tableName#", "#dbType#", "#tablePrimaryValue#" },
+                new String[] { storedVO.getTableName(), storedVO.getDbType(), sqlTypes.get("tablePrimaryValue") });
+        Object[] params = { storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
+        try {
+            Yank.execute(sql, params);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    /****************************
+     * SQL generation Providers *
+     ****************************/
+
+    @Override
+    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);
+
+        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) {
+            // see:
+            // http://www.jooq.org/doc/3.5/manual/sql-building/sql-statements/select-statement/limit-clause/
+            filterString += " OFFSET " + filter.getPageNumber() * filter.getPageSize() + " LIMIT "
+                    + filter.getPageSize();
+        }
+        String queryString = "NUMBERITEM".equalsIgnoreCase(simpleName) && numberDecimalcount > -1
+                ? "SELECT time, ROUND(CAST (value AS numeric)," + numberDecimalcount + ") FROM " + table
+                : "SELECT time, value FROM " + table;
+        if (!filterString.isEmpty()) {
+            queryString += filterString;
+        }
+        logger.debug("JDBC::query queryString = {}", queryString);
+        return queryString;
+    }
+
+    /*****************
+     * H E L P E R S *
+     *****************/
+
+    /******************************
+     * public Getters and Setters *
+     ******************************/
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcSqliteDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcSqliteDAO.java
new file mode 100644 (file)
index 0000000..fc2888e
--- /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.internal.db;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.knowm.yank.Yank;
+import org.knowm.yank.exceptions.YankSQLException;
+import org.openhab.core.items.Item;
+import org.openhab.core.types.State;
+import org.openhab.persistence.jdbc.internal.dto.ItemVO;
+import org.openhab.persistence.jdbc.internal.dto.ItemsVO;
+import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
+import org.openhab.persistence.jdbc.internal.utils.StringUtilsExt;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extended Database Configuration class. Class represents
+ * the extended database-specific configuration. Overrides and supplements the
+ * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcSqliteDAO extends JdbcBaseDAO {
+    private static final String DRIVER_CLASS_NAME = org.sqlite.JDBC.class.getName();
+    @SuppressWarnings("unused")
+    private static final String DATA_SOURCE_CLASS_NAME = org.sqlite.SQLiteDataSource.class.getName();
+
+    private final Logger logger = LoggerFactory.getLogger(JdbcSqliteDAO.class);
+
+    /********
+     * INIT *
+     ********/
+    public JdbcSqliteDAO() {
+        initSqlQueries();
+        initSqlTypes();
+        initDbProps();
+    }
+
+    private void initSqlQueries() {
+        logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
+        sqlGetDB = "PRAGMA DATABASE_LIST"; // "SELECT SQLITE_VERSION()"; // "PRAGMA DATABASE_LIST"->db Path/Name
+                                           // "PRAGMA SCHEMA_VERSION";
+        sqlIfTableExists = "SELECT name FROM sqlite_master WHERE type='table' AND name='#searchTable#'";
+        sqlCreateItemsTableIfNot = "CREATE TABLE IF NOT EXISTS #itemsManageTable# (ItemId INTEGER PRIMARY KEY AUTOINCREMENT, #colname# #coltype# NOT NULL)";
+        sqlInsertItemValue = "INSERT OR IGNORE INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, CAST( ? as #dbType#) )";
+    }
+
+    /**
+     * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
+     */
+    private void initSqlTypes() {
+        logger.debug("JDBC::initSqlTypes: Initialize the type array");
+        sqlTypes.put("tablePrimaryValue", "strftime('%Y-%m-%d %H:%M:%f' , 'now' , 'localtime')");
+    }
+
+    /**
+     * INFO: https://github.com/brettwooldridge/HikariCP
+     */
+    private void initDbProps() {
+        // Properties for HikariCP
+        databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
+        // driverClassName OR BETTER USE dataSourceClassName
+        // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
+    }
+
+    /**************
+     * ITEMS DAOs *
+     **************/
+
+    @Override
+    public @Nullable String doGetDB() throws JdbcSQLException {
+        try {
+            return Yank.queryColumn(sqlGetDB, "file", String.class, null).get(0);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    @Override
+    public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) throws JdbcSQLException {
+        String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
+                new String[] { "#itemsManageTable#", "#colname#", "#coltype#" },
+                new String[] { vo.getItemsManageTable(), vo.getColname(), vo.getColtype() });
+        logger.debug("JDBC::doCreateItemsTableIfNot sql={}", sql);
+        try {
+            Yank.execute(sql, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+        return vo;
+    }
+
+    /*************
+     * ITEM DAOs *
+     *************/
+    @Override
+    public void doStoreItemValue(Item item, State itemState, ItemVO vo) throws JdbcSQLException {
+        ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
+        String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
+                new String[] { "#tableName#", "#dbType#", "#tablePrimaryValue#" },
+                new String[] { storedVO.getTableName(), storedVO.getDbType(), sqlTypes.get("tablePrimaryValue") });
+        Object[] params = { storedVO.getValue() };
+        logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
+        try {
+            Yank.execute(sql, params);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+
+    /****************************
+     * SQL generation Providers *
+     ****************************/
+
+    /*****************
+     * H E L P E R S *
+     *****************/
+
+    /******************************
+     * public Getters and Setters *
+     ******************************/
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcTimescaledbDAO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcTimescaledbDAO.java
new file mode 100644 (file)
index 0000000..f81de85
--- /dev/null
@@ -0,0 +1,61 @@
+/**
+ * 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.db;
+
+import java.util.Properties;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.knowm.yank.Yank;
+import org.knowm.yank.exceptions.YankSQLException;
+import org.openhab.persistence.jdbc.internal.dto.ItemVO;
+import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
+import org.openhab.persistence.jdbc.internal.utils.StringUtilsExt;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extended Database Configuration class. Class represents the extended database-specific configuration. Overrides and
+ * supplements the default settings from JdbcBaseDAO and JdbcPostgresqlDAO.
+ *
+ * @author Riccardo Nimser-Joseph - Initial contribution
+ * @author Dan Cunningham - Fixes and refactoring
+ */
+@NonNullByDefault
+public class JdbcTimescaledbDAO extends JdbcPostgresqlDAO {
+    private final Logger logger = LoggerFactory.getLogger(JdbcTimescaledbDAO.class);
+
+    private final String sqlCreateHypertable = "SELECT created from create_hypertable('#tableName#', 'time')";
+
+    @Override
+    public Properties getConnectionProperties() {
+        Properties properties = (Properties) this.databaseProps.clone();
+        // Adjust the jdbc url since the service name 'timescaledb' is only used to differentiate the DAOs
+        if (properties.containsKey("jdbcUrl")) {
+            properties.put("jdbcUrl", properties.getProperty("jdbcUrl").replace("timescaledb", "postgresql"));
+        }
+        return properties;
+    }
+
+    @Override
+    public void doCreateItemTable(ItemVO vo) throws JdbcSQLException {
+        super.doCreateItemTable(vo);
+        String sql = StringUtilsExt.replaceArrayMerge(this.sqlCreateHypertable, new String[] { "#tableName#" },
+                new String[] { vo.getTableName() });
+        this.logger.debug("JDBC::doCreateItemTable sql={}", sql);
+        try {
+            Yank.queryScalar(sql, Boolean.class, null);
+        } catch (YankSQLException e) {
+            throw new JdbcSQLException(e);
+        }
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/ItemVO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/ItemVO.java
new file mode 100644 (file)
index 0000000..2fca6ef
--- /dev/null
@@ -0,0 +1,149 @@
+/**
+ * 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.dto;
+
+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;
+
+/**
+ * Represents the Item-data on the part of MyBatis/database.
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+public class ItemVO implements Serializable {
+    private final Logger logger = LoggerFactory.getLogger(ItemVO.class);
+
+    private static final long serialVersionUID = 1871441039821454890L;
+
+    private String tableName;
+    private @Nullable String newTableName;
+    private String dbType;
+    private String jdbcType;
+    private String itemType;
+    private Class<?> javaType;
+    private Date time;
+    private Object value;
+
+    public ItemVO(String tableName, @Nullable String newTableName) {
+        logger.debug("JDBC:ItemVO tableName={}; newTableName={}; ", tableName, newTableName);
+        this.tableName = tableName;
+        this.newTableName = newTableName;
+    }
+
+    public ItemVO() {
+    }
+
+    public void setValueTypes(String dbType, Class<?> javaType) {
+        logger.debug("JDBC:ItemVO setValueTypes dbType={}; javaType={};", dbType, javaType);
+        this.dbType = dbType;
+        this.javaType = javaType;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public void setTableName(String tableName) {
+        this.tableName = tableName;
+    }
+
+    public @Nullable String getNewTableName() {
+        return newTableName;
+    }
+
+    public void setNewTableName(String newTableName) {
+        this.newTableName = newTableName;
+    }
+
+    public String getDbType() {
+        return dbType;
+    }
+
+    public void setDbType(String dbType) {
+        this.dbType = dbType;
+    }
+
+    public String getJdbcType() {
+        return jdbcType;
+    }
+
+    public void setJdbcType(String jdbcType) {
+        this.jdbcType = jdbcType;
+    }
+
+    public String getItemType() {
+        return itemType;
+    }
+
+    public void setItemType(String itemType) {
+        this.itemType = itemType;
+    }
+
+    public String getJavaType() {
+        return javaType.getName();
+    }
+
+    public void setJavaType(Class<?> javaType) {
+        this.javaType = javaType;
+    }
+
+    public Date getTime() {
+        return time;
+    }
+
+    public void setTime(Date time) {
+        this.time = time;
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    public void setValue(Object value) {
+        this.value = value;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        ItemVO other = (ItemVO) obj;
+        if (value == null) {
+            if (other.value != null) {
+                return false;
+            }
+        } else if (!value.equals(other.value)) {
+            return false;
+        }
+        return Objects.equals(time, other.time);
+    }
+
+    @Override
+    public String 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();
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/ItemsVO.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/ItemsVO.java
new file mode 100644 (file)
index 0000000..8bc23e5
--- /dev/null
@@ -0,0 +1,148 @@
+/**
+ * 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.dto;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * Represents the table naming data.
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+public class ItemsVO implements Serializable {
+
+    private static final long serialVersionUID = 2871961811177601520L;
+
+    private static final String STR_FILTER = "[^a-zA-Z0-9]";
+
+    private String coltype = "VARCHAR(500)";
+    private String colname = "itemname";
+    private String itemsManageTable = "items";
+    private int itemId;
+    private String itemName;
+    private String tableName;
+    private String jdbcUriDatabaseName;
+
+    public String getColtype() {
+        return coltype;
+    }
+
+    public void setColtype(String coltype) {
+        this.coltype = coltype.replaceAll(STR_FILTER, "");
+    }
+
+    public String getColname() {
+        return colname;
+    }
+
+    public void setColname(String colname) {
+        this.colname = colname.replaceAll(STR_FILTER, "");
+    }
+
+    public String getItemsManageTable() {
+        return itemsManageTable;
+    }
+
+    public void setItemsManageTable(String itemsManageTable) {
+        this.itemsManageTable = itemsManageTable.replaceAll(STR_FILTER, "");
+    }
+
+    public int getItemId() {
+        return itemId;
+    }
+
+    public void setItemId(int itemId) {
+        this.itemId = itemId;
+    }
+
+    public String getItemName() {
+        return itemName;
+    }
+
+    public void setItemName(String itemName) {
+        this.itemName = itemName;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public void setTableName(String tableName) {
+        this.tableName = tableName;
+    }
+
+    public String getJdbcUriDatabaseName() {
+        return jdbcUriDatabaseName;
+    }
+
+    public void setJdbcUriDatabaseName(String jdbcUriDatabaseName) {
+        this.jdbcUriDatabaseName = jdbcUriDatabaseName;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(itemName, itemId);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        ItemsVO other = (ItemsVO) obj;
+        if (itemName == null) {
+            if (other.itemName != null) {
+                return false;
+            }
+        } else if (!itemName.equals(other.itemName)) {
+            return false;
+        }
+        return itemId == other.itemId;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("ItemsVO [coltype=");
+        builder.append(coltype);
+        builder.append(", colname=");
+        builder.append(colname);
+        builder.append(", itemsManageTable=");
+        builder.append(itemsManageTable);
+        builder.append(", itemid=");
+        builder.append(itemId);
+        builder.append(", itemname=");
+        builder.append(itemName);
+        builder.append(", table_name=");
+        builder.append(tableName);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/JdbcHistoricItem.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/JdbcHistoricItem.java
new file mode 100644 (file)
index 0000000..a9ae3eb
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * 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.dto;
+
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.persistence.HistoricItem;
+import org.openhab.core.types.State;
+
+/**
+ * Represents the data on the part of openHAB.
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcHistoricItem implements HistoricItem {
+
+    private final String name;
+    private final State state;
+    private final ZonedDateTime timestamp;
+
+    public JdbcHistoricItem(String name, State state, ZonedDateTime timestamp) {
+        this.name = name;
+        this.state = state;
+        this.timestamp = timestamp;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public State getState() {
+        return state;
+    }
+
+    @Override
+    public ZonedDateTime getTimestamp() {
+        return timestamp;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("JdbcItem [name=");
+        builder.append(name);
+        builder.append(", state=");
+        builder.append(state);
+        builder.append(", timestamp=");
+        builder.append(timestamp);
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/JdbcPersistenceItemInfo.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/JdbcPersistenceItemInfo.java
new file mode 100644 (file)
index 0000000..bc71959
--- /dev/null
@@ -0,0 +1,65 @@
+/**
+ * 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.dto;
+
+import java.util.Date;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.persistence.PersistenceItemInfo;
+
+/**
+ * Represents the item info for openHAB.
+ *
+ * @author Christoph Weitkamp - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcPersistenceItemInfo implements PersistenceItemInfo {
+
+    private final String name;
+    private final @Nullable Integer count;
+    private final @Nullable Date earliest;
+    private final @Nullable Date latest;
+
+    public JdbcPersistenceItemInfo(String name) {
+        this(name, null, null, null);
+    }
+
+    public JdbcPersistenceItemInfo(String name, @Nullable Integer count, @Nullable Date earliest,
+            @Nullable Date latest) {
+        this.name = name;
+        this.count = count;
+        this.earliest = earliest;
+        this.latest = latest;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public @Nullable Integer getCount() {
+        return count;
+    }
+
+    @Override
+    public @Nullable Date getEarliest() {
+        return earliest;
+    }
+
+    @Override
+    public @Nullable Date getLatest() {
+        return latest;
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/exceptions/JdbcException.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/exceptions/JdbcException.java
new file mode 100644 (file)
index 0000000..3647cdb
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * 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.exceptions;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Base class for JDBC exceptions.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcException extends Exception {
+
+    private static final long serialVersionUID = 1911437557128995424L;
+
+    public JdbcException(String message) {
+        super(message);
+    }
+
+    public JdbcException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/exceptions/JdbcSQLException.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/exceptions/JdbcSQLException.java
new file mode 100644 (file)
index 0000000..6b8e4a6
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * 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.exceptions;
+
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.knowm.yank.exceptions.YankSQLException;
+
+/**
+ * This exception wraps a {@link YankSQLException}.
+ *
+ * @author Jacob Laursen - Initial contribution
+ */
+@NonNullByDefault
+public class JdbcSQLException extends JdbcException {
+
+    private static final long serialVersionUID = 4562191548585905000L;
+
+    public JdbcSQLException(YankSQLException sqlException) {
+        super(Objects.requireNonNull(sqlException.getMessage()));
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/utils/DbMetaData.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/utils/DbMetaData.java
new file mode 100644 (file)
index 0000000..4ac2a08
--- /dev/null
@@ -0,0 +1,128 @@
+/**
+ * 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.utils;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.knowm.yank.Yank;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.zaxxer.hikari.HikariDataSource;
+
+/**
+ * Meta data class
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+@NonNullByDefault
+public class DbMetaData {
+
+    private final Logger logger = LoggerFactory.getLogger(DbMetaData.class);
+
+    private int dbMajorVersion;
+    private int dbMinorVersion;
+    private int driverMajorVersion;
+    private int driverMinorVersion;
+    private @Nullable String dbProductName;
+    private @Nullable String dbProductVersion;
+
+    public DbMetaData() {
+        HikariDataSource h = Yank.getDefaultConnectionPool();
+
+        DatabaseMetaData meta;
+        try {
+            meta = h.getConnection().getMetaData();
+
+            // Oracle (and some other vendors) do not support
+            // some the following methods; therefore, we need
+            // to use try-catch block.
+            try {
+                dbMajorVersion = meta.getDatabaseMajorVersion();
+                logger.debug("dbMajorVersion = '{}'", dbMajorVersion);
+            } catch (Exception e) {
+                logger.error("Asking for 'dbMajorVersion' is unsupported: '{}'", e.getMessage());
+            }
+
+            try {
+                dbMinorVersion = meta.getDatabaseMinorVersion();
+                logger.debug("dbMinorVersion = '{}'", dbMinorVersion);
+            } catch (Exception e) {
+                logger.error("Asking for 'dbMinorVersion' is unsupported: '{}'", e.getMessage());
+            }
+
+            driverMajorVersion = meta.getDriverMajorVersion();
+            logger.debug("driverMajorVersion = '{}'", driverMajorVersion);
+
+            driverMinorVersion = meta.getDriverMinorVersion();
+            logger.debug("driverMinorVersion = '{}'", driverMinorVersion);
+
+            dbProductName = meta.getDatabaseProductName();
+            logger.debug("dbProductName = '{}'", dbProductName);
+
+            dbProductVersion = meta.getDatabaseProductVersion();
+            logger.debug("dbProductVersion = '{}'", dbProductVersion);
+        } catch (SQLException e1) {
+            logger.error("Asking for 'dbMajorVersion' seems to be unsupported: '{}'", e1.getMessage());
+        }
+    }
+
+    public int getDbMajorVersion() {
+        return dbMajorVersion;
+    }
+
+    public int getDbMinorVersion() {
+        return dbMinorVersion;
+    }
+
+    public boolean isDbVersionGreater(int major, int minor) {
+        if (dbMajorVersion > major) {
+            return true;
+        } else if (dbMajorVersion == major) {
+            if (dbMinorVersion > minor) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public int getDriverMajorVersion() {
+        return driverMajorVersion;
+    }
+
+    public int getDriverMinorVersion() {
+        return driverMinorVersion;
+    }
+
+    public boolean isDriverVersionGreater(int major, int minor) {
+        if (major > driverMajorVersion) {
+            return true;
+        } else if (major == driverMajorVersion) {
+            if (minor > driverMinorVersion) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public @Nullable String getDbProductName() {
+        return dbProductName;
+    }
+
+    public @Nullable String getDbProductVersion() {
+        return dbProductVersion;
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/utils/MovingAverage.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/utils/MovingAverage.java
new file mode 100644 (file)
index 0000000..41a9589
--- /dev/null
@@ -0,0 +1,74 @@
+/**
+ * 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.utils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.LinkedList;
+import java.util.Queue;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Calculates the average/mean of a number series.
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+@NonNullByDefault
+public class MovingAverage {
+
+    private final Queue<BigDecimal> win = new LinkedList<>();
+    private final int period;
+    private BigDecimal sum = BigDecimal.ZERO;
+
+    public MovingAverage(int period) {
+        assert period > 0 : "Period must be a positive integer";
+        this.period = period;
+    }
+
+    public void add(Double num) {
+        add(new BigDecimal(num));
+    }
+
+    public void add(Long num) {
+        add(new BigDecimal(num));
+    }
+
+    public void add(Integer num) {
+        add(new BigDecimal(num));
+    }
+
+    public void add(BigDecimal num) {
+        sum = sum.add(num);
+        win.add(num);
+        if (win.size() > period) {
+            sum = sum.subtract(win.remove());
+        }
+    }
+
+    public BigDecimal getAverage() {
+        if (win.isEmpty()) {
+            return BigDecimal.ZERO; // technically the average is undefined
+        }
+        BigDecimal divisor = BigDecimal.valueOf(win.size());
+        return sum.divide(divisor, 2, RoundingMode.HALF_UP);
+    }
+
+    public double getAverageDouble() {
+        return getAverage().doubleValue();
+    }
+
+    public int getAverageInteger() {
+        return getAverage().intValue();
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/utils/StringUtilsExt.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/utils/StringUtilsExt.java
new file mode 100644 (file)
index 0000000..262224a
--- /dev/null
@@ -0,0 +1,281 @@
+/**
+ * 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.utils;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.persistence.FilterCriteria;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class
+ *
+ * @author Helmut Lehmeyer - Initial contribution
+ */
+@NonNullByDefault
+public class StringUtilsExt {
+    private static final Logger LOGGER = LoggerFactory.getLogger(StringUtilsExt.class);
+
+    /**
+     * Replaces multiple found words with the given Array contents
+     *
+     * @param str String for replacement
+     * @param separate A String or Array to be replaced
+     * @param separators Array will be merged to str
+     * @return
+     */
+    public static final String replaceArrayMerge(String str, String separate, Object[] separators) {
+        String s = str;
+        for (int i = 0; i < separators.length; i++) {
+            s = s.replaceFirst(separate, (String) separators[i]);
+        }
+        return s;
+    }
+
+    /**
+     * @see #replaceArrayMerge(String str, String separate, Object[] separators)
+     */
+    public static final String replaceArrayMerge(String str, String[] separate, String[] separators) {
+        String s = str;
+        for (int i = 0; i < separators.length; i++) {
+            s = s.replaceFirst(separate[i], separators[i]);
+        }
+        return s;
+    }
+
+    /**
+     * @see #parseJdbcURL(String url, Properties def)
+     */
+    public static Properties parseJdbcURL(String url) {
+        return parseJdbcURL(url, null);
+    }
+
+    /**
+     * <b>JDBC-URI Examples:</b><br/>
+     * jdbc:dbShortcut:c:/dev/databaseName<br/>
+     * jdbc:dbShortcut:scheme:c:/dev/databaseName<br/>
+     * jdbc:dbShortcut:scheme:c:\\dev\\databaseName<br/>
+     * jdbc:dbShortcut:./databaseName<br/>
+     * jdbc:dbShortcut:/databaseName<br/>
+     * jdbc:dbShortcut:~/databaseName<br/>
+     * jdbc:dbShortcut:/path/databaseName.db<br/>
+     * jdbc:dbShortcut:./../../path/databaseName<br/>
+     * jdbc:dbShortcut:scheme:./path/../path/databaseName;param1=true;<br/>
+     * jdbc:dbShortcut://192.168.0.145:3306/databaseName?param1=false&param2=true
+     * <p/>
+     *
+     * @param url JDBC-URI
+     * @param def Predefined Properties Object
+     * @return A merged Properties Object may contain:<br/>
+     *         parseValid (mandatory)<br/>
+     *         scheme<br/>
+     *         serverPath<br/>
+     *         dbShortcut<br/>
+     *         databaseName<br/>
+     *         portNumber<br/>
+     *         serverName<br/>
+     *         pathQuery<br/>
+     */
+    public static Properties parseJdbcURL(String url, @Nullable Properties def) {
+        Properties props;
+        if (def == null) {
+            props = new Properties();
+        } else {
+            props = new Properties(def);
+        }
+
+        if (url.length() < 9) {
+            return props;
+        }
+
+        // replace all \
+        if (url.contains("\\")) {
+            url = url.replaceAll("\\\\", "/");
+        }
+
+        // replace first ; with ?
+        if (url.contains(";")) {
+            // replace first ; with ?
+            url = url.replaceFirst(";", "?");
+            // replace other ; with &
+            url = url.replaceAll(";", "&");
+        }
+
+        if (url.split(":").length < 3 || url.indexOf("/") == -1) {
+            LOGGER.error("parseJdbcURL: URI '{}' is not well formated, expected uri like 'jdbc:dbShortcut:/path'", url);
+            props.put("parseValid", "false");
+            return props;
+        }
+
+        String[] protAndDb = stringBeforeSubstr(url, ":", 1).split(":");
+        if (!"jdbc".equals(protAndDb[0])) {
+            LOGGER.error("parseJdbcURL: URI '{}' is not well formated, expected suffix 'jdbc' found '{}'", url,
+                    protAndDb[0]);
+            props.put("parseValid", "false");
+            return props;
+        }
+        props.put("parseValid", "true");
+        props.put("dbShortcut", protAndDb[1]);
+
+        URI dbURI = null;
+        try {
+            dbURI = new URI(stringAfterSubstr(url, ":", 1).replaceFirst(" ", ""));
+            if (dbURI.getScheme() != null) {
+                props.put("scheme", dbURI.getScheme());
+                dbURI = new URI(stringAfterSubstr(url, ":", 2).replaceFirst(" ", ""));
+            }
+        } catch (URISyntaxException e) {
+            LOGGER.error("parseJdbcURL: URI '{}' is not well formated.", url, e);
+            return props;
+        }
+
+        // Query-Parameters
+        if (dbURI.getQuery() != null) {
+            String[] q = dbURI.getQuery().split("&");
+            for (int i = 0; i < q.length; i++) {
+                String[] t = q[i].split("=");
+                props.put(t[0], t[1]);
+            }
+            props.put("pathQuery", dbURI.getQuery());
+        }
+
+        String path = "";
+        if (dbURI.getPath() != null) {
+            String gp = dbURI.getPath();
+            String st = "/";
+            if (gp.indexOf("/") <= 1) {
+                if (substrPos(gp, st).size() > 1) {
+                    path = stringBeforeLastSubstr(gp, st) + st;
+                } else {
+                    path = stringBeforeSubstr(gp, st) + st;
+                }
+            }
+            if (dbURI.getScheme() != null && dbURI.getScheme().length() == 1) {
+                path = dbURI.getScheme() + ":" + path;
+            }
+            props.put("serverPath", path);
+        }
+        if (dbURI.getPath() != null) {
+            props.put("databaseName", stringAfterLastSubstr(dbURI.getPath(), "/"));
+        }
+        if (dbURI.getPort() != -1) {
+            props.put("portNumber", dbURI.getPort() + "");
+        }
+        if (dbURI.getHost() != null) {
+            props.put("serverName", dbURI.getHost());
+        }
+
+        return props;
+    }
+
+    /**
+     * Returns a String before the last occurrence of a substring
+     */
+    public static String stringBeforeLastSubstr(String s, String substr) {
+        List<Integer> a = substrPos(s, substr);
+        return s.substring(0, a.get(a.size() - 1));
+    }
+
+    /**
+     * Returns a String after the last occurrence of a substring
+     */
+    public static String stringAfterLastSubstr(String s, String substr) {
+        List<Integer> a = substrPos(s, substr);
+        return s.substring(a.get(a.size() - 1) + 1);
+    }
+
+    /**
+     * Returns a String after the first occurrence of a substring
+     */
+    public static String stringAfterSubstr(String s, String substr) {
+        return s.substring(s.indexOf(substr) + 1);
+    }
+
+    /**
+     * Returns a String after the n occurrence of a substring
+     */
+    public static String stringAfterSubstr(String s, String substr, int n) {
+        return s.substring(substrPos(s, substr).get(n) + 1);
+    }
+
+    /**
+     * Returns a String before the first occurrence of a substring
+     */
+    public static String stringBeforeSubstr(String s, String substr) {
+        return s.substring(0, s.indexOf(substr));
+    }
+
+    /**
+     * Returns a String before the n occurrence of a substring.
+     */
+    public static String stringBeforeSubstr(String s, String substr, int n) {
+        return s.substring(0, substrPos(s, substr).get(n));
+    }
+
+    /**
+     * Returns a list with indices of the occurrence of a substring.
+     */
+    public static List<Integer> substrPos(String s, String substr) {
+        return substrPos(s, substr, true);
+    }
+
+    /**
+     * Returns a list with indices of the occurrence of a substring.
+     */
+    public static List<Integer> substrPos(String s, String substr, boolean ignoreCase) {
+        int substrLength = substr.length();
+        int strLength = s.length();
+        List<Integer> arr = new ArrayList<>();
+
+        for (int i = 0; i < strLength - substrLength + 1; i++) {
+            if (s.regionMatches(ignoreCase, i, substr, 0, substrLength)) {
+                arr.add(i);
+            }
+        }
+        return arr;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#toString()
+     */
+    public static String filterToString(FilterCriteria filter) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("FilterCriteria [itemName=");
+        builder.append(filter.getItemName());
+        builder.append(", beginDate=");
+        builder.append(filter.getBeginDate());
+        builder.append(", endDate=");
+        builder.append(filter.getEndDate());
+        builder.append(", pageNumber=");
+        builder.append(filter.getPageNumber());
+        builder.append(", pageSize=");
+        builder.append(filter.getPageSize());
+        builder.append(", operator=");
+        builder.append(filter.getOperator());
+        builder.append(", ordering=");
+        builder.append(filter.getOrdering());
+        builder.append(", state=");
+        builder.append(filter.getState());
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/DbMetaData.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/DbMetaData.java
deleted file mode 100644 (file)
index b15e5b7..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-/**
- * 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.utils;
-
-import java.sql.DatabaseMetaData;
-import java.sql.SQLException;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.knowm.yank.Yank;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.zaxxer.hikari.HikariDataSource;
-
-/**
- * Meta data class
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-@NonNullByDefault
-public class DbMetaData {
-
-    private final Logger logger = LoggerFactory.getLogger(DbMetaData.class);
-
-    private int dbMajorVersion;
-    private int dbMinorVersion;
-    private int driverMajorVersion;
-    private int driverMinorVersion;
-    private @Nullable String dbProductName;
-    private @Nullable String dbProductVersion;
-
-    public DbMetaData() {
-        HikariDataSource h = Yank.getDefaultConnectionPool();
-        // HikariDataSource h = Yank.getDataSource();
-
-        DatabaseMetaData meta;
-        try {
-            meta = h.getConnection().getMetaData();
-
-            // Oracle (and some other vendors) do not support
-            // some the following methods; therefore, we need
-            // to use try-catch block.
-            try {
-                dbMajorVersion = meta.getDatabaseMajorVersion();
-                logger.debug("dbMajorVersion = '{}'", dbMajorVersion);
-            } catch (Exception e) {
-                logger.error("Asking for 'dbMajorVersion' is unsupported: '{}'", e.getMessage());
-            }
-
-            try {
-                dbMinorVersion = meta.getDatabaseMinorVersion();
-                logger.debug("dbMinorVersion = '{}'", dbMinorVersion);
-            } catch (Exception e) {
-                logger.error("Asking for 'dbMinorVersion' is unsupported: '{}'", e.getMessage());
-            }
-
-            driverMajorVersion = meta.getDriverMajorVersion();
-            logger.debug("driverMajorVersion = '{}'", driverMajorVersion);
-
-            driverMinorVersion = meta.getDriverMinorVersion();
-            logger.debug("driverMinorVersion = '{}'", driverMinorVersion);
-
-            dbProductName = meta.getDatabaseProductName();
-            logger.debug("dbProductName = '{}'", dbProductName);
-
-            dbProductVersion = meta.getDatabaseProductVersion();
-            logger.debug("dbProductVersion = '{}'", dbProductVersion);
-        } catch (SQLException e1) {
-            logger.error("Asking for 'dbMajorVersion' seems to be unsupported: '{}'", e1.getMessage());
-        }
-    }
-
-    public int getDbMajorVersion() {
-        return dbMajorVersion;
-    }
-
-    public int getDbMinorVersion() {
-        return dbMinorVersion;
-    }
-
-    public boolean isDbVersionGreater(int major, int minor) {
-        if (dbMajorVersion > major) {
-            return true;
-        } else if (dbMajorVersion == major) {
-            if (dbMinorVersion > minor) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public int getDriverMajorVersion() {
-        return driverMajorVersion;
-    }
-
-    public int getDriverMinorVersion() {
-        return driverMinorVersion;
-    }
-
-    public boolean isDriverVersionGreater(int major, int minor) {
-        if (major > driverMajorVersion) {
-            return true;
-        } else if (major == driverMajorVersion) {
-            if (minor > driverMinorVersion) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public @Nullable String getDbProductName() {
-        return dbProductName;
-    }
-
-    public @Nullable String getDbProductVersion() {
-        return dbProductVersion;
-    }
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/MovingAverage.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/MovingAverage.java
deleted file mode 100644 (file)
index a8d5ae0..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * 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.utils;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.LinkedList;
-import java.util.Queue;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-/**
- * Calculates the average/mean of a number series.
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-@NonNullByDefault
-public class MovingAverage {
-
-    private final Queue<BigDecimal> win = new LinkedList<>();
-    private final int period;
-    private BigDecimal sum = BigDecimal.ZERO;
-
-    public MovingAverage(int period) {
-        assert period > 0 : "Period must be a positive integer";
-        this.period = period;
-    }
-
-    public void add(Double num) {
-        add(new BigDecimal(num));
-    }
-
-    public void add(Long num) {
-        add(new BigDecimal(num));
-    }
-
-    public void add(Integer num) {
-        add(new BigDecimal(num));
-    }
-
-    public void add(BigDecimal num) {
-        sum = sum.add(num);
-        win.add(num);
-        if (win.size() > period) {
-            sum = sum.subtract(win.remove());
-        }
-    }
-
-    public BigDecimal getAverage() {
-        if (win.isEmpty()) {
-            return BigDecimal.ZERO; // technically the average is undefined
-        }
-        BigDecimal divisor = BigDecimal.valueOf(win.size());
-        return sum.divide(divisor, 2, RoundingMode.HALF_UP);
-    }
-
-    public double getAverageDouble() {
-        return getAverage().doubleValue();
-    }
-
-    public int getAverageInteger() {
-        return getAverage().intValue();
-    }
-}
diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/StringUtilsExt.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/StringUtilsExt.java
deleted file mode 100644 (file)
index 1894126..0000000
+++ /dev/null
@@ -1,281 +0,0 @@
-/**
- * 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.utils;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.persistence.FilterCriteria;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Utility class
- *
- * @author Helmut Lehmeyer - Initial contribution
- */
-@NonNullByDefault
-public class StringUtilsExt {
-    private static final Logger LOGGER = LoggerFactory.getLogger(StringUtilsExt.class);
-
-    /**
-     * Replaces multiple found words with the given Array contents
-     *
-     * @param str String for replacement
-     * @param separate A String or Array to be replaced
-     * @param separators Array will be merged to str
-     * @return
-     */
-    public static final String replaceArrayMerge(String str, String separate, Object[] separators) {
-        String s = str;
-        for (int i = 0; i < separators.length; i++) {
-            s = s.replaceFirst(separate, (String) separators[i]);
-        }
-        return s;
-    }
-
-    /**
-     * @see #replaceArrayMerge(String str, String separate, Object[] separators)
-     */
-    public static final String replaceArrayMerge(String str, String[] separate, String[] separators) {
-        String s = str;
-        for (int i = 0; i < separators.length; i++) {
-            s = s.replaceFirst(separate[i], separators[i]);
-        }
-        return s;
-    }
-
-    /**
-     * @see #parseJdbcURL(String url, Properties def)
-     */
-    public static Properties parseJdbcURL(String url) {
-        return parseJdbcURL(url, null);
-    }
-
-    /**
-     * <b>JDBC-URI Examples:</b><br/>
-     * jdbc:dbShortcut:c:/dev/databaseName<br/>
-     * jdbc:dbShortcut:scheme:c:/dev/databaseName<br/>
-     * jdbc:dbShortcut:scheme:c:\\dev\\databaseName<br/>
-     * jdbc:dbShortcut:./databaseName<br/>
-     * jdbc:dbShortcut:/databaseName<br/>
-     * jdbc:dbShortcut:~/databaseName<br/>
-     * jdbc:dbShortcut:/path/databaseName.db<br/>
-     * jdbc:dbShortcut:./../../path/databaseName<br/>
-     * jdbc:dbShortcut:scheme:./path/../path/databaseName;param1=true;<br/>
-     * jdbc:dbShortcut://192.168.0.145:3306/databaseName?param1=false&param2=true
-     * <p/>
-     *
-     * @param url JDBC-URI
-     * @param def Predefined Properties Object
-     * @return A merged Properties Object may contain:<br/>
-     *         parseValid (mandatory)<br/>
-     *         scheme<br/>
-     *         serverPath<br/>
-     *         dbShortcut<br/>
-     *         databaseName<br/>
-     *         portNumber<br/>
-     *         serverName<br/>
-     *         pathQuery<br/>
-     */
-    public static Properties parseJdbcURL(String url, @Nullable Properties def) {
-        Properties props;
-        if (def == null) {
-            props = new Properties();
-        } else {
-            props = new Properties(def);
-        }
-
-        if (url.length() < 9) {
-            return props;
-        }
-
-        // replace all \
-        if (url.contains("\\")) {
-            url = url.replaceAll("\\\\", "/");
-        }
-
-        // replace first ; with ?
-        if (url.contains(";")) {
-            // replace first ; with ?
-            url = url.replaceFirst(";", "?");
-            // replace other ; with &
-            url = url.replaceAll(";", "&");
-        }
-
-        if (url.split(":").length < 3 || url.indexOf("/") == -1) {
-            LOGGER.error("parseJdbcURL: URI '{}' is not well formated, expected uri like 'jdbc:dbShortcut:/path'", url);
-            props.put("parseValid", "false");
-            return props;
-        }
-
-        String[] protAndDb = stringBeforeSubstr(url, ":", 1).split(":");
-        if (!"jdbc".equals(protAndDb[0])) {
-            LOGGER.error("parseJdbcURL: URI '{}' is not well formated, expected suffix 'jdbc' found '{}'", url,
-                    protAndDb[0]);
-            props.put("parseValid", "false");
-            return props;
-        }
-        props.put("parseValid", "true");
-        props.put("dbShortcut", protAndDb[1]);
-
-        URI dbURI = null;
-        try {
-            dbURI = new URI(stringAfterSubstr(url, ":", 1).replaceFirst(" ", ""));
-            if (dbURI.getScheme() != null) {
-                props.put("scheme", dbURI.getScheme());
-                dbURI = new URI(stringAfterSubstr(url, ":", 2).replaceFirst(" ", ""));
-            }
-        } catch (URISyntaxException e) {
-            LOGGER.error("parseJdbcURL: URI '{}' is not well formated.", url, e);
-            return props;
-        }
-
-        // Query-Parameters
-        if (dbURI.getQuery() != null) {
-            String[] q = dbURI.getQuery().split("&");
-            for (int i = 0; i < q.length; i++) {
-                String[] t = q[i].split("=");
-                props.put(t[0], t[1]);
-            }
-            props.put("pathQuery", dbURI.getQuery());
-        }
-
-        String path = "";
-        if (dbURI.getPath() != null) {
-            String gp = dbURI.getPath();
-            String st = "/";
-            if (gp.indexOf("/") <= 1) {
-                if (substrPos(gp, st).size() > 1) {
-                    path = stringBeforeLastSubstr(gp, st) + st;
-                } else {
-                    path = stringBeforeSubstr(gp, st) + st;
-                }
-            }
-            if (dbURI.getScheme() != null && dbURI.getScheme().length() == 1) {
-                path = dbURI.getScheme() + ":" + path;
-            }
-            props.put("serverPath", path);
-        }
-        if (dbURI.getPath() != null) {
-            props.put("databaseName", stringAfterLastSubstr(dbURI.getPath(), "/"));
-        }
-        if (dbURI.getPort() != -1) {
-            props.put("portNumber", dbURI.getPort() + "");
-        }
-        if (dbURI.getHost() != null) {
-            props.put("serverName", dbURI.getHost());
-        }
-
-        return props;
-    }
-
-    /**
-     * Returns a String before the last occurrence of a substring
-     */
-    public static String stringBeforeLastSubstr(String s, String substr) {
-        List<Integer> a = substrPos(s, substr);
-        return s.substring(0, a.get(a.size() - 1));
-    }
-
-    /**
-     * Returns a String after the last occurrence of a substring
-     */
-    public static String stringAfterLastSubstr(String s, String substr) {
-        List<Integer> a = substrPos(s, substr);
-        return s.substring(a.get(a.size() - 1) + 1);
-    }
-
-    /**
-     * Returns a String after the first occurrence of a substring
-     */
-    public static String stringAfterSubstr(String s, String substr) {
-        return s.substring(s.indexOf(substr) + 1);
-    }
-
-    /**
-     * Returns a String after the n occurrence of a substring
-     */
-    public static String stringAfterSubstr(String s, String substr, int n) {
-        return s.substring(substrPos(s, substr).get(n) + 1);
-    }
-
-    /**
-     * Returns a String before the first occurrence of a substring
-     */
-    public static String stringBeforeSubstr(String s, String substr) {
-        return s.substring(0, s.indexOf(substr));
-    }
-
-    /**
-     * Returns a String before the n occurrence of a substring.
-     */
-    public static String stringBeforeSubstr(String s, String substr, int n) {
-        return s.substring(0, substrPos(s, substr).get(n));
-    }
-
-    /**
-     * Returns a list with indices of the occurrence of a substring.
-     */
-    public static List<Integer> substrPos(String s, String substr) {
-        return substrPos(s, substr, true);
-    }
-
-    /**
-     * Returns a list with indices of the occurrence of a substring.
-     */
-    public static List<Integer> substrPos(String s, String substr, boolean ignoreCase) {
-        int substrLength = substr.length();
-        int strLength = s.length();
-        List<Integer> arr = new ArrayList<>();
-
-        for (int i = 0; i < strLength - substrLength + 1; i++) {
-            if (s.regionMatches(ignoreCase, i, substr, 0, substrLength)) {
-                arr.add(i);
-            }
-        }
-        return arr;
-    }
-
-    /*
-     * (non-Javadoc)
-     *
-     * @see java.lang.Object#toString()
-     */
-    public static String filterToString(FilterCriteria filter) {
-        StringBuilder builder = new StringBuilder();
-        builder.append("FilterCriteria [itemName=");
-        builder.append(filter.getItemName());
-        builder.append(", beginDate=");
-        builder.append(filter.getBeginDate());
-        builder.append(", endDate=");
-        builder.append(filter.getEndDate());
-        builder.append(", pageNumber=");
-        builder.append(filter.getPageNumber());
-        builder.append(", pageSize=");
-        builder.append(filter.getPageSize());
-        builder.append(", operator=");
-        builder.append(filter.getOperator());
-        builder.append(", ordering=");
-        builder.append(filter.getOrdering());
-        builder.append(", state=");
-        builder.append(filter.getState());
-        builder.append("]");
-        return builder.toString();
-    }
-}
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
deleted file mode 100644 (file)
index dfa4d16..0000000
+++ /dev/null
@@ -1,273 +0,0 @@
-/**
- * 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 static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-
-import java.time.Instant;
-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.library.items.CallItem;
-import org.openhab.core.library.items.ColorItem;
-import org.openhab.core.library.items.ContactItem;
-import org.openhab.core.library.items.DateTimeItem;
-import org.openhab.core.library.items.DimmerItem;
-import org.openhab.core.library.items.ImageItem;
-import org.openhab.core.library.items.LocationItem;
-import org.openhab.core.library.items.NumberItem;
-import org.openhab.core.library.items.PlayerItem;
-import org.openhab.core.library.items.RollershutterItem;
-import org.openhab.core.library.items.StringItem;
-import org.openhab.core.library.items.SwitchItem;
-import org.openhab.core.library.types.DateTimeType;
-import org.openhab.core.library.types.DecimalType;
-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.PlayPauseType;
-import org.openhab.core.library.types.PointType;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.types.RawType;
-import org.openhab.core.library.types.RewindFastforwardType;
-import org.openhab.core.library.types.StringListType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.library.unit.SIUnits;
-import org.openhab.core.persistence.FilterCriteria;
-import org.openhab.core.persistence.FilterCriteria.Ordering;
-import org.openhab.core.types.State;
-
-/**
- * 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 testObjectAsStateReturnsValidState() {
-        State decimalType = jdbcBaseDAO.objectAsState(new NumberItem("testNumberItem"), null, 7.3);
-        assertInstanceOf(DecimalType.class, decimalType);
-        assertEquals(DecimalType.valueOf("7.3"), decimalType);
-        State quantityType = jdbcBaseDAO.objectAsState(new NumberItem("testNumberItem"), SIUnits.CELSIUS, 7.3);
-        assertInstanceOf(QuantityType.class, quantityType);
-        assertEquals(QuantityType.valueOf("7.3 °C"), quantityType);
-
-        State hsbType = jdbcBaseDAO.objectAsState(new ColorItem("testColorItem"), null, "184,100,52");
-        assertInstanceOf(HSBType.class, hsbType);
-        assertEquals(HSBType.valueOf("184,100,52"), hsbType);
-
-        State percentType = jdbcBaseDAO.objectAsState(new DimmerItem("testDimmerItem"), null, 52);
-        assertInstanceOf(PercentType.class, percentType);
-        assertEquals(PercentType.valueOf("52"), percentType);
-
-        percentType = jdbcBaseDAO.objectAsState(new RollershutterItem("testRollershutterItem"), null, 39);
-        assertInstanceOf(PercentType.class, percentType);
-        assertEquals(PercentType.valueOf("39"), percentType);
-
-        State openClosedType = jdbcBaseDAO.objectAsState(new ContactItem("testContactItem"), null, "OPEN");
-        assertInstanceOf(OpenClosedType.class, openClosedType);
-        assertThat(openClosedType, is(OpenClosedType.OPEN));
-
-        State playPauseType = jdbcBaseDAO.objectAsState(new PlayerItem("testPlayerItem"), null, "PLAY");
-        assertInstanceOf(PlayPauseType.class, playPauseType);
-        assertThat(playPauseType, is(PlayPauseType.PLAY));
-        State rewindFastforwardType = jdbcBaseDAO.objectAsState(new PlayerItem("testPlayerItem"), null, "REWIND");
-        assertInstanceOf(RewindFastforwardType.class, rewindFastforwardType);
-        assertThat(rewindFastforwardType, is(RewindFastforwardType.REWIND));
-
-        State onOffType = jdbcBaseDAO.objectAsState(new SwitchItem("testSwitchItem"), null, "ON");
-        assertInstanceOf(OnOffType.class, onOffType);
-        assertThat(onOffType, is(OnOffType.ON));
-
-        State stringListType = jdbcBaseDAO.objectAsState(new CallItem("testCallItem"), null, "0699222222,0179999998");
-        assertInstanceOf(StringListType.class, stringListType);
-        assertEquals(StringListType.valueOf("0699222222,0179999998"), stringListType);
-
-        State expectedRawType = new RawType(new byte[0], "application/octet-stream");
-        State rawType = jdbcBaseDAO.objectAsState(new ImageItem("testImageItem"), null, expectedRawType.toFullString());
-        assertInstanceOf(RawType.class, rawType);
-        assertThat(rawType, is(expectedRawType));
-
-        State pointType = jdbcBaseDAO.objectAsState(new LocationItem("testLocationItem"), null, "1,2,3");
-        assertInstanceOf(PointType.class, pointType);
-        assertEquals(PointType.valueOf("1,2,3"), pointType);
-
-        State stringType = jdbcBaseDAO.objectAsState(new StringItem("testStringItem"), null, "String");
-        assertInstanceOf(StringType.class, stringType);
-        assertEquals(StringType.valueOf("String"), stringType);
-    }
-
-    @Test
-    public void objectAsStateReturnsValiDateTimeTypeForTimestamp() {
-        State dateTimeType = jdbcBaseDAO.objectAsState(new DateTimeItem("testDateTimeItem"), null,
-                java.sql.Timestamp.valueOf("2021-02-01 23:30:02.049"));
-        assertInstanceOf(DateTimeType.class, dateTimeType);
-        assertEquals(DateTimeType.valueOf("2021-02-01T23:30:02.049"), dateTimeType);
-    }
-
-    @Test
-    public void objectAsStateReturnsValidDateTimeTypeForLocalDateTime() {
-        State dateTimeType = jdbcBaseDAO.objectAsState(new DateTimeItem("testDateTimeItem"), null,
-                LocalDateTime.parse("2021-02-01T23:30:02.049"));
-        assertInstanceOf(DateTimeType.class, dateTimeType);
-        assertEquals(DateTimeType.valueOf("2021-02-01T23:30:02.049"), dateTimeType);
-    }
-
-    @Test
-    public void objectAsStateReturnsValidDateTimeTypeForLong() {
-        State dateTimeType = jdbcBaseDAO.objectAsState(new DateTimeItem("testDateTimeItem"), null,
-                Long.valueOf("1612222202049"));
-        assertInstanceOf(DateTimeType.class, dateTimeType);
-        assertEquals(
-                new DateTimeType(
-                        ZonedDateTime.ofInstant(Instant.parse("2021-02-01T23:30:02.049Z"), ZoneId.systemDefault())),
-                dateTimeType);
-    }
-
-    @Test
-    public void objectAsStateReturnsValidDateTimeTypeForSqlDate() {
-        State dateTimeType = jdbcBaseDAO.objectAsState(new DateTimeItem("testDateTimeItem"), null,
-                java.sql.Date.valueOf("2021-02-01"));
-        assertInstanceOf(DateTimeType.class, dateTimeType);
-        assertEquals(DateTimeType.valueOf("2021-02-01T00:00:00.000"), dateTimeType);
-    }
-
-    @Test
-    public void objectAsStateReturnsValidDateTimeTypeForInstant() {
-        State dateTimeType = jdbcBaseDAO.objectAsState(new DateTimeItem("testDateTimeItem"), null,
-                Instant.parse("2021-02-01T23:30:02.049Z"));
-        assertInstanceOf(DateTimeType.class, dateTimeType);
-        assertEquals(
-                new DateTimeType(
-                        ZonedDateTime.ofInstant(Instant.parse("2021-02-01T23:30:02.049Z"), ZoneId.systemDefault())),
-                dateTimeType);
-    }
-
-    @Test
-    public void objectAsStateReturnsValidDateTimeTypeForString() {
-        State dateTimeType = jdbcBaseDAO.objectAsState(new DateTimeItem("testDateTimeItem"), null,
-                "2021-02-01 23:30:02.049");
-        assertInstanceOf(DateTimeType.class, dateTimeType);
-        assertEquals(DateTimeType.valueOf("2021-02-01T23:30:02.049"), dateTimeType);
-    }
-
-    @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("TRUNCATE TABLE " + 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);
-    }
-}
index 257bf97acf5689e4c1b473924b932073d2860f09..6639459fab8c70208393a59a5588bbc1ae947722 100644 (file)
@@ -30,7 +30,7 @@ import org.mockito.Mockito;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.mockito.junit.jupiter.MockitoSettings;
 import org.mockito.quality.Strictness;
-import org.openhab.persistence.jdbc.dto.ItemVO;
+import org.openhab.persistence.jdbc.internal.dto.ItemVO;
 import org.slf4j.LoggerFactory;
 
 import ch.qos.logback.classic.Level;
diff --git a/bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/internal/db/JdbcBaseDAOTest.java b/bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/internal/db/JdbcBaseDAOTest.java
new file mode 100644 (file)
index 0000000..eb3528e
--- /dev/null
@@ -0,0 +1,273 @@
+/**
+ * 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.db;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+import java.time.Instant;
+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.library.items.CallItem;
+import org.openhab.core.library.items.ColorItem;
+import org.openhab.core.library.items.ContactItem;
+import org.openhab.core.library.items.DateTimeItem;
+import org.openhab.core.library.items.DimmerItem;
+import org.openhab.core.library.items.ImageItem;
+import org.openhab.core.library.items.LocationItem;
+import org.openhab.core.library.items.NumberItem;
+import org.openhab.core.library.items.PlayerItem;
+import org.openhab.core.library.items.RollershutterItem;
+import org.openhab.core.library.items.StringItem;
+import org.openhab.core.library.items.SwitchItem;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.DecimalType;
+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.PlayPauseType;
+import org.openhab.core.library.types.PointType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.RawType;
+import org.openhab.core.library.types.RewindFastforwardType;
+import org.openhab.core.library.types.StringListType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.persistence.FilterCriteria;
+import org.openhab.core.persistence.FilterCriteria.Ordering;
+import org.openhab.core.types.State;
+
+/**
+ * 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 testObjectAsStateReturnsValidState() {
+        State decimalType = jdbcBaseDAO.objectAsState(new NumberItem("testNumberItem"), null, 7.3);
+        assertInstanceOf(DecimalType.class, decimalType);
+        assertEquals(DecimalType.valueOf("7.3"), decimalType);
+        State quantityType = jdbcBaseDAO.objectAsState(new NumberItem("testNumberItem"), SIUnits.CELSIUS, 7.3);
+        assertInstanceOf(QuantityType.class, quantityType);
+        assertEquals(QuantityType.valueOf("7.3 °C"), quantityType);
+
+        State hsbType = jdbcBaseDAO.objectAsState(new ColorItem("testColorItem"), null, "184,100,52");
+        assertInstanceOf(HSBType.class, hsbType);
+        assertEquals(HSBType.valueOf("184,100,52"), hsbType);
+
+        State percentType = jdbcBaseDAO.objectAsState(new DimmerItem("testDimmerItem"), null, 52);
+        assertInstanceOf(PercentType.class, percentType);
+        assertEquals(PercentType.valueOf("52"), percentType);
+
+        percentType = jdbcBaseDAO.objectAsState(new RollershutterItem("testRollershutterItem"), null, 39);
+        assertInstanceOf(PercentType.class, percentType);
+        assertEquals(PercentType.valueOf("39"), percentType);
+
+        State openClosedType = jdbcBaseDAO.objectAsState(new ContactItem("testContactItem"), null, "OPEN");
+        assertInstanceOf(OpenClosedType.class, openClosedType);
+        assertThat(openClosedType, is(OpenClosedType.OPEN));
+
+        State playPauseType = jdbcBaseDAO.objectAsState(new PlayerItem("testPlayerItem"), null, "PLAY");
+        assertInstanceOf(PlayPauseType.class, playPauseType);
+        assertThat(playPauseType, is(PlayPauseType.PLAY));
+        State rewindFastforwardType = jdbcBaseDAO.objectAsState(new PlayerItem("testPlayerItem"), null, "REWIND");
+        assertInstanceOf(RewindFastforwardType.class, rewindFastforwardType);
+        assertThat(rewindFastforwardType, is(RewindFastforwardType.REWIND));
+
+        State onOffType = jdbcBaseDAO.objectAsState(new SwitchItem("testSwitchItem"), null, "ON");
+        assertInstanceOf(OnOffType.class, onOffType);
+        assertThat(onOffType, is(OnOffType.ON));
+
+        State stringListType = jdbcBaseDAO.objectAsState(new CallItem("testCallItem"), null, "0699222222,0179999998");
+        assertInstanceOf(StringListType.class, stringListType);
+        assertEquals(StringListType.valueOf("0699222222,0179999998"), stringListType);
+
+        State expectedRawType = new RawType(new byte[0], "application/octet-stream");
+        State rawType = jdbcBaseDAO.objectAsState(new ImageItem("testImageItem"), null, expectedRawType.toFullString());
+        assertInstanceOf(RawType.class, rawType);
+        assertThat(rawType, is(expectedRawType));
+
+        State pointType = jdbcBaseDAO.objectAsState(new LocationItem("testLocationItem"), null, "1,2,3");
+        assertInstanceOf(PointType.class, pointType);
+        assertEquals(PointType.valueOf("1,2,3"), pointType);
+
+        State stringType = jdbcBaseDAO.objectAsState(new StringItem("testStringItem"), null, "String");
+        assertInstanceOf(StringType.class, stringType);
+        assertEquals(StringType.valueOf("String"), stringType);
+    }
+
+    @Test
+    public void objectAsStateReturnsValiDateTimeTypeForTimestamp() {
+        State dateTimeType = jdbcBaseDAO.objectAsState(new DateTimeItem("testDateTimeItem"), null,
+                java.sql.Timestamp.valueOf("2021-02-01 23:30:02.049"));
+        assertInstanceOf(DateTimeType.class, dateTimeType);
+        assertEquals(DateTimeType.valueOf("2021-02-01T23:30:02.049"), dateTimeType);
+    }
+
+    @Test
+    public void objectAsStateReturnsValidDateTimeTypeForLocalDateTime() {
+        State dateTimeType = jdbcBaseDAO.objectAsState(new DateTimeItem("testDateTimeItem"), null,
+                LocalDateTime.parse("2021-02-01T23:30:02.049"));
+        assertInstanceOf(DateTimeType.class, dateTimeType);
+        assertEquals(DateTimeType.valueOf("2021-02-01T23:30:02.049"), dateTimeType);
+    }
+
+    @Test
+    public void objectAsStateReturnsValidDateTimeTypeForLong() {
+        State dateTimeType = jdbcBaseDAO.objectAsState(new DateTimeItem("testDateTimeItem"), null,
+                Long.valueOf("1612222202049"));
+        assertInstanceOf(DateTimeType.class, dateTimeType);
+        assertEquals(
+                new DateTimeType(
+                        ZonedDateTime.ofInstant(Instant.parse("2021-02-01T23:30:02.049Z"), ZoneId.systemDefault())),
+                dateTimeType);
+    }
+
+    @Test
+    public void objectAsStateReturnsValidDateTimeTypeForSqlDate() {
+        State dateTimeType = jdbcBaseDAO.objectAsState(new DateTimeItem("testDateTimeItem"), null,
+                java.sql.Date.valueOf("2021-02-01"));
+        assertInstanceOf(DateTimeType.class, dateTimeType);
+        assertEquals(DateTimeType.valueOf("2021-02-01T00:00:00.000"), dateTimeType);
+    }
+
+    @Test
+    public void objectAsStateReturnsValidDateTimeTypeForInstant() {
+        State dateTimeType = jdbcBaseDAO.objectAsState(new DateTimeItem("testDateTimeItem"), null,
+                Instant.parse("2021-02-01T23:30:02.049Z"));
+        assertInstanceOf(DateTimeType.class, dateTimeType);
+        assertEquals(
+                new DateTimeType(
+                        ZonedDateTime.ofInstant(Instant.parse("2021-02-01T23:30:02.049Z"), ZoneId.systemDefault())),
+                dateTimeType);
+    }
+
+    @Test
+    public void objectAsStateReturnsValidDateTimeTypeForString() {
+        State dateTimeType = jdbcBaseDAO.objectAsState(new DateTimeItem("testDateTimeItem"), null,
+                "2021-02-01 23:30:02.049");
+        assertInstanceOf(DateTimeType.class, dateTimeType);
+        assertEquals(DateTimeType.valueOf("2021-02-01T23:30:02.049"), dateTimeType);
+    }
+
+    @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("TRUNCATE TABLE " + 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);
+    }
+}