]> git.basschouten.com Git - openhab-addons.git/blob
d1be0bee42ff27172dfbcaabc5213830ffcdeb84
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.persistence.jdbc.internal.db;
14
15 import java.time.ZoneId;
16 import java.time.ZonedDateTime;
17 import java.util.List;
18 import java.util.Objects;
19 import java.util.stream.Collectors;
20
21 import javax.measure.Quantity;
22 import javax.measure.Unit;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.knowm.yank.Yank;
27 import org.knowm.yank.exceptions.YankSQLException;
28 import org.openhab.core.items.Item;
29 import org.openhab.core.library.items.NumberItem;
30 import org.openhab.core.persistence.FilterCriteria;
31 import org.openhab.core.persistence.FilterCriteria.Ordering;
32 import org.openhab.core.persistence.HistoricItem;
33 import org.openhab.core.types.State;
34 import org.openhab.persistence.jdbc.internal.dto.ItemVO;
35 import org.openhab.persistence.jdbc.internal.dto.ItemsVO;
36 import org.openhab.persistence.jdbc.internal.dto.JdbcHistoricItem;
37 import org.openhab.persistence.jdbc.internal.exceptions.JdbcSQLException;
38 import org.openhab.persistence.jdbc.internal.utils.StringUtilsExt;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * Extended Database Configuration class. Class represents
44  * the extended database-specific configuration. Overrides and supplements the
45  * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
46  *
47  * @author Helmut Lehmeyer - Initial contribution
48  */
49 @NonNullByDefault
50 public class JdbcDerbyDAO extends JdbcBaseDAO {
51     private static final String DRIVER_CLASS_NAME = org.apache.derby.jdbc.EmbeddedDriver.class.getName();
52     @SuppressWarnings("unused")
53     private static final String DATA_SOURCE_CLASS_NAME = org.apache.derby.jdbc.EmbeddedDataSource.class.getName();
54
55     private final Logger logger = LoggerFactory.getLogger(JdbcDerbyDAO.class);
56
57     /********
58      * INIT *
59      ********/
60     public JdbcDerbyDAO() {
61         initSqlTypes();
62         initDbProps();
63         initSqlQueries();
64     }
65
66     private void initSqlQueries() {
67         logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
68         sqlPingDB = "values 1";
69         sqlGetDB = "VALUES SYSCS_UTIL.SYSCS_GET_DATABASE_PROPERTY( 'DataDictionaryVersion' )"; // returns version
70         sqlIfTableExists = "SELECT * FROM SYS.SYSTABLES WHERE TABLENAME='#searchTable#'";
71         sqlCreateItemsTableIfNot = "CREATE TABLE #itemsManageTable# ( ItemId INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1), #colname# #coltype# NOT NULL)";
72         sqlCreateItemTable = "CREATE TABLE #tableName# (time #tablePrimaryKey# NOT NULL, value #dbType#, PRIMARY KEY(time))";
73         // Prevent error against duplicate time value (seldom): No powerful Merge found:
74         // http://www.codeproject.com/Questions/162627/how-to-insert-new-record-in-my-table-if-not-exists
75         sqlInsertItemValue = "INSERT INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, CAST( ? as #dbType#) )";
76         sqlAlterTableColumn = "ALTER TABLE #tableName# ALTER COLUMN #columnName# SET DATA TYPE #columnType#";
77     }
78
79     private void initSqlTypes() {
80         sqlTypes.put("DATETIMEITEM", "TIMESTAMP");
81         sqlTypes.put("DIMMERITEM", "SMALLINT");
82         sqlTypes.put("IMAGEITEM", "VARCHAR(32000)");
83         sqlTypes.put("ROLLERSHUTTERITEM", "SMALLINT");
84         sqlTypes.put("STRINGITEM", "VARCHAR(32000)");
85         sqlTypes.put("tablePrimaryValue", "CURRENT_TIMESTAMP");
86         logger.debug("JDBC::initSqlTypes: Initialized the type array sqlTypes={}", sqlTypes.values());
87     }
88
89     /**
90      * INFO: https://github.com/brettwooldridge/HikariCP
91      */
92     private void initDbProps() {
93         // Properties for HikariCP
94         // Use driverClassName
95         databaseProps.setProperty("driverClassName", DRIVER_CLASS_NAME);
96         // OR dataSourceClassName
97         // databaseProps.setProperty("dataSourceClassName", DATA_SOURCE_CLASS_NAME);
98         databaseProps.setProperty("maximumPoolSize", "1");
99         databaseProps.setProperty("minimumIdle", "1");
100     }
101
102     @Override
103     public void initAfterFirstDbConnection() {
104         logger.debug("JDBC::initAfterFirstDbConnection: Initializing step, after db is connected.");
105         // Initialize sqlTypes, depending on DB version for example
106         // derby does not like this... dbMeta = new DbMetaData();// get DB information
107     }
108
109     /**************
110      * ITEMS DAOs *
111      **************/
112     @Override
113     public @Nullable Integer doPingDB() throws JdbcSQLException {
114         try {
115             return Yank.queryScalar(sqlPingDB, Integer.class, null);
116         } catch (YankSQLException e) {
117             throw new JdbcSQLException(e);
118         }
119     }
120
121     @Override
122     public boolean doIfTableExists(ItemsVO vo) throws JdbcSQLException {
123         String sql = StringUtilsExt.replaceArrayMerge(sqlIfTableExists, new String[] { "#searchTable#" },
124                 new String[] { vo.getItemsManageTable().toUpperCase() });
125         logger.debug("JDBC::doIfTableExists sql={}", sql);
126         try {
127             final @Nullable String result = Yank.queryScalar(sql, String.class, null);
128             return Objects.nonNull(result);
129         } catch (YankSQLException e) {
130             throw new JdbcSQLException(e);
131         }
132     }
133
134     @Override
135     public Long doCreateNewEntryInItemsTable(ItemsVO vo) throws JdbcSQLException {
136         String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
137                 new String[] { "#itemsManageTable#", "#itemname#" },
138                 new String[] { vo.getItemsManageTable().toUpperCase(), vo.getItemName() });
139         logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
140         try {
141             return Yank.insert(sql, null);
142         } catch (YankSQLException e) {
143             throw new JdbcSQLException(e);
144         }
145     }
146
147     @Override
148     public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) throws JdbcSQLException {
149         boolean tableExists = doIfTableExists(vo);
150         if (!tableExists) {
151             String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
152                     new String[] { "#itemsManageTable#", "#colname#", "#coltype#" },
153                     new String[] { vo.getItemsManageTable().toUpperCase(), vo.getColname(), vo.getColtype() });
154             logger.debug("JDBC::doCreateItemsTableIfNot tableExists={} therefore sql={}", tableExists, sql);
155             try {
156                 Yank.execute(sql, null);
157             } catch (YankSQLException e) {
158                 throw new JdbcSQLException(e);
159             }
160         } else {
161             logger.debug("JDBC::doCreateItemsTableIfNot tableExists={}, did not CREATE TABLE", tableExists);
162         }
163         return vo;
164     }
165
166     /*************
167      * ITEM DAOs *
168      *************/
169     @Override
170     public void doCreateItemTable(ItemVO vo) throws JdbcSQLException {
171         String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemTable,
172                 new String[] { "#tableName#", "#dbType#", "#tablePrimaryKey#" },
173                 new String[] { vo.getTableName(), vo.getDbType(), sqlTypes.get("tablePrimaryKey") });
174         try {
175             Yank.execute(sql, null);
176         } catch (YankSQLException e) {
177             throw new JdbcSQLException(e);
178         }
179     }
180
181     @Override
182     public void doStoreItemValue(Item item, State itemState, ItemVO vo) throws JdbcSQLException {
183         ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
184         String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
185                 new String[] { "#tableName#", "#dbType#", "#tablePrimaryValue#" },
186                 new String[] { storedVO.getTableName().toUpperCase(), storedVO.getDbType(),
187                         sqlTypes.get("tablePrimaryValue") });
188         Object[] params = { storedVO.getValue() };
189         logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, storedVO.getValue());
190         try {
191             Yank.execute(sql, params);
192         } catch (YankSQLException e) {
193             throw new JdbcSQLException(e);
194         }
195     }
196
197     @Override
198     public void doStoreItemValue(Item item, State itemState, ItemVO vo, ZonedDateTime date) throws JdbcSQLException {
199         ItemVO storedVO = storeItemValueProvider(item, itemState, vo);
200         String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
201                 new String[] { "#tableName#", "#dbType#", "#tablePrimaryValue#" },
202                 new String[] { storedVO.getTableName().toUpperCase(), storedVO.getDbType(), "?" });
203         java.sql.Timestamp timestamp = new java.sql.Timestamp(date.toInstant().toEpochMilli());
204         Object[] params = { timestamp, storedVO.getValue() };
205         logger.debug("JDBC::doStoreItemValue sql={} timestamp={} value='{}'", sql, timestamp, storedVO.getValue());
206         try {
207             Yank.execute(sql, params);
208         } catch (YankSQLException e) {
209             throw new JdbcSQLException(e);
210         }
211     }
212
213     @Override
214     public List<HistoricItem> doGetHistItemFilterQuery(Item item, FilterCriteria filter, int numberDecimalcount,
215             String table, String name, ZoneId timeZone) throws JdbcSQLException {
216         String sql = histItemFilterQueryProvider(filter, numberDecimalcount, table, name, timeZone);
217         List<Object[]> m;
218         try {
219             m = Yank.queryObjectArrays(sql, null);
220         } catch (YankSQLException e) {
221             throw new JdbcSQLException(e);
222         }
223         logger.debug("JDBC::doGetHistItemFilterQuery got Array length={}", m.size());
224         // we already retrieve the unit here once as it is a very costly operation
225         String itemName = item.getName();
226         Unit<? extends Quantity<?>> unit = item instanceof NumberItem ni ? ni.getUnit() : null;
227         return m.stream().map(o -> {
228             logger.debug("JDBC::doGetHistItemFilterQuery 0='{}' 1='{}'", o[0], o[1]);
229             return new JdbcHistoricItem(itemName, objectAsState(item, unit, o[1]), objectAsZonedDateTime(o[0]));
230         }).collect(Collectors.<HistoricItem> toList());
231     }
232
233     /****************************
234      * SQL generation Providers *
235      ****************************/
236
237     @Override
238     protected String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
239             String simpleName, ZoneId timeZone) {
240         logger.debug(
241                 "JDBC::getHistItemFilterQueryProvider filter = {}, numberDecimalcount = {}, table = {}, simpleName = {}",
242                 StringUtilsExt.filterToString(filter), numberDecimalcount, table, simpleName);
243
244         String filterString = "";
245         ZonedDateTime beginDate = filter.getBeginDate();
246         if (beginDate != null) {
247             filterString += filterString.isEmpty() ? " WHERE" : " AND";
248             filterString += " TIME>='" + JDBC_DATE_FORMAT.format(beginDate.withZoneSameInstant(timeZone)) + "'";
249         }
250         ZonedDateTime endDate = filter.getEndDate();
251         if (endDate != null) {
252             filterString += filterString.isEmpty() ? " WHERE" : " AND";
253             filterString += " TIME<='" + JDBC_DATE_FORMAT.format(endDate.withZoneSameInstant(timeZone)) + "'";
254         }
255         filterString += (filter.getOrdering() == Ordering.ASCENDING) ? " ORDER BY time ASC" : " ORDER BY time DESC";
256         if (filter.getPageSize() != 0x7fffffff) {
257             // TODO: TESTING!!!
258             // filterString += " LIMIT " + filter.getPageNumber() *
259             // filter.getPageSize() + "," + filter.getPageSize();
260             // SELECT time, value FROM ohscriptfiles_sw_ace_paths_0001 ORDER BY
261             // time DESC OFFSET 1 ROWS FETCH NEXT 0 ROWS ONLY
262             // filterString += " OFFSET " + filter.getPageSize() +" ROWS FETCH
263             // FIRST||NEXT " + filter.getPageNumber() * filter.getPageSize() + "
264             // ROWS ONLY";
265             filterString += " OFFSET " + filter.getPageSize() + " ROWS FETCH FIRST "
266                     + (filter.getPageNumber() * filter.getPageSize() + 1) + " ROWS ONLY";
267         }
268
269         // http://www.seemoredata.com/en/showthread.php?132-Round-function-in-Apache-Derby
270         // simulated round function in Derby: CAST(value 0.0005 AS DECIMAL(15,3))
271         // simulated round function in Derby: "CAST(value 0.0005 AS DECIMAL(15,"+numberDecimalcount+"))"
272
273         String queryString = "SELECT time,";
274         if ("NUMBERITEM".equalsIgnoreCase(simpleName) && numberDecimalcount > -1) {
275             // rounding HALF UP
276             queryString += "CAST(value 0.";
277             for (int i = 0; i < numberDecimalcount; i++) {
278                 queryString += "0";
279             }
280             queryString += "5 AS DECIMAL(31," + numberDecimalcount + "))"; // 31 is DECIMAL max precision
281                                                                            // https://db.apache.org/derby/docs/10.0/manuals/develop/develop151.html
282         } else {
283             queryString += " value FROM " + table.toUpperCase();
284         }
285
286         if (!filterString.isEmpty()) {
287             queryString += filterString;
288         }
289         logger.debug("JDBC::query queryString = {}", queryString);
290         return queryString;
291     }
292
293     /*****************
294      * H E L P E R S *
295      *****************/
296
297     /******************************
298      * public Getters and Setters *
299      ******************************/
300 }