2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.persistence.jdbc.internal.db;
15 import java.math.BigDecimal;
16 import java.time.Instant;
17 import java.time.LocalDateTime;
18 import java.time.ZoneId;
19 import java.time.ZonedDateTime;
20 import java.time.format.DateTimeFormatter;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.List;
25 import java.util.Objects;
26 import java.util.Properties;
27 import java.util.stream.Collectors;
29 import javax.measure.Quantity;
30 import javax.measure.Unit;
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.knowm.yank.Yank;
35 import org.knowm.yank.exceptions.YankSQLException;
36 import org.openhab.core.items.GroupItem;
37 import org.openhab.core.items.Item;
38 import org.openhab.core.library.items.ColorItem;
39 import org.openhab.core.library.items.ContactItem;
40 import org.openhab.core.library.items.DateTimeItem;
41 import org.openhab.core.library.items.DimmerItem;
42 import org.openhab.core.library.items.ImageItem;
43 import org.openhab.core.library.items.NumberItem;
44 import org.openhab.core.library.items.PlayerItem;
45 import org.openhab.core.library.items.RollershutterItem;
46 import org.openhab.core.library.items.SwitchItem;
47 import org.openhab.core.library.types.DateTimeType;
48 import org.openhab.core.library.types.DecimalType;
49 import org.openhab.core.library.types.HSBType;
50 import org.openhab.core.library.types.PercentType;
51 import org.openhab.core.library.types.QuantityType;
52 import org.openhab.core.library.types.RawType;
53 import org.openhab.core.library.unit.Units;
54 import org.openhab.core.persistence.FilterCriteria;
55 import org.openhab.core.persistence.FilterCriteria.Ordering;
56 import org.openhab.core.persistence.HistoricItem;
57 import org.openhab.core.types.State;
58 import org.openhab.core.types.TypeParser;
59 import org.openhab.persistence.jdbc.internal.dto.Column;
60 import org.openhab.persistence.jdbc.internal.dto.ItemVO;
61 import org.openhab.persistence.jdbc.internal.dto.ItemsVO;
62 import org.openhab.persistence.jdbc.internal.dto.JdbcHistoricItem;
63 import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
64 import org.openhab.persistence.jdbc.internal.utils.DbMetaData;
65 import org.openhab.persistence.jdbc.internal.utils.StringUtilsExt;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
70 * Default Database Configuration class.
72 * @author Helmut Lehmeyer - Initial contribution
75 public class JdbcBaseDAO {
76 private final Logger logger = LoggerFactory.getLogger(JdbcBaseDAO.class);
78 public final Properties databaseProps = new Properties();
79 protected String urlSuffix = "";
80 public final Map<String, String> sqlTypes = new HashMap<>();
82 // Get Database Meta data
83 protected @Nullable DbMetaData dbMeta;
85 protected String sqlPingDB = "SELECT 1";
86 protected String sqlGetDB = "SELECT DATABASE()";
87 protected String sqlIfTableExists = "SHOW TABLES LIKE '#searchTable#'";
88 protected String sqlCreateNewEntryInItemsTable = "INSERT INTO #itemsManageTable# (ItemName) VALUES ('#itemname#')";
89 protected String sqlCreateItemsTableIfNot = "CREATE TABLE IF NOT EXISTS #itemsManageTable# (ItemId INT NOT NULL AUTO_INCREMENT,#colname# #coltype# NOT NULL,PRIMARY KEY (ItemId))";
90 protected String sqlDropItemsTableIfExists = "DROP TABLE IF EXISTS #itemsManageTable#";
91 protected String sqlDropTable = "DROP TABLE #tableName#";
92 protected String sqlDeleteItemsEntry = "DELETE FROM #itemsManageTable# WHERE ItemName='#itemname#'";
93 protected String sqlGetItemIDTableNames = "SELECT ItemId, ItemName FROM #itemsManageTable#";
94 protected String sqlGetItemTables = "SELECT table_name FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_schema='#jdbcUriDatabaseName#' AND NOT table_name='#itemsManageTable#'";
95 protected String sqlGetTableColumnTypes = "SELECT column_name, column_type, is_nullable FROM information_schema.columns WHERE table_schema='#jdbcUriDatabaseName#' AND table_name='#tableName#'";
96 protected String sqlCreateItemTable = "CREATE TABLE IF NOT EXISTS #tableName# (time #tablePrimaryKey# NOT NULL, value #dbType#, PRIMARY KEY(time))";
97 protected String sqlAlterTableColumn = "ALTER TABLE #tableName# MODIFY COLUMN #columnName# #columnType#";
98 protected String sqlInsertItemValue = "INSERT INTO #tableName# (time, value) VALUES( #tablePrimaryValue#, ? ) ON DUPLICATE KEY UPDATE VALUE= ?";
99 protected String sqlGetRowCount = "SELECT COUNT(*) FROM #tableName#";
104 public JdbcBaseDAO() {
110 * ## Get high precision by fractal seconds, examples ##
112 * mysql > 5.5 + mariadb > 5.2:
113 * DROP TABLE FractionalSeconds;
114 * CREATE TABLE FractionalSeconds (time TIMESTAMP(3), value TIMESTAMP(3));
115 * INSERT INTO FractionalSeconds (time, value) VALUES( NOW(3), '1999-01-09 20:11:11.126' );
116 * SELECT time FROM FractionalSeconds ORDER BY time DESC LIMIT 1;
118 * mysql <= 5.5 + mariadb <= 5.2: !!! NO high precision and fractal seconds !!!
119 * DROP TABLE FractionalSeconds;
120 * CREATE TABLE FractionalSeconds (time TIMESTAMP, value TIMESTAMP);
121 * INSERT INTO FractionalSeconds (time, value) VALUES( NOW(), '1999-01-09 20:11:11.126' );
122 * SELECT time FROM FractionalSeconds ORDER BY time DESC LIMIT 1;
125 * DROP TABLE FractionalSeconds;
126 * CREATE TABLE FractionalSeconds (time TIMESTAMP, value TIMESTAMP);
127 * INSERT INTO FractionalSeconds (time, value) VALUES( CURRENT_TIMESTAMP, '1999-01-09 20:11:11.126' );
128 * SELECT time, value FROM FractionalSeconds;
130 * H2 + postgreSQL + hsqldb:
131 * DROP TABLE FractionalSeconds;
132 * CREATE TABLE FractionalSeconds (time TIMESTAMP, value TIMESTAMP);
133 * INSERT INTO FractionalSeconds (time, value) VALUES( NOW(), '1999-01-09 20:11:11.126' );
134 * SELECT time, value FROM FractionalSeconds;
137 * DROP TABLE FractionalSeconds;
138 * CREATE TABLE FractionalSeconds (time TIMESTAMP, value TIMESTAMP);
139 * INSERT INTO FractionalSeconds (time, value) VALUES( strftime('%Y-%m-%d %H:%M:%f' , 'now' , 'localtime'),
140 * '1999-01-09 20:11:11.124' );
141 * SELECT time FROM FractionalSeconds ORDER BY time DESC LIMIT 1;
146 * INFO: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
148 private void initSqlTypes() {
149 logger.debug("JDBC::initSqlTypes: Initialize the type array");
150 sqlTypes.put("CALLITEM", "VARCHAR(200)");
151 sqlTypes.put("COLORITEM", "VARCHAR(70)");
152 sqlTypes.put("CONTACTITEM", "VARCHAR(6)");
153 sqlTypes.put("DATETIMEITEM", "TIMESTAMP");
154 sqlTypes.put("DIMMERITEM", "TINYINT");
155 sqlTypes.put("IMAGEITEM", "VARCHAR(65500)");// jdbc max 21845
156 sqlTypes.put("LOCATIONITEM", "VARCHAR(50)");
157 sqlTypes.put("NUMBERITEM", "DOUBLE");
158 sqlTypes.put("PLAYERITEM", "VARCHAR(20)");
159 sqlTypes.put("ROLLERSHUTTERITEM", "TINYINT");
160 sqlTypes.put("STRINGITEM", "VARCHAR(65500)");// jdbc max 21845
161 sqlTypes.put("SWITCHITEM", "VARCHAR(6)");
162 sqlTypes.put("tablePrimaryKey", "TIMESTAMP");
163 sqlTypes.put("tablePrimaryValue", "NOW()");
167 * INFO: https://github.com/brettwooldridge/HikariCP
169 * driverClassName (used with jdbcUrl):
170 * Derby: org.apache.derby.jdbc.EmbeddedDriver
172 * HSQLDB: org.hsqldb.jdbcDriver
173 * Jaybird: org.firebirdsql.jdbc.FBDriver
174 * MariaDB: org.mariadb.jdbc.Driver
175 * MySQL: com.mysql.cj.jdbc.Driver
176 * MaxDB: com.sap.dbtech.jdbc.DriverSapDB
177 * PostgreSQL: org.postgresql.Driver
178 * SyBase: com.sybase.jdbc3.jdbc.SybDriver
179 * SqLite: org.sqlite.JDBC
181 * dataSourceClassName (for alternative Configuration):
182 * Derby: org.apache.derby.jdbc.ClientDataSource
183 * H2: org.h2.jdbcx.JdbcDataSource
184 * HSQLDB: org.hsqldb.jdbc.JDBCDataSource
185 * Jaybird: org.firebirdsql.pool.FBSimpleDataSource
186 * MariaDB, MySQL: org.mariadb.jdbc.MySQLDataSource
187 * MaxDB: com.sap.dbtech.jdbc.DriverSapDB
188 * PostgreSQL: org.postgresql.ds.PGSimpleDataSource
189 * SyBase: com.sybase.jdbc4.jdbc.SybDataSource
190 * SqLite: org.sqlite.SQLiteDataSource
192 * HikariPool - configuration Example:
193 * allowPoolSuspension.............false
194 * autoCommit......................true
195 * catalog.........................
196 * connectionInitSql...............
197 * connectionTestQuery.............
198 * connectionTimeout...............30000
199 * dataSource......................
200 * dataSourceClassName.............
201 * dataSourceJNDI..................
202 * dataSourceProperties............{password=<masked>}
203 * driverClassName.................
204 * healthCheckProperties...........{}
205 * healthCheckRegistry.............
206 * idleTimeout.....................600000
207 * initializationFailFast..........true
208 * isolateInternalQueries..........false
209 * jdbc4ConnectionTest.............false
210 * jdbcUrl.........................jdbc:mysql://192.168.0.1:3306/test
211 * leakDetectionThreshold..........0
212 * maxLifetime.....................1800000
213 * maximumPoolSize.................10
214 * metricRegistry..................
215 * metricsTrackerFactory...........
216 * minimumIdle.....................10
217 * password........................<masked>
218 * poolName........................HikariPool-0
219 * readOnly........................false
220 * registerMbeans..................false
221 * scheduledExecutorService........
222 * threadFactory...................
223 * transactionIsolation............
224 * username........................xxxx
225 * validationTimeout...............5000
227 private void initDbProps() {
228 // databaseProps.setProperty("dataSource.url", "jdbc:mysql://192.168.0.1:3306/test");
229 // databaseProps.setProperty("dataSource.user", "test");
230 // databaseProps.setProperty("dataSource.password", "test");
232 // Most relevant Performance values
233 // maximumPoolSize to 20, minimumIdle to 5, and idleTimeout to 2 minutes.
234 // databaseProps.setProperty("maximumPoolSize", ""+maximumPoolSize);
235 // databaseProps.setProperty("minimumIdle", ""+minimumIdle);
236 // databaseProps.setProperty("idleTimeout", ""+idleTimeout);
237 // databaseProps.setProperty("connectionTimeout",""+connectionTimeout);
238 // databaseProps.setProperty("idleTimeout", ""+idleTimeout);
239 // databaseProps.setProperty("maxLifetime", ""+maxLifetime);
240 // databaseProps.setProperty("validationTimeout",""+validationTimeout);
243 public void initAfterFirstDbConnection() {
244 logger.debug("JDBC::initAfterFirstDbConnection: Initializing step, after db is connected.");
245 // Initialize sqlTypes, depending on DB version for example
246 dbMeta = new DbMetaData();// get DB information
249 public Properties getConnectionProperties() {
250 return new Properties(this.databaseProps);
256 public @Nullable Integer doPingDB() throws JdbcSQLException {
258 final @Nullable Integer result = Yank.queryScalar(sqlPingDB, Integer.class, null);
260 } catch (YankSQLException e) {
261 throw new JdbcSQLException(e);
265 public @Nullable String doGetDB() throws JdbcSQLException {
267 final @Nullable String result = Yank.queryScalar(sqlGetDB, String.class, null);
269 } catch (YankSQLException e) {
270 throw new JdbcSQLException(e);
274 public boolean doIfTableExists(ItemsVO vo) throws JdbcSQLException {
275 String sql = StringUtilsExt.replaceArrayMerge(sqlIfTableExists, new String[] { "#searchTable#" },
276 new String[] { vo.getItemsManageTable() });
277 logger.debug("JDBC::doIfTableExists sql={}", sql);
279 final @Nullable String result = Yank.queryScalar(sql, String.class, null);
280 return Objects.nonNull(result);
281 } catch (YankSQLException e) {
282 throw new JdbcSQLException(e);
286 public boolean doIfTableExists(String tableName) throws JdbcSQLException {
287 String sql = StringUtilsExt.replaceArrayMerge(sqlIfTableExists, new String[] { "#searchTable#" },
288 new String[] { tableName });
289 logger.debug("JDBC::doIfTableExists sql={}", sql);
291 final @Nullable String result = Yank.queryScalar(sql, String.class, null);
292 return Objects.nonNull(result);
293 } catch (YankSQLException e) {
294 throw new JdbcSQLException(e);
298 public Long doCreateNewEntryInItemsTable(ItemsVO vo) throws JdbcSQLException {
299 String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
300 new String[] { "#itemsManageTable#", "#itemname#" },
301 new String[] { vo.getItemsManageTable(), vo.getItemName() });
302 logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
304 return Yank.insert(sql, null);
305 } catch (YankSQLException e) {
306 throw new JdbcSQLException(e);
310 public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) throws JdbcSQLException {
311 String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
312 new String[] { "#itemsManageTable#", "#colname#", "#coltype#" },
313 new String[] { vo.getItemsManageTable(), vo.getColname(), vo.getColtype() });
314 logger.debug("JDBC::doCreateItemsTableIfNot sql={}", sql);
316 Yank.execute(sql, null);
317 } catch (YankSQLException e) {
318 throw new JdbcSQLException(e);
323 public ItemsVO doDropItemsTableIfExists(ItemsVO vo) throws JdbcSQLException {
324 String sql = StringUtilsExt.replaceArrayMerge(sqlDropItemsTableIfExists, new String[] { "#itemsManageTable#" },
325 new String[] { vo.getItemsManageTable() });
326 logger.debug("JDBC::doDropItemsTableIfExists sql={}", sql);
328 Yank.execute(sql, null);
329 } catch (YankSQLException e) {
330 throw new JdbcSQLException(e);
335 public void doDropTable(String tableName) throws JdbcSQLException {
336 String sql = StringUtilsExt.replaceArrayMerge(sqlDropTable, new String[] { "#tableName#" },
337 new String[] { tableName });
338 logger.debug("JDBC::doDropTable sql={}", sql);
340 Yank.execute(sql, null);
341 } catch (YankSQLException e) {
342 throw new JdbcSQLException(e);
346 public void doDeleteItemsEntry(ItemsVO vo) throws JdbcSQLException {
347 String sql = StringUtilsExt.replaceArrayMerge(sqlDeleteItemsEntry,
348 new String[] { "#itemsManageTable#", "#itemname#" },
349 new String[] { vo.getItemsManageTable(), vo.getItemName() });
350 logger.debug("JDBC::doDeleteItemsEntry sql={}", sql);
352 Yank.execute(sql, null);
353 } catch (YankSQLException e) {
354 throw new JdbcSQLException(e);
358 public List<ItemsVO> doGetItemIDTableNames(ItemsVO vo) throws JdbcSQLException {
359 String sql = StringUtilsExt.replaceArrayMerge(sqlGetItemIDTableNames, new String[] { "#itemsManageTable#" },
360 new String[] { vo.getItemsManageTable() });
361 logger.debug("JDBC::doGetItemIDTableNames sql={}", sql);
363 return Yank.queryBeanList(sql, ItemsVO.class, null);
364 } catch (YankSQLException e) {
365 throw new JdbcSQLException(e);
369 public List<ItemsVO> doGetItemTables(ItemsVO vo) throws JdbcSQLException {
370 String sql = StringUtilsExt.replaceArrayMerge(sqlGetItemTables,
371 new String[] { "#jdbcUriDatabaseName#", "#itemsManageTable#" },
372 new String[] { vo.getJdbcUriDatabaseName(), vo.getItemsManageTable() });
373 logger.debug("JDBC::doGetItemTables sql={}", sql);
375 return Yank.queryBeanList(sql, ItemsVO.class, null);
376 } catch (YankSQLException e) {
377 throw new JdbcSQLException(e);
381 public List<Column> doGetTableColumns(ItemsVO vo) throws JdbcSQLException {
382 String sql = StringUtilsExt.replaceArrayMerge(sqlGetTableColumnTypes,
383 new String[] { "#jdbcUriDatabaseName#", "#tableName#" },
384 new String[] { vo.getJdbcUriDatabaseName(), vo.getTableName() });
385 logger.debug("JDBC::doGetTableColumns sql={}", sql);
387 return Yank.queryBeanList(sql, Column.class, null);
388 } catch (YankSQLException e) {
389 throw new JdbcSQLException(e);
396 public void doUpdateItemTableNames(List<ItemVO> vol) throws JdbcSQLException {
397 logger.debug("JDBC::doUpdateItemTableNames vol.size = {}", vol.size());
398 for (ItemVO itemTable : vol) {
399 String sql = updateItemTableNamesProvider(itemTable);
401 Yank.execute(sql, null);
402 } catch (YankSQLException e) {
403 throw new JdbcSQLException(e);
408 public void doCreateItemTable(ItemVO vo) throws JdbcSQLException {
409 String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemTable,
410 new String[] { "#tableName#", "#dbType#", "#tablePrimaryKey#" },
411 new String[] { vo.getTableName(), vo.getDbType(), sqlTypes.get("tablePrimaryKey") });
412 logger.debug("JDBC::doCreateItemTable sql={}", sql);
414 Yank.execute(sql, null);
415 } catch (YankSQLException e) {
416 throw new JdbcSQLException(e);
420 public void doAlterTableColumn(String tableName, String columnName, String columnType, boolean nullable)
421 throws JdbcSQLException {
422 String sql = StringUtilsExt.replaceArrayMerge(sqlAlterTableColumn,
423 new String[] { "#tableName#", "#columnName#", "#columnType#" },
424 new String[] { tableName, columnName, nullable ? columnType : columnType + " NOT NULL" });
425 logger.debug("JDBC::doAlterTableColumn sql={}", sql);
427 Yank.execute(sql, null);
428 } catch (YankSQLException e) {
429 throw new JdbcSQLException(e);
433 public void doStoreItemValue(Item item, State itemState, ItemVO vo) throws JdbcSQLException {
434 ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
435 String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
436 new String[] { "#tableName#", "#tablePrimaryValue#" },
437 new String[] { storedVO.getTableName(), sqlTypes.get("tablePrimaryValue") });
438 Object[] params = { storedVO.getValue(), storedVO.getValue() };
439 logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
441 Yank.execute(sql, params);
442 } catch (YankSQLException e) {
443 throw new JdbcSQLException(e);
447 public void doStoreItemValue(Item item, State itemState, ItemVO vo, ZonedDateTime date) throws JdbcSQLException {
448 ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
449 String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
450 new String[] { "#tableName#", "#tablePrimaryValue#" }, new String[] { storedVO.getTableName(), "?" });
451 java.sql.Timestamp timestamp = new java.sql.Timestamp(date.toInstant().toEpochMilli());
452 Object[] params = { timestamp, storedVO.getValue(), storedVO.getValue() };
453 logger.debug("JDBC::doStoreItemValue sql={} timestamp={} value='{}'", sql, timestamp, storedVO.getValue());
455 Yank.execute(sql, params);
456 } catch (YankSQLException e) {
457 throw new JdbcSQLException(e);
461 public List<HistoricItem> doGetHistItemFilterQuery(Item item, FilterCriteria filter, int numberDecimalcount,
462 String table, String name, ZoneId timeZone) throws JdbcSQLException {
463 String sql = histItemFilterQueryProvider(filter, numberDecimalcount, table, name, timeZone);
464 logger.debug("JDBC::doGetHistItemFilterQuery sql={}", sql);
467 m = Yank.queryObjectArrays(sql, null);
468 } catch (YankSQLException e) {
469 throw new JdbcSQLException(e);
472 logger.debug("JDBC::doGetHistItemFilterQuery Query failed. Returning an empty list.");
475 // we already retrieve the unit here once as it is a very costly operation
476 String itemName = item.getName();
477 Unit<? extends Quantity<?>> unit = item instanceof NumberItem ? ((NumberItem) item).getUnit() : null;
479 .map(o -> new JdbcHistoricItem(itemName, objectAsState(item, unit, o[1]), objectAsZonedDateTime(o[0])))
480 .collect(Collectors.<HistoricItem> toList());
483 public void doDeleteItemValues(FilterCriteria filter, String table, ZoneId timeZone) throws JdbcSQLException {
484 String sql = histItemFilterDeleteProvider(filter, table, timeZone);
485 logger.debug("JDBC::doDeleteItemValues sql={}", sql);
487 Yank.execute(sql, null);
488 } catch (YankSQLException e) {
489 throw new JdbcSQLException(e);
493 public long doGetRowCount(String tableName) throws JdbcSQLException {
494 final String sql = StringUtilsExt.replaceArrayMerge(sqlGetRowCount, new String[] { "#tableName#" },
495 new String[] { tableName });
496 logger.debug("JDBC::doGetRowCount sql={}", sql);
498 final @Nullable Long result = Yank.queryScalar(sql, Long.class, null);
499 return Objects.requireNonNullElse(result, 0L);
500 } catch (YankSQLException e) {
501 throw new JdbcSQLException(e);
508 static final DateTimeFormatter JDBC_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
510 protected String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
511 String simpleName, ZoneId timeZone) {
513 "JDBC::getHistItemFilterQueryProvider filter = {}, numberDecimalcount = {}, table = {}, simpleName = {}",
514 filter, numberDecimalcount, table, simpleName);
516 String filterString = resolveTimeFilter(filter, timeZone);
517 filterString += (filter.getOrdering() == Ordering.ASCENDING) ? " ORDER BY time ASC" : " ORDER BY time DESC";
518 if (filter.getPageSize() != Integer.MAX_VALUE) {
519 filterString += " LIMIT " + filter.getPageNumber() * filter.getPageSize() + "," + filter.getPageSize();
521 // SELECT time, ROUND(value,3) FROM number_item_0114 ORDER BY time DESC LIMIT 0,1
523 String queryString = "NUMBERITEM".equalsIgnoreCase(simpleName) && numberDecimalcount > -1
524 ? "SELECT time, ROUND(value," + numberDecimalcount + ") FROM " + table
525 : "SELECT time, value FROM " + table;
526 if (!filterString.isEmpty()) {
527 queryString += filterString;
529 logger.debug("JDBC::query queryString = {}", queryString);
533 protected String histItemFilterDeleteProvider(FilterCriteria filter, String table, ZoneId timeZone) {
534 logger.debug("JDBC::histItemFilterDeleteProvider filter = {}, table = {}", filter, table);
536 String filterString = resolveTimeFilter(filter, timeZone);
537 String deleteString = filterString.isEmpty() ? "TRUNCATE TABLE " + table
538 : "DELETE FROM " + table + filterString;
539 logger.debug("JDBC::delete deleteString = {}", deleteString);
543 protected String resolveTimeFilter(FilterCriteria filter, ZoneId timeZone) {
544 String filterString = "";
545 ZonedDateTime beginDate = filter.getBeginDate();
546 if (beginDate != null) {
547 filterString += filterString.isEmpty() ? " WHERE" : " AND";
548 filterString += " TIME>='" + JDBC_DATE_FORMAT.format(beginDate.withZoneSameInstant(timeZone)) + "'";
550 ZonedDateTime endDate = filter.getEndDate();
551 if (endDate != null) {
552 filterString += filterString.isEmpty() ? " WHERE" : " AND";
553 filterString += " TIME<='" + JDBC_DATE_FORMAT.format(endDate.withZoneSameInstant(timeZone)) + "'";
558 private String updateItemTableNamesProvider(ItemVO itemTable) {
559 String queryString = "ALTER TABLE " + itemTable.getTableName() + " RENAME TO " + itemTable.getNewTableName();
560 logger.debug("JDBC::query queryString = {}", queryString);
564 protected ItemVO storeItemValueProvider(Item item, State itemState, ItemVO vo) {
565 String itemType = getItemType(item);
567 logger.debug("JDBC::storeItemValueProvider: item '{}' as Type '{}' in '{}' with state '{}'", item.getName(),
568 itemType, vo.getTableName(), itemState);
571 logger.debug("JDBC::storeItemValueProvider: itemState: '{}'", itemState);
575 * 1. DimmerItem.getStateAs(PercentType.class).toString() always
577 * RollershutterItem.getStateAs(PercentType.class).toString() works
580 * 2. (item instanceof ColorItem) == (item instanceof DimmerItem) =
581 * true Therefore for instance tests ColorItem always has to be
582 * tested before DimmerItem
588 vo.setValueTypes(getSqlTypes().get(itemType), java.lang.String.class);
589 vo.setValue(itemState.toString());
592 State convertedState = itemState;
593 if (item instanceof NumberItem && itemState instanceof QuantityType) {
594 Unit<? extends Quantity<?>> unit = ((NumberItem) item).getUnit();
595 if (unit != null && !Units.ONE.equals(unit)) {
596 convertedState = ((QuantityType<?>) itemState).toUnit(unit);
597 if (convertedState == null) {
599 "JDBC::storeItemValueProvider: Failed to convert state '{}' to unit '{}'. Please check your item definition for correctness.",
601 convertedState = itemState;
605 String it = getSqlTypes().get(itemType);
607 logger.warn("JDBC::storeItemValueProvider: No SQL type defined for item type {}", itemType);
608 } else if (it.toUpperCase().contains("DOUBLE")) {
609 vo.setValueTypes(it, java.lang.Double.class);
610 double value = ((Number) convertedState).doubleValue();
611 logger.debug("JDBC::storeItemValueProvider: newVal.doubleValue: '{}'", value);
613 } else if (it.toUpperCase().contains("DECIMAL") || it.toUpperCase().contains("NUMERIC")) {
614 vo.setValueTypes(it, java.math.BigDecimal.class);
615 BigDecimal value = BigDecimal.valueOf(((Number) convertedState).doubleValue());
616 logger.debug("JDBC::storeItemValueProvider: newVal.toBigDecimal: '{}'", value);
618 } else if (it.toUpperCase().contains("INT")) {
619 vo.setValueTypes(it, java.lang.Integer.class);
620 int value = ((Number) convertedState).intValue();
621 logger.debug("JDBC::storeItemValueProvider: newVal.intValue: '{}'", value);
623 } else {// fall back to String
624 vo.setValueTypes(it, java.lang.String.class);
625 logger.warn("JDBC::storeItemValueProvider: itemState: '{}'", convertedState);
626 vo.setValue(convertedState.toString());
629 case "ROLLERSHUTTERITEM":
631 vo.setValueTypes(getSqlTypes().get(itemType), java.lang.Integer.class);
632 int value = ((DecimalType) itemState).intValue();
633 logger.debug("JDBC::storeItemValueProvider: newVal.intValue: '{}'", value);
637 vo.setValueTypes(getSqlTypes().get(itemType), java.sql.Timestamp.class);
638 java.sql.Timestamp d = new java.sql.Timestamp(
639 ((DateTimeType) itemState).getZonedDateTime().toInstant().toEpochMilli());
640 logger.debug("JDBC::storeItemValueProvider: DateTimeItem: '{}'", d);
644 vo.setValueTypes(getSqlTypes().get(itemType), java.lang.String.class);
645 String encodedString = item.getState().toFullString();
646 logger.debug("JDBC::storeItemValueProvider: ImageItem: '{}'", encodedString);
647 vo.setValue(encodedString);
650 // All other items should return the best format by default
651 vo.setValueTypes(getSqlTypes().get(itemType), java.lang.String.class);
652 logger.debug("JDBC::storeItemValueProvider: other: itemState: '{}'", itemState);
653 vo.setValue(itemState.toString());
662 protected State objectAsState(Item item, @Nullable Unit<? extends Quantity<?>> unit, Object v) {
664 "JDBC::ItemResultHandler::handleResult getState value = '{}', unit = '{}', getClass = '{}', clazz = '{}'",
665 v, unit, v.getClass(), v.getClass().getSimpleName());
666 if (item instanceof NumberItem) {
667 String it = getSqlTypes().get("NUMBERITEM");
669 throw new UnsupportedOperationException("No SQL type defined for item type NUMBERITEM");
671 if (it.toUpperCase().contains("DOUBLE")) {
672 return unit == null ? new DecimalType(((Number) v).doubleValue())
673 : QuantityType.valueOf(((Number) v).doubleValue(), unit);
674 } else if (it.toUpperCase().contains("DECIMAL") || it.toUpperCase().contains("NUMERIC")) {
675 return unit == null ? new DecimalType((BigDecimal) v)
676 : QuantityType.valueOf(((BigDecimal) v).doubleValue(), unit);
677 } else if (it.toUpperCase().contains("INT")) {
678 return unit == null ? new DecimalType(objectAsInteger(v))
679 : QuantityType.valueOf(((Integer) v).doubleValue(), unit);
681 return unit == null ? DecimalType.valueOf(objectAsString(v)) : QuantityType.valueOf(objectAsString(v));
682 } else if (item instanceof DateTimeItem) {
683 return new DateTimeType(objectAsZonedDateTime(v));
684 } else if (item instanceof ColorItem) {
685 return HSBType.valueOf(objectAsString(v));
686 } else if (item instanceof DimmerItem || item instanceof RollershutterItem) {
687 return new PercentType(objectAsInteger(v));
688 } else if (item instanceof ImageItem) {
689 return RawType.valueOf(objectAsString(v));
690 } else if (item instanceof ContactItem || item instanceof PlayerItem || item instanceof SwitchItem) {
691 State state = TypeParser.parseState(item.getAcceptedDataTypes(), ((String) v).toString().trim());
693 throw new UnsupportedOperationException("Unable to parse state for item " + item.toString());
697 State state = TypeParser.parseState(item.getAcceptedDataTypes(), ((String) v).toString());
699 throw new UnsupportedOperationException("Unable to parse state for item " + item.toString());
705 protected ZonedDateTime objectAsZonedDateTime(Object v) {
706 if (v instanceof Long) {
707 return ZonedDateTime.ofInstant(Instant.ofEpochMilli(((Number) v).longValue()), ZoneId.systemDefault());
708 } else if (v instanceof java.sql.Date) {
709 return ZonedDateTime.ofInstant(Instant.ofEpochMilli(((java.sql.Date) v).getTime()), ZoneId.systemDefault());
710 } else if (v instanceof LocalDateTime) {
711 return ((LocalDateTime) v).atZone(ZoneId.systemDefault());
712 } else if (v instanceof Instant) {
713 return ((Instant) v).atZone(ZoneId.systemDefault());
714 } else if (v instanceof java.sql.Timestamp) {
715 return ((java.sql.Timestamp) v).toInstant().atZone(ZoneId.systemDefault());
716 } else if (v instanceof java.lang.String) {
717 return ZonedDateTime.ofInstant(java.sql.Timestamp.valueOf(v.toString()).toInstant(),
718 ZoneId.systemDefault());
720 throw new UnsupportedOperationException("Date of type " + v.getClass().getName() + " is not supported");
723 protected Integer objectAsInteger(Object v) {
724 if (v instanceof Byte) {
725 return ((Byte) v).intValue();
727 return ((Integer) v).intValue();
730 protected String objectAsString(Object v) {
731 if (v instanceof byte[]) {
732 return new String((byte[]) v);
734 return ((String) v).toString();
737 public String getItemType(Item i) {
739 String def = "STRINGITEM";
740 if (i instanceof GroupItem) {
741 item = ((GroupItem) i).getBaseItem();
743 // if GroupItem:<ItemType> is not defined in *.items using StringType
745 "JDBC::getItemType: Cannot detect ItemType for {} because the GroupItems' base type isn't set in *.items File.",
747 Iterator<Item> iterator = ((GroupItem) i).getMembers().iterator();
748 if (!iterator.hasNext()) {
750 "JDBC::getItemType: No Child-Members of GroupItem {}, use ItemType for STRINGITEM as Fallback",
754 item = iterator.next();
757 String itemType = item.getClass().getSimpleName().toUpperCase();
758 if (sqlTypes.get(itemType) == null) {
760 "JDBC::getItemType: No sqlType found for ItemType {}, use ItemType for STRINGITEM as Fallback for {}",
761 itemType, i.getName());
767 /******************************
768 * public Getters and Setters *
769 ******************************/
770 public Map<String, String> getSqlTypes() {
774 public String getDataType(Item item) {
775 String dataType = sqlTypes.get(getItemType(item));
776 if (dataType == null) {
777 throw new UnsupportedOperationException("No data type found for " + getItemType(item));