From: Jacob Laursen Date: Wed, 16 Nov 2022 08:28:46 +0000 (+0100) Subject: [jdbc] Improve error handling safety (#13726) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=0873dd3ddff9db767c1970e5c9afaf7ca4c88579;p=openhab-addons.git [jdbc] Improve error handling safety (#13726) * Wrap YankSQLException into checked exception for all Yank calls * Move files into internal Signed-off-by: Jacob Laursen --- 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 index e644138ae2..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/ItemTableCheckEntry.java +++ /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 index 3553aee29d..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/ItemTableCheckEntryStatus.java +++ /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 index 6923430aa7..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/console/JdbcCommandExtension.java +++ /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 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 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 getUsages() { - return Arrays.asList( - buildCommandUsage(CMD_TABLES + " " + SUBCMD_TABLES_LIST + " [" + PARAMETER_ALL + "]", - "list tables (all = include valid)"), - buildCommandUsage( - CMD_TABLES + " " + SUBCMD_TABLES_CLEAN + " []" + " [" + 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 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 index 393635d968..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcBaseDAO.java +++ /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 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=} - * 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........................ - * 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 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 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 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 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 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> 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. 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> 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> 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: 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 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 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 index f0d12bff03..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcDerbyDAO.java +++ /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 doGetHistItemFilterQuery(Item item, FilterCriteria filter, int numberDecimalcount, - String table, String name, ZoneId timeZone) { - String sql = histItemFilterQueryProvider(filter, numberDecimalcount, table, name, timeZone); - List 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> 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. 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 index 140f536672..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcH2DAO.java +++ /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 index fe9eb8f78e..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcHsqldbDAO.java +++ /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 index 740cc0f73d..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcMariadbDAO.java +++ /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 index 361b177995..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcMysqlDAO.java +++ /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 index 974fd8fd0b..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcPostgresqlDAO.java +++ /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 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 index 4559e1b183..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcSqliteDAO.java +++ /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 index c27503274e..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/db/JdbcTimescaledbDAO.java +++ /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 index af4a03ba39..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/ItemVO.java +++ /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 index 9f8f7da3cb..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/ItemsVO.java +++ /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 index be1a2e32cd..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/JdbcHistoricItem.java +++ /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 index 0e91c66b1a..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/dto/JdbcPersistenceItemInfo.java +++ /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 index 0000000000..707ae0e57a --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/ItemTableCheckEntry.java @@ -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 index 0000000000..ce70ed6c47 --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/ItemTableCheckEntryStatus.java @@ -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"; + } + } +} diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcConfiguration.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcConfiguration.java index b67fb9a31a..5ce07056f3 100644 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcConfiguration.java +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcConfiguration.java @@ -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 configuration; diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcMapper.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcMapper.java index cae0a5346d..27422eedbb 100644 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcMapper.java +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcMapper.java @@ -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 getItemIDTableNames() { + private List getItemIDTableNames() throws JdbcSQLException { logger.debug("JDBC::getItemIDTableNames"); long timerStart = System.currentTimeMillis(); List vo = conf.getDBDAO().doGetItemIDTableNames(new ItemsVO()); @@ -155,7 +155,7 @@ public class JdbcMapper { return vo; } - protected List getItemTables() { + protected List 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 vol) { + private void updateItemTableNames(List 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 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; diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java index e0fbf11552..3e4783f32b 100644 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/JdbcPersistenceService.java @@ -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 getCheckedEntries() { + public List getCheckedEntries() throws JdbcSQLException { List 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; diff --git a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/NamingStrategy.java b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/NamingStrategy.java index 3fcf9f065f..8d4e41500e 100644 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/NamingStrategy.java +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/NamingStrategy.java @@ -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 index 0000000000..c4d1f1fcd1 --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/console/JdbcCommandExtension.java @@ -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 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 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 getUsages() { + return Arrays.asList( + buildCommandUsage(CMD_TABLES + " " + SUBCMD_TABLES_LIST + " [" + PARAMETER_ALL + "]", + "list tables (all = include valid)"), + buildCommandUsage( + CMD_TABLES + " " + SUBCMD_TABLES_CLEAN + " []" + " [" + 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 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 index 0000000000..b8a68a26bf --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcBaseDAO.java @@ -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 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=} + * 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........................ + * 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 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 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 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 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 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> 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. 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> 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> 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: 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 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 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 index 0000000000..818fca918a --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcDerbyDAO.java @@ -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 doGetHistItemFilterQuery(Item item, FilterCriteria filter, int numberDecimalcount, + String table, String name, ZoneId timeZone) throws JdbcSQLException { + String sql = histItemFilterQueryProvider(filter, numberDecimalcount, table, name, timeZone); + List 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> 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. 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 index 0000000000..2bbdcf7ebf --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcH2DAO.java @@ -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 index 0000000000..35160585bb --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcHsqldbDAO.java @@ -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 index 0000000000..f793270af6 --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcMariadbDAO.java @@ -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 index 0000000000..dc83b69e5a --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcMysqlDAO.java @@ -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 index 0000000000..7a8ab72e88 --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcPostgresqlDAO.java @@ -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 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 index 0000000000..fc2888e795 --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcSqliteDAO.java @@ -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 index 0000000000..f81de85c80 --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/db/JdbcTimescaledbDAO.java @@ -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 index 0000000000..2fca6ef691 --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/ItemVO.java @@ -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 index 0000000000..8bc23e5243 --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/ItemsVO.java @@ -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 index 0000000000..a9ae3eb74b --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/JdbcHistoricItem.java @@ -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 index 0000000000..bc71959c4e --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/dto/JdbcPersistenceItemInfo.java @@ -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 index 0000000000..3647cdbdbe --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/exceptions/JdbcException.java @@ -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 index 0000000000..6b8e4a6971 --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/exceptions/JdbcSQLException.java @@ -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 index 0000000000..4ac2a08ded --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/utils/DbMetaData.java @@ -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 index 0000000000..41a9589317 --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/utils/MovingAverage.java @@ -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 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 index 0000000000..262224a7da --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/internal/utils/StringUtilsExt.java @@ -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); + } + + /** + * JDBC-URI Examples:
+ * jdbc:dbShortcut:c:/dev/databaseName
+ * jdbc:dbShortcut:scheme:c:/dev/databaseName
+ * jdbc:dbShortcut:scheme:c:\\dev\\databaseName
+ * jdbc:dbShortcut:./databaseName
+ * jdbc:dbShortcut:/databaseName
+ * jdbc:dbShortcut:~/databaseName
+ * jdbc:dbShortcut:/path/databaseName.db
+ * jdbc:dbShortcut:./../../path/databaseName
+ * jdbc:dbShortcut:scheme:./path/../path/databaseName;param1=true;
+ * jdbc:dbShortcut://192.168.0.145:3306/databaseName?param1=false¶m2=true + *

+ * + * @param url JDBC-URI + * @param def Predefined Properties Object + * @return A merged Properties Object may contain:
+ * parseValid (mandatory)
+ * scheme
+ * serverPath
+ * dbShortcut
+ * databaseName
+ * portNumber
+ * serverName
+ * pathQuery
+ */ + 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 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 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 substrPos(String s, String substr) { + return substrPos(s, substr, true); + } + + /** + * Returns a list with indices of the occurrence of a substring. + */ + public static List substrPos(String s, String substr, boolean ignoreCase) { + int substrLength = substr.length(); + int strLength = s.length(); + List 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 index b15e5b724c..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/DbMetaData.java +++ /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 index a8d5ae0216..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/MovingAverage.java +++ /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 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 index 1894126da4..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/main/java/org/openhab/persistence/jdbc/utils/StringUtilsExt.java +++ /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); - } - - /** - * JDBC-URI Examples:
- * jdbc:dbShortcut:c:/dev/databaseName
- * jdbc:dbShortcut:scheme:c:/dev/databaseName
- * jdbc:dbShortcut:scheme:c:\\dev\\databaseName
- * jdbc:dbShortcut:./databaseName
- * jdbc:dbShortcut:/databaseName
- * jdbc:dbShortcut:~/databaseName
- * jdbc:dbShortcut:/path/databaseName.db
- * jdbc:dbShortcut:./../../path/databaseName
- * jdbc:dbShortcut:scheme:./path/../path/databaseName;param1=true;
- * jdbc:dbShortcut://192.168.0.145:3306/databaseName?param1=false¶m2=true - *

- * - * @param url JDBC-URI - * @param def Predefined Properties Object - * @return A merged Properties Object may contain:
- * parseValid (mandatory)
- * scheme
- * serverPath
- * dbShortcut
- * databaseName
- * portNumber
- * serverName
- * pathQuery
- */ - 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 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 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 substrPos(String s, String substr) { - return substrPos(s, substr, true); - } - - /** - * Returns a list with indices of the occurrence of a substring. - */ - public static List substrPos(String s, String substr, boolean ignoreCase) { - int substrLength = substr.length(); - int strLength = s.length(); - List 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 index dfa4d162a2..0000000000 --- a/bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/db/JdbcBaseDAOTest.java +++ /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); - } -} diff --git a/bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/internal/NamingStrategyTest.java b/bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/internal/NamingStrategyTest.java index 257bf97acf..6639459fab 100644 --- a/bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/internal/NamingStrategyTest.java +++ b/bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/internal/NamingStrategyTest.java @@ -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 index 0000000000..eb3528e998 --- /dev/null +++ b/bundles/org.openhab.persistence.jdbc/src/test/java/org/openhab/persistence/jdbc/internal/db/JdbcBaseDAOTest.java @@ -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); + } +}