]> git.basschouten.com Git - openhab-addons.git/blob
76b57e9cd6ef3a41c49be150f50c2a0f8a62c881
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.db;
14
15 import java.time.ZoneId;
16 import java.time.format.DateTimeFormatter;
17 import java.util.ArrayList;
18 import java.util.List;
19
20 import org.knowm.yank.Yank;
21 import org.openhab.core.items.Item;
22 import org.openhab.core.persistence.FilterCriteria;
23 import org.openhab.core.persistence.FilterCriteria.Ordering;
24 import org.openhab.core.persistence.HistoricItem;
25 import org.openhab.persistence.jdbc.model.ItemVO;
26 import org.openhab.persistence.jdbc.model.ItemsVO;
27 import org.openhab.persistence.jdbc.model.JdbcHistoricItem;
28 import org.openhab.persistence.jdbc.utils.StringUtilsExt;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 /**
33  * Extended Database Configuration class. Class represents
34  * the extended database-specific configuration. Overrides and supplements the
35  * default settings from JdbcBaseDAO. Enter only the differences to JdbcBaseDAO here.
36  *
37  * @author Helmut Lehmeyer - Initial contribution
38  */
39 public class JdbcDerbyDAO extends JdbcBaseDAO {
40     private final Logger logger = LoggerFactory.getLogger(JdbcDerbyDAO.class);
41
42     /********
43      * INIT *
44      ********/
45     public JdbcDerbyDAO() {
46         super();
47         initSqlTypes();
48         initDbProps();
49         initSqlQueries();
50     }
51
52     private void initSqlQueries() {
53         logger.debug("JDBC::initSqlQueries: '{}'", this.getClass().getSimpleName());
54         sqlPingDB = "values 1";
55         sqlGetDB = "VALUES SYSCS_UTIL.SYSCS_GET_DATABASE_PROPERTY( 'DataDictionaryVersion' )"; // returns version
56         sqlIfTableExists = "SELECT * FROM SYS.SYSTABLES WHERE TABLENAME='#searchTable#'";
57         sqlCreateItemsTableIfNot = "CREATE TABLE #itemsManageTable# ( ItemId INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1), #colname# #coltype# NOT NULL)";
58         sqlCreateItemTable = "CREATE TABLE #tableName# (time #tablePrimaryKey# NOT NULL, value #dbType#, PRIMARY KEY(time))";
59         // Prevent error against duplicate time value (seldom): No powerful Merge found:
60         // http://www.codeproject.com/Questions/162627/how-to-insert-new-record-in-my-table-if-not-exists
61         sqlInsertItemValue = "INSERT INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, CAST( ? as #dbType#) )";
62     }
63
64     private void initSqlTypes() {
65         sqlTypes.put("DATETIMEITEM", "TIMESTAMP");
66         sqlTypes.put("DIMMERITEM", "SMALLINT");
67         sqlTypes.put("IMAGEITEM", "VARCHAR(32000)");
68         sqlTypes.put("ROLLERSHUTTERITEM", "SMALLINT");
69         sqlTypes.put("STRINGITEM", "VARCHAR(32000)");
70         sqlTypes.put("tablePrimaryValue", "CURRENT_TIMESTAMP");
71         logger.debug("JDBC::initSqlTypes: Initialized the type array sqlTypes={}", sqlTypes.values());
72     }
73
74     /**
75      * INFO: https://github.com/brettwooldridge/HikariCP
76      */
77     private void initDbProps() {
78         // Properties for HikariCP
79         // Use driverClassName
80         databaseProps.setProperty("driverClassName", "org.apache.derby.jdbc.EmbeddedDriver");
81         // OR dataSourceClassName
82         // databaseProps.setProperty("dataSourceClassName", "org.apache.derby.jdbc.EmbeddedDataSource");
83         databaseProps.setProperty("maximumPoolSize", "1");
84         databaseProps.setProperty("minimumIdle", "1");
85     }
86
87     @Override
88     public void initAfterFirstDbConnection() {
89         logger.debug("JDBC::initAfterFirstDbConnection: Initializing step, after db is connected.");
90         // Initialize sqlTypes, depending on DB version for example
91         // derby does not like this... dbMeta = new DbMetaData();// get DB information
92     }
93
94     /**************
95      * ITEMS DAOs *
96      **************/
97     @Override
98     public Integer doPingDB() {
99         return Yank.queryScalar(sqlPingDB, Integer.class, null);
100     }
101
102     @Override
103     public boolean doIfTableExists(ItemsVO vo) {
104         String sql = StringUtilsExt.replaceArrayMerge(sqlIfTableExists, new String[] { "#searchTable#" },
105                 new String[] { vo.getItemsManageTable().toUpperCase() });
106         logger.debug("JDBC::doIfTableExists sql={}", sql);
107         return Yank.queryScalar(sql, String.class, null) != null;
108     }
109
110     @Override
111     public Long doCreateNewEntryInItemsTable(ItemsVO vo) {
112         String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
113                 new String[] { "#itemsManageTable#", "#itemname#" },
114                 new String[] { vo.getItemsManageTable().toUpperCase(), vo.getItemname() });
115         logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
116         return Yank.insert(sql, null);
117     }
118
119     @Override
120     public ItemsVO doCreateItemsTableIfNot(ItemsVO vo) {
121         // boolean tableExists = Yank.queryScalar(SQL_IF_TABLE_EXISTS.replace("#searchTable#",
122         // vo.getItemsManageTable().toUpperCase()), String.class, null) == null;
123         boolean tableExists = doIfTableExists(vo);
124         if (!tableExists) {
125             String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemsTableIfNot,
126                     new String[] { "#itemsManageTable#", "#colname#", "#coltype#" },
127                     new String[] { vo.getItemsManageTable().toUpperCase(), vo.getColname(), vo.getColtype() });
128             logger.debug("JDBC::doCreateItemsTableIfNot tableExists={} therefore sql={}", tableExists, sql);
129             Yank.execute(sql, null);
130         } else {
131             logger.debug("JDBC::doCreateItemsTableIfNot tableExists={}, did not CREATE TABLE", tableExists);
132         }
133         return vo;
134     }
135
136     /*************
137      * ITEM DAOs *
138      *************/
139     @Override
140     public void doCreateItemTable(ItemVO vo) {
141         String sql = StringUtilsExt.replaceArrayMerge(sqlCreateItemTable,
142                 new String[] { "#tableName#", "#dbType#", "#tablePrimaryKey#" },
143                 new String[] { vo.getTableName(), vo.getDbType(), sqlTypes.get("tablePrimaryKey") });
144         Yank.execute(sql, null);
145     }
146
147     @Override
148     public void doStoreItemValue(Item item, ItemVO vo) {
149         vo = storeItemValueProvider(item, vo);
150         String sql = StringUtilsExt.replaceArrayMerge(sqlInsertItemValue,
151                 new String[] { "#tableName#", "#dbType#", "#tablePrimaryValue#" },
152                 new String[] { vo.getTableName().toUpperCase(), vo.getDbType(), sqlTypes.get("tablePrimaryValue") });
153         Object[] params = new Object[] { vo.getValue() };
154         logger.debug("JDBC::doStoreItemValue sql={} value='{}'", sql, vo.getValue());
155         Yank.execute(sql, params);
156     }
157
158     @Override
159     public List<HistoricItem> doGetHistItemFilterQuery(Item item, FilterCriteria filter, int numberDecimalcount,
160             String table, String name, ZoneId timeZone) {
161         String sql = histItemFilterQueryProvider(filter, numberDecimalcount, table, name, timeZone);
162         List<Object[]> m = Yank.queryObjectArrays(sql, null);
163
164         logger.debug("JDBC::doGetHistItemFilterQuery got Array length={}", m.size());
165
166         List<HistoricItem> items = new ArrayList<>();
167         for (int i = 0; i < m.size(); i++) {
168             logger.debug("JDBC::doGetHistItemFilterQuery 0='{}' 1='{}'", m.get(i)[0], m.get(i)[1]);
169             items.add(new JdbcHistoricItem(item.getName(), getState(item, m.get(i)[1]), objectAsDate(m.get(i)[0])));
170         }
171         return items;
172     }
173
174     /****************************
175      * SQL generation Providers *
176      ****************************/
177     static final DateTimeFormatter JDBC_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
178
179     private String histItemFilterQueryProvider(FilterCriteria filter, int numberDecimalcount, String table,
180             String simpleName, ZoneId timeZone) {
181         logger.debug(
182                 "JDBC::getHistItemFilterQueryProvider filter = {}, numberDecimalcount = {}, table = {}, simpleName = {}",
183                 StringUtilsExt.filterToString(filter), numberDecimalcount, table, simpleName);
184
185         String filterString = "";
186         if (filter.getBeginDate() != null) {
187             filterString += filterString.isEmpty() ? " WHERE" : " AND";
188             filterString += " TIME>'" + JDBC_DATE_FORMAT.format(filter.getBeginDate().withZoneSameInstant(timeZone))
189                     + "'";
190         }
191         if (filter.getEndDate() != null) {
192             filterString += filterString.isEmpty() ? " WHERE" : " AND";
193             filterString += " TIME<'" + JDBC_DATE_FORMAT.format(filter.getEndDate().withZoneSameInstant(timeZone))
194                     + "'";
195         }
196         filterString += (filter.getOrdering() == Ordering.ASCENDING) ? " ORDER BY time ASC" : " ORDER BY time DESC";
197         if (filter.getPageSize() != 0x7fffffff) {
198             // TODO: TESTING!!!
199             // filterString += " LIMIT " + filter.getPageNumber() *
200             // filter.getPageSize() + "," + filter.getPageSize();
201             // SELECT time, value FROM ohscriptfiles_sw_ace_paths_0001 ORDER BY
202             // time DESC OFFSET 1 ROWS FETCH NEXT 0 ROWS ONLY
203             // filterString += " OFFSET " + filter.getPageSize() +" ROWS FETCH
204             // FIRST||NEXT " + filter.getPageNumber() * filter.getPageSize() + "
205             // ROWS ONLY";
206             filterString += " OFFSET " + filter.getPageSize() + " ROWS FETCH FIRST "
207                     + (filter.getPageNumber() * filter.getPageSize() + 1) + " ROWS ONLY";
208         }
209
210         // http://www.seemoredata.com/en/showthread.php?132-Round-function-in-Apache-Derby
211         // simulated round function in Derby: CAST(value 0.0005 AS DECIMAL(15,3))
212         // simulated round function in Derby: "CAST(value 0.0005 AS DECIMAL(15,"+numberDecimalcount+"))"
213
214         String queryString = "SELECT time,";
215         if ("NUMBERITEM".equalsIgnoreCase(simpleName) && numberDecimalcount > -1) {
216             // rounding HALF UP
217             queryString += "CAST(value 0.";
218             for (int i = 0; i < numberDecimalcount; i++) {
219                 queryString += "0";
220             }
221             queryString += "5 AS DECIMAL(31," + numberDecimalcount + "))"; // 31 is DECIMAL max precision
222                                                                            // https://db.apache.org/derby/docs/10.0/manuals/develop/develop151.html
223         } else {
224             queryString += " value FROM " + table.toUpperCase();
225         }
226
227         if (!filterString.isEmpty()) {
228             queryString += filterString;
229         }
230         logger.debug("JDBC::query queryString = {}", queryString);
231         return queryString;
232     }
233
234     /*****************
235      * H E L P E R S *
236      *****************/
237
238     /******************************
239      * public Getters and Setters *
240      ******************************/
241 }