[jdbc] Add support for case sensitive table names reflecting item names 1:1 (#13544)
* Do not append number when using real item names * Extract getTableName to separate class * Add initial test coverage * Extract migration logic to separate class * Support migration from real names back to numbered * Simplify zero-padding * Fix NullPointerException * Fix MySQL compatibility when CLIENT_MULTI_STATEMENTS option is not set * Add option for case sensitive table names * Add real name with suffix mode for backwards compatibility * Remove real name in lower case without suffix mode * Map directly from item name to table name * Fix ambiguous table name scenario * Add additional testcase * Add migration path for changed table prefix * Drop items table when using direct mapping * Add configuration note * Fix table alignment * Extend description as more migration paths are now supported * Do not stop halfway through a migration * For clarity, do not use abbreviation for operating system Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
parent
f880ca91f9
commit
70abb5d1f6
|
@ -38,38 +38,39 @@ The following databases are currently supported and tested:
|
|||
|
||||
This service can be configured in the file `services/jdbc.cfg`.
|
||||
|
||||
| Property | Default | Required | Description |
|
||||
| ------------------------- | ------------------------------------------------------------ | :-------: | ------------------------------------------------------------ |
|
||||
| url | | Yes | JDBC URL to establish a connection to your database. Examples:<br/><br/>`jdbc:derby:./testDerby;create=true`<br/>`jdbc:h2:./testH2`<br/>`jdbc:hsqldb:./testHsqlDb`<br/>`jdbc:mariadb://192.168.0.1:3306/testMariadb`<br/>`jdbc:mysql://192.168.0.1:3306/testMysql?serverTimezone=UTC`<br/>`jdbc:postgresql://192.168.0.1:5432/testPostgresql`<br/>`jdbc:timescaledb://192.168.0.1:5432/testPostgresql`<br/>`jdbc:sqlite:./testSqlite.db`.<br/><br/>If no database is available it will be created; for example the url `jdbc:h2:./testH2` creates a new H2 database in openHAB folder. Example to create your own MySQL database directly:<br/><br/>`CREATE DATABASE 'yourDB' CHARACTER SET utf8 COLLATE utf8_general_ci;` |
|
||||
| user | | if needed | database user name |
|
||||
| password | | if needed | database user password |
|
||||
| errReconnectThreshold | 0 | No | when the service is deactivated (0 means ignore) |
|
||||
| sqltype.CALL | `VARCHAR(200)` | No | All `sqlType` options allow you to change the SQL data type used to store values for different openHAB item states. See the following links for further information: [mybatis](https://mybatis.github.io/mybatis-3/apidocs/reference/org/apache/ibatis/type/JdbcType.html) [H2](https://www.h2database.com/html/datatypes.html) [PostgresSQL](https://www.postgresql.org/docs/9.3/static/datatype.html) |
|
||||
| sqltype.COLOR | `VARCHAR(70)` | No | see above |
|
||||
| sqltype.CONTACT | `VARCHAR(6)` | No | see above |
|
||||
| sqltype.DATETIME | `DATETIME` | No | see above |
|
||||
| sqltype.DIMMER | `TINYINT` | No | see above |
|
||||
| sqltype.IMAGE | `VARCHAR(65500)` | No | see above |
|
||||
| sqltype.LOCATION | `VARCHAR(50)` | No | see above |
|
||||
| sqltype.NUMBER | `DOUBLE` | No | see above |
|
||||
| sqltype.PLAYER | `VARCHAR(20)` | No | see above |
|
||||
| sqltype.ROLLERSHUTTER | `TINYINT` | No | see above |
|
||||
| sqltype.STRING | `VARCHAR(65500)` | No | see above |
|
||||
| sqltype.SWITCH | `VARCHAR(6)` | No | see above |
|
||||
| sqltype.tablePrimaryKey | `TIMESTAMP` | No | type of `time` column for newly created item tables |
|
||||
| sqltype.tablePrimaryValue | `NOW()` | No | value of `time` column for newly inserted rows |
|
||||
| numberDecimalcount | 3 | No | for Itemtype "Number" default decimal digit count |
|
||||
| tableNamePrefix | `item` | No | table name prefix. For Migration from MySQL Persistence, set to `Item`. |
|
||||
| tableUseRealItemNames | `false` | No | table name prefix generation. When set to `true`, real item names are used for table names and `tableNamePrefix` is ignored. When set to `false`, the `tableNamePrefix` is used to generate table names with sequential numbers. |
|
||||
| tableIdDigitCount | 4 | No | when `tableUseRealItemNames` is `false` and thus table names are generated sequentially, this controls how many zero-padded digits are used in the table name. With the default of 4, the first table name will end with `0001`. For migration from the MySQL persistence service, set this to 0. |
|
||||
| rebuildTableNames | false | No | rename existing tables using `tableUseRealItemNames` and `tableIdDigitCount`. USE WITH CARE! Deactivate after Renaming is done! |
|
||||
| jdbc.maximumPoolSize | configured per database in package `org.openhab.persistence.jdbc.db.*` | No | Some embedded databases can handle only one connection. See [this link](https://github.com/brettwooldridge/HikariCP/issues/256) for more information |
|
||||
| jdbc.minimumIdle | see above | No | see above |
|
||||
| enableLogTime | `false` | No | timekeeping |
|
||||
| Property | Default | Required | Description |
|
||||
| --------------------------- | ------------------------------------------------------------ | :-------: | ------------------------------------------------------------ |
|
||||
| url | | Yes | JDBC URL to establish a connection to your database. Examples:<br/><br/>`jdbc:derby:./testDerby;create=true`<br/>`jdbc:h2:./testH2`<br/>`jdbc:hsqldb:./testHsqlDb`<br/>`jdbc:mariadb://192.168.0.1:3306/testMariadb`<br/>`jdbc:mysql://192.168.0.1:3306/testMysql?serverTimezone=UTC`<br/>`jdbc:postgresql://192.168.0.1:5432/testPostgresql`<br/>`jdbc:timescaledb://192.168.0.1:5432/testPostgresql`<br/>`jdbc:sqlite:./testSqlite.db`.<br/><br/>If no database is available it will be created; for example the url `jdbc:h2:./testH2` creates a new H2 database in openHAB folder. Example to create your own MySQL database directly:<br/><br/>`CREATE DATABASE 'yourDB' CHARACTER SET utf8 COLLATE utf8_general_ci;` |
|
||||
| user | | if needed | database user name |
|
||||
| password | | if needed | database user password |
|
||||
| errReconnectThreshold | 0 | No | when the service is deactivated (0 means ignore) |
|
||||
| sqltype.CALL | `VARCHAR(200)` | No | All `sqlType` options allow you to change the SQL data type used to store values for different openHAB item states. See the following links for further information: [mybatis](https://mybatis.github.io/mybatis-3/apidocs/reference/org/apache/ibatis/type/JdbcType.html) [H2](https://www.h2database.com/html/datatypes.html) [PostgresSQL](https://www.postgresql.org/docs/9.3/static/datatype.html) |
|
||||
| sqltype.COLOR | `VARCHAR(70)` | No | see above |
|
||||
| sqltype.CONTACT | `VARCHAR(6)` | No | see above |
|
||||
| sqltype.DATETIME | `DATETIME` | No | see above |
|
||||
| sqltype.DIMMER | `TINYINT` | No | see above |
|
||||
| sqltype.IMAGE | `VARCHAR(65500)` | No | see above |
|
||||
| sqltype.LOCATION | `VARCHAR(50)` | No | see above |
|
||||
| sqltype.NUMBER | `DOUBLE` | No | see above |
|
||||
| sqltype.PLAYER | `VARCHAR(20)` | No | see above |
|
||||
| sqltype.ROLLERSHUTTER | `TINYINT` | No | see above |
|
||||
| sqltype.STRING | `VARCHAR(65500)` | No | see above |
|
||||
| sqltype.SWITCH | `VARCHAR(6)` | No | see above |
|
||||
| sqltype.tablePrimaryKey | `TIMESTAMP` | No | type of `time` column for newly created item tables |
|
||||
| sqltype.tablePrimaryValue | `NOW()` | No | value of `time` column for newly inserted rows |
|
||||
| numberDecimalcount | 3 | No | for Itemtype "Number" default decimal digit count |
|
||||
| tableNamePrefix | `item` | No | table name prefix. For Migration from MySQL Persistence, set to `Item`. |
|
||||
| tableUseRealItemNames | `false` | No | table name prefix generation. When set to `true`, real item names are used for table names and `tableNamePrefix` is ignored. When set to `false`, the `tableNamePrefix` is used to generate table names with sequential numbers. |
|
||||
| tableCaseSensitiveItemNames | `false` | No | table name case when `tableUseRealItemNames` is `true`. When set to `true`, item name case is preserved in table names and no suffix is used. When set to `false`, table names are lower cased and a numeric suffix is added. Please read [this](#case-sensitive-item-names) before enabling. |
|
||||
| tableIdDigitCount | 4 | No | when `tableUseRealItemNames` is `false` and thus table names are generated sequentially, this controls how many zero-padded digits are used in the table name. With the default of 4, the first table name will end with `0001`. For migration from the MySQL persistence service, set this to 0. |
|
||||
| rebuildTableNames | false | No | rename existing tables using `tableUseRealItemNames` and `tableIdDigitCount`. USE WITH CARE! Deactivate after Renaming is done! |
|
||||
| jdbc.maximumPoolSize | configured per database in package `org.openhab.persistence.jdbc.db.*` | No | Some embedded databases can handle only one connection. See [this link](https://github.com/brettwooldridge/HikariCP/issues/256) for more information |
|
||||
| jdbc.minimumIdle | see above | No | see above |
|
||||
| enableLogTime | `false` | No | timekeeping |
|
||||
|
||||
All item- and event-related configuration is done in the file `persistence/jdbc.persist`.
|
||||
|
||||
To configure this service as the default persistence service for openHAB 2, add or change the line
|
||||
To configure this service as the default persistence service for openHAB, add or change the line
|
||||
|
||||
```
|
||||
org.openhab.core.persistence:default=jdbc
|
||||
|
@ -85,6 +86,13 @@ services/jdbc.cfg
|
|||
url=jdbc:postgresql://192.168.0.1:5432/testPostgresql
|
||||
```
|
||||
|
||||
### Case Sensitive Item Names
|
||||
|
||||
To avoid numbered suffixes entirely, `tableUseRealItemNames` and `tableCaseSensitiveItemNames` must both be enabled.
|
||||
With this configuration, tables are named exactly like their corresponding items.
|
||||
In order for this to work correctly, the underlying operating system, database server and configuration must support case sensitive table names.
|
||||
For MySQL, see [MySQL: Identifier Case Sensitivity](https://dev.mysql.com/doc/refman/8.0/en/identifier-case-sensitivity.html) for more information.
|
||||
|
||||
### Migration from MySQL to JDBC Persistence Services
|
||||
|
||||
The JDBC Persistence service can act as a replacement for the MySQL Persistence service.
|
||||
|
|
|
@ -84,8 +84,9 @@ public class JdbcBaseDAO {
|
|||
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 sqlDeleteItemsEntry = "DELETE FROM items WHERE ItemName=#itemname#";
|
||||
protected String sqlGetItemIDTableNames = "SELECT itemid, itemname FROM #itemsManageTable#";
|
||||
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= ?";
|
||||
|
@ -266,7 +267,7 @@ public class JdbcBaseDAO {
|
|||
public Long doCreateNewEntryInItemsTable(ItemsVO vo) {
|
||||
String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
|
||||
new String[] { "#itemsManageTable#", "#itemname#" },
|
||||
new String[] { vo.getItemsManageTable(), vo.getItemname() });
|
||||
new String[] { vo.getItemsManageTable(), vo.getItemName() });
|
||||
logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
|
||||
return Yank.insert(sql, null);
|
||||
}
|
||||
|
@ -280,9 +281,17 @@ public class JdbcBaseDAO {
|
|||
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 doDeleteItemsEntry(ItemsVO vo) {
|
||||
String sql = StringUtilsExt.replaceArrayMerge(sqlDeleteItemsEntry, new String[] { "#itemname#" },
|
||||
new String[] { vo.getItemname() });
|
||||
new String[] { vo.getItemName() });
|
||||
logger.debug("JDBC::doDeleteItemsEntry sql={}", sql);
|
||||
Yank.execute(sql, null);
|
||||
}
|
||||
|
@ -306,8 +315,9 @@ public class JdbcBaseDAO {
|
|||
* ITEM DAOs *
|
||||
*************/
|
||||
public void doUpdateItemTableNames(List<ItemVO> vol) {
|
||||
if (!vol.isEmpty()) {
|
||||
String sql = updateItemTableNamesProvider(vol);
|
||||
logger.debug("JDBC::doUpdateItemTableNames vol.size = {}", vol.size());
|
||||
for (ItemVO itemTable : vol) {
|
||||
String sql = updateItemTableNamesProvider(itemTable);
|
||||
Yank.execute(sql, null);
|
||||
}
|
||||
}
|
||||
|
@ -416,13 +426,8 @@ public class JdbcBaseDAO {
|
|||
return filterString;
|
||||
}
|
||||
|
||||
private String updateItemTableNamesProvider(List<ItemVO> namesList) {
|
||||
logger.debug("JDBC::updateItemTableNamesProvider namesList.size = {}", namesList.size());
|
||||
String queryString = "";
|
||||
for (int i = 0; i < namesList.size(); i++) {
|
||||
ItemVO it = namesList.get(i);
|
||||
queryString += "ALTER TABLE " + it.getTableName() + " RENAME TO " + it.getNewTableName() + ";";
|
||||
}
|
||||
private String updateItemTableNamesProvider(ItemVO itemTable) {
|
||||
String queryString = "ALTER TABLE " + itemTable.getTableName() + " RENAME TO " + itemTable.getNewTableName();
|
||||
logger.debug("JDBC::query queryString = {}", queryString);
|
||||
return queryString;
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ public class JdbcDerbyDAO extends JdbcBaseDAO {
|
|||
public Long doCreateNewEntryInItemsTable(ItemsVO vo) {
|
||||
String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
|
||||
new String[] { "#itemsManageTable#", "#itemname#" },
|
||||
new String[] { vo.getItemsManageTable().toUpperCase(), vo.getItemname() });
|
||||
new String[] { vo.getItemsManageTable().toUpperCase(), vo.getItemName() });
|
||||
logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
|
||||
return Yank.insert(sql, null);
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ public class JdbcHsqldbDAO extends JdbcBaseDAO {
|
|||
public Long doCreateNewEntryInItemsTable(ItemsVO vo) {
|
||||
String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
|
||||
new String[] { "#itemsManageTable#", "#itemname#" },
|
||||
new String[] { vo.getItemsManageTable(), vo.getItemname() });
|
||||
new String[] { vo.getItemsManageTable(), vo.getItemName() });
|
||||
logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
|
||||
return Yank.insert(sql, null);
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ public class JdbcPostgresqlDAO extends JdbcBaseDAO {
|
|||
public Long doCreateNewEntryInItemsTable(ItemsVO vo) {
|
||||
String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
|
||||
new String[] { "#itemsManageTable#", "#itemname#" },
|
||||
new String[] { vo.getItemsManageTable(), vo.getItemname() });
|
||||
new String[] { vo.getItemsManageTable(), vo.getItemName() });
|
||||
logger.debug("JDBC::doCreateNewEntryInItemsTable sql={}", sql);
|
||||
return Yank.insert(sql, null);
|
||||
}
|
||||
|
|
|
@ -28,9 +28,9 @@ public class ItemsVO implements Serializable {
|
|||
private String coltype = "VARCHAR(500)";
|
||||
private String colname = "itemname";
|
||||
private String itemsManageTable = "items";
|
||||
private int itemid;
|
||||
private String itemname;
|
||||
private String table_name;
|
||||
private int itemId;
|
||||
private String itemName;
|
||||
private String tableName;
|
||||
private String jdbcUriDatabaseName;
|
||||
|
||||
public String getColtype() {
|
||||
|
@ -57,28 +57,28 @@ public class ItemsVO implements Serializable {
|
|||
this.itemsManageTable = itemsManageTable.replaceAll(STR_FILTER, "");
|
||||
}
|
||||
|
||||
public int getItemid() {
|
||||
return itemid;
|
||||
public int getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
|
||||
public void setItemid(int itemid) {
|
||||
this.itemid = itemid;
|
||||
public void setItemId(int itemId) {
|
||||
this.itemId = itemId;
|
||||
}
|
||||
|
||||
public String getItemname() {
|
||||
return itemname;
|
||||
public String getItemName() {
|
||||
return itemName;
|
||||
}
|
||||
|
||||
public void setItemname(String itemname) {
|
||||
this.itemname = itemname;
|
||||
public void setItemName(String itemName) {
|
||||
this.itemName = itemName;
|
||||
}
|
||||
|
||||
public String getTable_name() {
|
||||
return table_name;
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
public void setTable_name(String table_name) {
|
||||
this.table_name = table_name;
|
||||
public void setTableName(String tableName) {
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
public String getJdbcUriDatabaseName() {
|
||||
|
@ -98,8 +98,8 @@ public class ItemsVO implements Serializable {
|
|||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((itemname == null) ? 0 : itemname.hashCode());
|
||||
result = prime * result + (itemid ^ (itemid >>> 32));
|
||||
result = prime * result + ((itemName == null) ? 0 : itemName.hashCode());
|
||||
result = prime * result + (itemId ^ (itemId >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -120,14 +120,14 @@ public class ItemsVO implements Serializable {
|
|||
return false;
|
||||
}
|
||||
ItemsVO other = (ItemsVO) obj;
|
||||
if (itemname == null) {
|
||||
if (other.itemname != null) {
|
||||
if (itemName == null) {
|
||||
if (other.itemName != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!itemname.equals(other.itemname)) {
|
||||
} else if (!itemName.equals(other.itemName)) {
|
||||
return false;
|
||||
}
|
||||
return itemid == other.itemid;
|
||||
return itemId == other.itemId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -140,11 +140,11 @@ public class ItemsVO implements Serializable {
|
|||
builder.append(", itemsManageTable=");
|
||||
builder.append(itemsManageTable);
|
||||
builder.append(", itemid=");
|
||||
builder.append(itemid);
|
||||
builder.append(itemId);
|
||||
builder.append(", itemname=");
|
||||
builder.append(itemname);
|
||||
builder.append(itemName);
|
||||
builder.append(", table_name=");
|
||||
builder.append(table_name);
|
||||
builder.append(tableName);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ public class JdbcConfiguration {
|
|||
// private String password;
|
||||
private int numberDecimalcount = 3;
|
||||
private boolean tableUseRealItemNames = false;
|
||||
private boolean tableCaseSensitiveItemNames = false;
|
||||
private String tableNamePrefix = "item";
|
||||
private int tableIdDigitCount = 4;
|
||||
private boolean rebuildTableNames = false;
|
||||
|
@ -163,6 +164,12 @@ public class JdbcConfiguration {
|
|||
logger.debug("JDBC::updateConfig: tableUseRealItemNames={}", tableUseRealItemNames);
|
||||
}
|
||||
|
||||
String lc = (String) configuration.get("tableCaseSensitiveItemNames");
|
||||
if (lc != null && !lc.isBlank()) {
|
||||
tableCaseSensitiveItemNames = Boolean.parseBoolean(lc);
|
||||
logger.debug("JDBC::updateConfig: tableCaseSensitiveItemNames={}", tableCaseSensitiveItemNames);
|
||||
}
|
||||
|
||||
String td = (String) configuration.get("tableIdDigitCount");
|
||||
if (td != null && !td.isBlank() && isNumericPattern.matcher(td).matches()) {
|
||||
tableIdDigitCount = Integer.parseInt(td);
|
||||
|
@ -363,6 +370,19 @@ public class JdbcConfiguration {
|
|||
return tableUseRealItemNames;
|
||||
}
|
||||
|
||||
public boolean getTableCaseSensitiveItemNames() {
|
||||
return tableCaseSensitiveItemNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if real item names (without number suffix) is enabled.
|
||||
*
|
||||
* @return true if both tableUseRealItemNames and tableCaseSensitiveItemNames are enabled.
|
||||
*/
|
||||
public boolean getTableUseRealCaseSensitiveItemNames() {
|
||||
return tableUseRealItemNames && tableCaseSensitiveItemNames;
|
||||
}
|
||||
|
||||
public int getTableIdDigitCount() {
|
||||
return tableIdDigitCount;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -53,10 +54,10 @@ public class JdbcMapper {
|
|||
protected int errCnt;
|
||||
protected boolean initialized = false;
|
||||
protected @NonNullByDefault({}) JdbcConfiguration conf;
|
||||
protected final Map<String, String> sqlTables = new HashMap<>();
|
||||
protected final Map<String, String> itemNameToTableNameMap = new HashMap<>();
|
||||
protected @NonNullByDefault({}) NamingStrategy namingStrategy;
|
||||
private long afterAccessMin = 10000;
|
||||
private long afterAccessMax = 0;
|
||||
private static final String ITEM_NAME_PATTERN = "[^a-zA-Z_0-9\\-]";
|
||||
|
||||
public JdbcMapper(TimeZoneProvider timeZoneProvider) {
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
|
@ -97,11 +98,19 @@ public class JdbcMapper {
|
|||
return res != null ? res : "";
|
||||
}
|
||||
|
||||
public boolean ifItemsTableExists() {
|
||||
logger.debug("JDBC::ifItemsTableExists");
|
||||
long timerStart = System.currentTimeMillis();
|
||||
boolean res = conf.getDBDAO().doIfTableExists(new ItemsVO());
|
||||
logTime("doIfTableExists", timerStart, System.currentTimeMillis());
|
||||
return res;
|
||||
}
|
||||
|
||||
public ItemsVO createNewEntryInItemsTable(ItemsVO vo) {
|
||||
logger.debug("JDBC::createNewEntryInItemsTable");
|
||||
long timerStart = System.currentTimeMillis();
|
||||
Long i = conf.getDBDAO().doCreateNewEntryInItemsTable(vo);
|
||||
vo.setItemid(i.intValue());
|
||||
vo.setItemId(i.intValue());
|
||||
logTime("doCreateNewEntryInItemsTable", timerStart, System.currentTimeMillis());
|
||||
return vo;
|
||||
}
|
||||
|
@ -114,6 +123,14 @@ public class JdbcMapper {
|
|||
return true;
|
||||
}
|
||||
|
||||
public boolean dropItemsTableIfExists(ItemsVO vo) {
|
||||
logger.debug("JDBC::dropItemsTableIfExists");
|
||||
long timerStart = System.currentTimeMillis();
|
||||
conf.getDBDAO().doDropItemsTableIfExists(vo);
|
||||
logTime("doDropItemsTableIfExists", timerStart, System.currentTimeMillis());
|
||||
return true;
|
||||
}
|
||||
|
||||
public ItemsVO deleteItemsEntry(ItemsVO vo) {
|
||||
logger.debug("JDBC::deleteItemsEntry");
|
||||
long timerStart = System.currentTimeMillis();
|
||||
|
@ -252,47 +269,66 @@ public class JdbcMapper {
|
|||
* DATABASE TABLEHANDLING *
|
||||
**************************/
|
||||
protected void checkDBSchema() {
|
||||
// Create Items Table if does not exist
|
||||
createItemsTableIfNot(new ItemsVO());
|
||||
if (!conf.getTableUseRealCaseSensitiveItemNames()) {
|
||||
createItemsTableIfNot(new ItemsVO());
|
||||
}
|
||||
if (conf.getRebuildTableNames()) {
|
||||
formatTableNames();
|
||||
|
||||
if (conf.getTableUseRealCaseSensitiveItemNames()) {
|
||||
dropItemsTableIfExists(new ItemsVO());
|
||||
}
|
||||
logger.info(
|
||||
"JDBC::checkDBSchema: Rebuild complete, configure the 'rebuildTableNames' setting to 'false' to stop rebuilds on startup");
|
||||
} else {
|
||||
// Reset the error counter
|
||||
errCnt = 0;
|
||||
}
|
||||
populateItemNameToTableNameMap();
|
||||
}
|
||||
|
||||
private void populateItemNameToTableNameMap() {
|
||||
itemNameToTableNameMap.clear();
|
||||
if (conf.getTableUseRealCaseSensitiveItemNames()) {
|
||||
for (String itemName : getItemTables().stream().map(t -> t.getTableName()).collect(Collectors.toList())) {
|
||||
itemNameToTableNameMap.put(itemName, itemName);
|
||||
}
|
||||
} else {
|
||||
for (ItemsVO vo : getItemIDTableNames()) {
|
||||
sqlTables.put(vo.getItemname(), getTableName(vo.getItemid(), vo.getItemname()));
|
||||
itemNameToTableNameMap.put(vo.getItemName(),
|
||||
namingStrategy.getTableName(vo.getItemId(), vo.getItemName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String getTable(Item item) {
|
||||
int rowId = 0;
|
||||
int itemId = 0;
|
||||
ItemsVO isvo;
|
||||
ItemVO ivo;
|
||||
|
||||
String itemName = item.getName();
|
||||
String tableName = sqlTables.get(itemName);
|
||||
String tableName = itemNameToTableNameMap.get(itemName);
|
||||
|
||||
// Table already exists - return the name
|
||||
if (tableName != null) {
|
||||
if (!Objects.isNull(tableName)) {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
logger.debug("JDBC::getTable: no table found for item '{}' in sqlTables", itemName);
|
||||
|
||||
// Create a new entry in items table
|
||||
isvo = new ItemsVO();
|
||||
isvo.setItemname(itemName);
|
||||
isvo = createNewEntryInItemsTable(isvo);
|
||||
rowId = isvo.getItemid();
|
||||
if (rowId == 0) {
|
||||
logger.error("JDBC::getTable: Creating table for item '{}' failed.", itemName);
|
||||
if (!conf.getTableUseRealCaseSensitiveItemNames()) {
|
||||
// Create a new entry in items table
|
||||
isvo = new ItemsVO();
|
||||
isvo.setItemName(itemName);
|
||||
isvo = createNewEntryInItemsTable(isvo);
|
||||
itemId = isvo.getItemId();
|
||||
if (itemId == 0) {
|
||||
logger.error("JDBC::getTable: Creating items entry for item '{}' failed.", itemName);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the table name
|
||||
logger.debug("JDBC::getTable: getTableName with rowId={} itemName={}", rowId, itemName);
|
||||
tableName = getTableName(rowId, itemName);
|
||||
logger.debug("JDBC::getTable: getTableName with rowId={} itemName={}", itemId, itemName);
|
||||
tableName = namingStrategy.getTableName(itemId, itemName);
|
||||
|
||||
// Create table for item
|
||||
String dataType = conf.getDBDAO().getDataType(item);
|
||||
|
@ -301,18 +337,8 @@ public class JdbcMapper {
|
|||
ivo = createItemTable(ivo);
|
||||
logger.debug("JDBC::getTable: Table created for item '{}' with dataType {} in SQL database.", itemName,
|
||||
dataType);
|
||||
sqlTables.put(itemName, tableName);
|
||||
|
||||
// Check if the new entry is in the table list
|
||||
// If it's not in the list, then there was an error and we need to do
|
||||
// some tidying up
|
||||
// The item needs to be removed from the index table to avoid duplicates
|
||||
if (sqlTables.get(itemName) == null) {
|
||||
logger.error("JDBC::getTable: Item '{}' was not added to the table - removing index", itemName);
|
||||
isvo = new ItemsVO();
|
||||
isvo.setItemname(itemName);
|
||||
deleteItemsEntry(isvo);
|
||||
}
|
||||
itemNameToTableNameMap.put(itemName, tableName);
|
||||
|
||||
return tableName;
|
||||
}
|
||||
|
@ -323,93 +349,57 @@ public class JdbcMapper {
|
|||
initialized = false;
|
||||
}
|
||||
|
||||
Map<Integer, String> tableIds = new HashMap<>();
|
||||
List<ItemsVO> itemIdTableNames = ifItemsTableExists() ? getItemIDTableNames() : new ArrayList<ItemsVO>();
|
||||
List<String> itemTables = getItemTables().stream().map(t -> t.getTableName()).collect(Collectors.toList());
|
||||
List<ItemVO> oldNewTableNames;
|
||||
|
||||
//
|
||||
for (ItemsVO vo : getItemIDTableNames()) {
|
||||
String t = getTableName(vo.getItemid(), vo.getItemname());
|
||||
sqlTables.put(vo.getItemname(), t);
|
||||
tableIds.put(vo.getItemid(), t);
|
||||
}
|
||||
|
||||
//
|
||||
List<ItemsVO> al = getItemTables();
|
||||
|
||||
String oldName = "";
|
||||
String newName = "";
|
||||
List<ItemVO> oldNewTablenames = new ArrayList<>();
|
||||
for (int i = 0; i < al.size(); i++) {
|
||||
int id = -1;
|
||||
oldName = al.get(i).getTable_name();
|
||||
logger.info("JDBC::formatTableNames: found Table Name= {}", oldName);
|
||||
|
||||
if (oldName.startsWith(conf.getTableNamePrefix()) && !oldName.contains("_")) {
|
||||
id = Integer.parseInt(oldName.substring(conf.getTableNamePrefix().length()));
|
||||
logger.info("JDBC::formatTableNames: found Table with Prefix '{}' Name= {} id= {}",
|
||||
conf.getTableNamePrefix(), oldName, (id));
|
||||
} else if (oldName.contains("_")) {
|
||||
id = Integer.parseInt(oldName.substring(oldName.lastIndexOf("_") + 1));
|
||||
logger.info("JDBC::formatTableNames: found Table Name= {} id= {}", oldName, (id));
|
||||
if (itemIdTableNames.isEmpty()) {
|
||||
// Without mappings we can only migrate from direct item name to numeric mapping.
|
||||
if (conf.getTableUseRealCaseSensitiveItemNames()) {
|
||||
logger.info("JDBC::formatTableNames: Nothing to migrate.");
|
||||
initialized = tmpinit;
|
||||
return;
|
||||
}
|
||||
logger.info("JDBC::formatTableNames: found Table id= {}", id);
|
||||
|
||||
newName = tableIds.get(id);
|
||||
logger.info("JDBC::formatTableNames: found Table newName= {}", newName);
|
||||
|
||||
if (newName != null) {
|
||||
if (!oldName.equalsIgnoreCase(newName)) {
|
||||
oldNewTablenames.add(new ItemVO(oldName, newName));
|
||||
logger.info("JDBC::formatTableNames: Table '{}' will be renamed to '{}'", oldName, newName);
|
||||
oldNewTableNames = new ArrayList<>();
|
||||
for (String itemName : itemTables) {
|
||||
ItemsVO isvo = new ItemsVO();
|
||||
isvo.setItemName(itemName);
|
||||
isvo = createNewEntryInItemsTable(isvo);
|
||||
int itemId = isvo.getItemId();
|
||||
if (itemId == 0) {
|
||||
logger.error("JDBC::formatTableNames: Creating items entry for item '{}' failed.", itemName);
|
||||
} else {
|
||||
logger.info("JDBC::formatTableNames: Table oldName='{}' newName='{}' nothing to rename", oldName,
|
||||
newName);
|
||||
String newTableName = namingStrategy.getTableName(itemId, itemName);
|
||||
oldNewTableNames.add(new ItemVO(itemName, newTableName));
|
||||
logger.info("JDBC::formatTableNames: Table '{}' will be renamed to '{}'", itemName, newTableName);
|
||||
}
|
||||
} else {
|
||||
logger.error("JDBC::formatTableNames: Table '{}' could NOT be renamed to '{}'", oldName, newName);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
String itemsManageTable = new ItemsVO().getItemsManageTable();
|
||||
Map<Integer, String> itemIdToItemNameMap = new HashMap<>();
|
||||
|
||||
for (ItemsVO vo : itemIdTableNames) {
|
||||
int itemId = vo.getItemId();
|
||||
String itemName = vo.getItemName();
|
||||
itemIdToItemNameMap.put(itemId, itemName);
|
||||
}
|
||||
|
||||
oldNewTableNames = namingStrategy.prepareMigration(itemTables, itemIdToItemNameMap, itemsManageTable);
|
||||
}
|
||||
|
||||
updateItemTableNames(oldNewTablenames);
|
||||
logger.info("JDBC::formatTableNames: Finished updating {} item table names", oldNewTablenames.size());
|
||||
updateItemTableNames(oldNewTableNames);
|
||||
logger.info("JDBC::formatTableNames: Finished updating {} item table names", oldNewTableNames.size());
|
||||
|
||||
initialized = tmpinit;
|
||||
}
|
||||
|
||||
private String getTableName(int rowId, String itemName) {
|
||||
return getTableNamePrefix(itemName) + formatRight(rowId, conf.getTableIdDigitCount());
|
||||
}
|
||||
|
||||
private String getTableNamePrefix(String itemName) {
|
||||
String name = conf.getTableNamePrefix();
|
||||
if (conf.getTableUseRealItemNames()) {
|
||||
// Create the table name with real Item Names
|
||||
name = (itemName.replaceAll(ITEM_NAME_PATTERN, "") + "_").toLowerCase();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public Set<PersistenceItemInfo> getItems() {
|
||||
// TODO: in general it would be possible to query the count, earliest and latest values for each item too but it
|
||||
// would be a very costly operation
|
||||
return sqlTables.keySet().stream().map(itemName -> new JdbcPersistenceItemInfo(itemName))
|
||||
return itemNameToTableNameMap.keySet().stream().map(itemName -> new JdbcPersistenceItemInfo(itemName))
|
||||
.collect(Collectors.<PersistenceItemInfo> toSet());
|
||||
}
|
||||
|
||||
private static String formatRight(final Object value, final int len) {
|
||||
final String valueAsString = String.valueOf(value);
|
||||
if (valueAsString.length() < len) {
|
||||
final StringBuffer result = new StringBuffer(len);
|
||||
for (int i = len - valueAsString.length(); i > 0; i--) {
|
||||
result.append('0');
|
||||
}
|
||||
result.append(valueAsString);
|
||||
return result.toString();
|
||||
} else {
|
||||
return valueAsString;
|
||||
}
|
||||
}
|
||||
|
||||
/*****************
|
||||
* H E L P E R S *
|
||||
*****************/
|
||||
|
|
|
@ -206,7 +206,7 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
|
|||
}
|
||||
}
|
||||
|
||||
String table = sqlTables.get(itemName);
|
||||
String table = itemNameToTableNameMap.get(itemName);
|
||||
if (table == null) {
|
||||
logger.debug("JDBC::query: unable to find table for item with name: '{}', no data in database.", itemName);
|
||||
return List.of();
|
||||
|
@ -229,6 +229,7 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
|
|||
|
||||
conf = new JdbcConfiguration(configuration);
|
||||
if (conf.valid && checkDBAccessability()) {
|
||||
namingStrategy = new NamingStrategy(conf);
|
||||
checkDBSchema();
|
||||
// connection has been established ... initialization completed!
|
||||
initialized = true;
|
||||
|
@ -259,7 +260,7 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
|
|||
throw new IllegalArgumentException("Item name must not be null");
|
||||
}
|
||||
|
||||
String table = sqlTables.get(itemName);
|
||||
String table = itemNameToTableNameMap.get(itemName);
|
||||
if (table == null) {
|
||||
logger.debug("JDBC::remove: unable to find table for item with name: '{}', no data in database.", itemName);
|
||||
return false;
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class manages strategy for table names.
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NamingStrategy {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NamingStrategy.class);
|
||||
|
||||
private JdbcConfiguration configuration;
|
||||
|
||||
public NamingStrategy(JdbcConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public String getTableName(int itemId, String itemName) {
|
||||
if (!ItemUtil.isValidItemName(itemName)) {
|
||||
throw new IllegalArgumentException(itemName + " is not a valid item name");
|
||||
}
|
||||
if (configuration.getTableUseRealItemNames()) {
|
||||
return formatTableName(itemName, itemId);
|
||||
} else {
|
||||
return configuration.getTableNamePrefix() + getSuffix(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
private String formatTableName(String itemName, int itemId) {
|
||||
if (configuration.getTableCaseSensitiveItemNames()) {
|
||||
return itemName;
|
||||
} else {
|
||||
return itemName.toLowerCase() + "_" + getSuffix(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
private String getSuffix(int itemId) {
|
||||
int digits = configuration.getTableIdDigitCount();
|
||||
if (digits > 0) {
|
||||
return String.format("%0" + configuration.getTableIdDigitCount() + "d", itemId);
|
||||
} else {
|
||||
return String.valueOf(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ItemVO> prepareMigration(List<String> itemTables, Map<Integer, String> itemIdToItemNameMap,
|
||||
String itemsManageTable) {
|
||||
List<ItemVO> oldNewTableNames = new ArrayList<>();
|
||||
Map<String, Integer> tableNameToItemIdMap = new HashMap<>();
|
||||
|
||||
for (Entry<Integer, String> entry : itemIdToItemNameMap.entrySet()) {
|
||||
String itemName = entry.getValue();
|
||||
tableNameToItemIdMap.put(itemName, entry.getKey());
|
||||
}
|
||||
|
||||
for (String oldName : itemTables) {
|
||||
Integer itemIdBoxed = tableNameToItemIdMap.get(oldName);
|
||||
int itemId = -1;
|
||||
|
||||
if (Objects.nonNull(itemIdBoxed)) {
|
||||
itemId = itemIdBoxed;
|
||||
logger.info("JDBC::formatTableNames: found by name; table name= {} id= {}", oldName, itemId);
|
||||
} else {
|
||||
try {
|
||||
itemId = Integer.parseInt(oldName.replaceFirst("^.*\\D", ""));
|
||||
logger.info("JDBC::formatTableNames: found by id; table name= {} id= {}", oldName, itemId);
|
||||
} catch (NumberFormatException e) {
|
||||
// Fall through.
|
||||
}
|
||||
}
|
||||
|
||||
String itemName = itemIdToItemNameMap.get(itemId);
|
||||
|
||||
if (!Objects.isNull(itemName)) {
|
||||
String newName = getTableName(itemId, itemName);
|
||||
if (newName.equalsIgnoreCase(itemsManageTable)) {
|
||||
logger.error(
|
||||
"JDBC::formatTableNames: Table '{}' could NOT be renamed to '{}' since it conflicts with manage table",
|
||||
oldName, newName);
|
||||
} else if (!oldName.equals(newName)) {
|
||||
oldNewTableNames.add(new ItemVO(oldName, newName));
|
||||
logger.info("JDBC::formatTableNames: Table '{}' will be renamed to '{}'", oldName, newName);
|
||||
} else {
|
||||
logger.info("JDBC::formatTableNames: Table oldName='{}' newName='{}' nothing to rename", oldName,
|
||||
newName);
|
||||
}
|
||||
} else {
|
||||
logger.error("JDBC::formatTableNames: Table '{}' could NOT be renamed for id '{}'", oldName, itemId);
|
||||
}
|
||||
}
|
||||
|
||||
return oldNewTableNames;
|
||||
}
|
||||
}
|
|
@ -143,6 +143,11 @@
|
|||
#tableUseRealItemNames=
|
||||
tableUseRealItemNames=true
|
||||
|
||||
# Tablename Prefix generation, using case sensitive item names (optional, default: disabled -> table names are lower cased
|
||||
# with numeric suffix appended).
|
||||
# If true, no suffix is used.
|
||||
#tableCaseSensitiveItemNames=true
|
||||
|
||||
# Tablename Suffix length (optional, default: 4 -> 0001-9999)
|
||||
# for Migration from MYSQL-Bundle set to 0.
|
||||
#tableIdDigitCount=
|
||||
|
@ -165,6 +170,15 @@
|
|||
<option value="false">Disable</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="tableCaseSensitiveItemNames" type="text">
|
||||
<label>Tablename Case Sensitive</label>
|
||||
<description><![CDATA[Enables Tablename generation with case sensitive item names case when "Tablename Realname Generation" is enabled <br>
|
||||
If true, no suffix is used. (optional, default: disabled -> table names are lower cased with numeric suffix appended).]]></description>
|
||||
<options>
|
||||
<option value="true">Enable</option>
|
||||
<option value="false">Disable</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="tableIdDigitCount" type="text">
|
||||
<label>Tablename Suffix ID Count</label>
|
||||
<description><![CDATA[Tablename Suffix ID Count <br>(optional, default: 4 -> 0001-9999). <br>
|
||||
|
@ -172,7 +186,8 @@
|
|||
</parameter>
|
||||
<parameter name="rebuildTableNames" type="text">
|
||||
<label>Tablename Rebuild</label>
|
||||
<description><![CDATA[Rename existing tables using 'Tablename Realname Generation' and 'Tablename Suffix ID Count', (optional, default: disabled). <br>
|
||||
<description><![CDATA[Rename existing tables using 'Tablename Prefix String', 'Tablename Realname Generation', 'Tablename Case Sensitive' and
|
||||
'Tablename Suffix ID Count'. (optional, default: disabled). <br>
|
||||
USE WITH CARE! Deactivate after renaming is done!]]></description>
|
||||
<options>
|
||||
<option value="true">Enable</option>
|
||||
|
|
|
@ -9,7 +9,7 @@ persistence.config.jdbc.minimumIdle.description = Overrides min idle database co
|
|||
persistence.config.jdbc.password.label = Database Password
|
||||
persistence.config.jdbc.password.description = Defines the database password.
|
||||
persistence.config.jdbc.rebuildTableNames.label = Tablename Rebuild
|
||||
persistence.config.jdbc.rebuildTableNames.description = Rename existing tables using 'Tablename Realname Generation' and 'Tablename Suffix ID Count', (optional, default: disabled). <br> USE WITH CARE! Deactivate after renaming is done!
|
||||
persistence.config.jdbc.rebuildTableNames.description = Rename existing tables using 'Tablename Prefix String', 'Tablename Realname Generation', 'Tablename Case Sensitive' and 'Tablename Suffix ID Count'. (optional, default: disabled). <br> USE WITH CARE! Deactivate after renaming is done!
|
||||
persistence.config.jdbc.rebuildTableNames.option.true = Enable
|
||||
persistence.config.jdbc.rebuildTableNames.option.false = Disable
|
||||
persistence.config.jdbc.sqltype.CALL.label = SqlType CALL
|
||||
|
@ -36,6 +36,10 @@ persistence.config.jdbc.sqltype.STRING.label = SqlType STRING
|
|||
persistence.config.jdbc.sqltype.STRING.description = Overrides used JDBC/SQL datatype for STRING <br>(optional, default: "VARCHAR(65500)").
|
||||
persistence.config.jdbc.sqltype.SWITCH.label = SqlType SWITCH
|
||||
persistence.config.jdbc.sqltype.SWITCH.description = Overrides used JDBC/SQL datatype for SWITCH <br>(optional, default: "VARCHAR(6)").
|
||||
persistence.config.jdbc.tableCaseSensitiveItemNames.label = Tablename Case Sensitive
|
||||
persistence.config.jdbc.tableCaseSensitiveItemNames.description = Enables Tablename generation with case sensitive item names case when "Tablename Realname Generation" is enabled <br> If true, no suffix is used. (optional, default: disabled -> table names are lower cased with numeric suffix appended).
|
||||
persistence.config.jdbc.tableCaseSensitiveItemNames.option.true = Enable
|
||||
persistence.config.jdbc.tableCaseSensitiveItemNames.option.false = Disable
|
||||
persistence.config.jdbc.tableIdDigitCount.label = Tablename Suffix ID Count
|
||||
persistence.config.jdbc.tableIdDigitCount.description = Tablename Suffix ID Count <br>(optional, default: 4 -> 0001-9999). <br> For migration from MYSQL-Bundle set to 0.
|
||||
persistence.config.jdbc.tableNamePrefix.label = Tablename Prefix String
|
||||
|
|
|
@ -0,0 +1,444 @@
|
|||
/**
|
||||
* 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 static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
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.slf4j.LoggerFactory;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
|
||||
/**
|
||||
* Tests the {@link NamingStrategy} class.
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@NonNullByDefault
|
||||
public class NamingStrategyTest {
|
||||
private static final String ITEMS_MANAGE_TABLE_NAME = "items";
|
||||
|
||||
private @Mock @NonNullByDefault({}) JdbcConfiguration configurationMock;
|
||||
private NamingStrategy namingStrategy = new NamingStrategy(configurationMock);
|
||||
|
||||
@BeforeEach
|
||||
public void initialize() {
|
||||
final Logger logger = (Logger) LoggerFactory.getLogger(NamingStrategy.class);
|
||||
logger.setLevel(Level.OFF);
|
||||
namingStrategy = new NamingStrategy(configurationMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTableNameWhenInvalidItemNameThrows() {
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> {
|
||||
namingStrategy.getTableName(1, "4Two");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTableNameWhenUseRealItemNamesNameIsLowerCaseAndNumbered() {
|
||||
Mockito.doReturn(true).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn(false).when(configurationMock).getTableCaseSensitiveItemNames();
|
||||
assertThat(namingStrategy.getTableName(1, "Test"), is("test_1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTableNameWhenUseRealCaseSensitiveItemNamesNameIsSameCase() {
|
||||
Mockito.doReturn(true).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn(true).when(configurationMock).getTableCaseSensitiveItemNames();
|
||||
assertThat(namingStrategy.getTableName(1, "Camel"), is("Camel"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTableNameWhenUseRealCaseSensitiveItemNamesNameIsSameCaseLower() {
|
||||
Mockito.doReturn(true).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn(true).when(configurationMock).getTableCaseSensitiveItemNames();
|
||||
assertThat(namingStrategy.getTableName(1, "lower"), is("lower"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTableNameWhenNotUseRealItemNamesAndCount4NameHasLeavingZeros() {
|
||||
Mockito.doReturn(false).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn(4).when(configurationMock).getTableIdDigitCount();
|
||||
Mockito.doReturn("Item").when(configurationMock).getTableNamePrefix();
|
||||
assertThat(namingStrategy.getTableName(2, "Test"), is("Item0002"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTableNameWhenNotUseRealItemNamesAndCount0() {
|
||||
Mockito.doReturn(false).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn(0).when(configurationMock).getTableIdDigitCount();
|
||||
Mockito.doReturn("Item").when(configurationMock).getTableNamePrefix();
|
||||
assertThat(namingStrategy.getTableName(12345, "Test"), is("Item12345"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromNumberedToRealNames() {
|
||||
final int itemId = 1;
|
||||
final String itemName = "Test";
|
||||
final String tableName = "Item1";
|
||||
|
||||
List<ItemVO> actual = prepareMigrationRealItemNames(itemId, itemName, tableName);
|
||||
|
||||
assertTableName(actual, "Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationWithChangedPrefix() {
|
||||
Mockito.doReturn(0).when(configurationMock).getTableIdDigitCount();
|
||||
Mockito.doReturn(false).when(configurationMock).getTableUseRealItemNames();
|
||||
|
||||
final int itemId = 1;
|
||||
final String itemName = "Test";
|
||||
final String tableName = "Item1";
|
||||
|
||||
List<ItemVO> actual = prepareMigration(itemId, itemName, tableName, "item");
|
||||
|
||||
assertTableName(actual, "item1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationShouldNotStopWhenEncounteringUnknownItem() {
|
||||
Mockito.doReturn(true).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn(true).when(configurationMock).getTableCaseSensitiveItemNames();
|
||||
Mockito.doReturn("Item").when(configurationMock).getTableNamePrefix();
|
||||
|
||||
Map<Integer, String> itemIdToItemNameMap = new HashMap<>(2);
|
||||
itemIdToItemNameMap.put(1, "First");
|
||||
itemIdToItemNameMap.put(3, "Third");
|
||||
|
||||
List<String> itemTables = new ArrayList<String>(3);
|
||||
itemTables.add("Item1");
|
||||
itemTables.add("Item2");
|
||||
itemTables.add("Item3");
|
||||
|
||||
List<ItemVO> actual = namingStrategy.prepareMigration(itemTables, itemIdToItemNameMap, ITEMS_MANAGE_TABLE_NAME);
|
||||
|
||||
assertThat(actual.size(), is(2));
|
||||
assertThat(actual.get(0).getNewTableName(), is("First"));
|
||||
assertThat(actual.get(1).getNewTableName(), is("Third"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromMixedNumberedToNumberedRealNames() {
|
||||
Mockito.doReturn(true).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn(false).when(configurationMock).getTableCaseSensitiveItemNames();
|
||||
Mockito.doReturn("Item").when(configurationMock).getTableNamePrefix();
|
||||
|
||||
Map<Integer, String> itemIdToItemNameMap = new HashMap<>(3);
|
||||
itemIdToItemNameMap.put(1, "First");
|
||||
itemIdToItemNameMap.put(2, "Second");
|
||||
itemIdToItemNameMap.put(3, "Third");
|
||||
|
||||
List<String> itemTables = new ArrayList<String>(3);
|
||||
itemTables.add("Item1");
|
||||
itemTables.add("Item002");
|
||||
itemTables.add("third_0003");
|
||||
|
||||
List<ItemVO> actual = namingStrategy.prepareMigration(itemTables, itemIdToItemNameMap, ITEMS_MANAGE_TABLE_NAME);
|
||||
|
||||
assertThat(actual.size(), is(3));
|
||||
assertThat(actual.get(0).getNewTableName(), is("first_1"));
|
||||
assertThat(actual.get(1).getNewTableName(), is("second_2"));
|
||||
assertThat(actual.get(2).getNewTableName(), is("third_3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromMixedNumberedToCaseSensitiveRealNames() {
|
||||
Mockito.doReturn(true).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn(true).when(configurationMock).getTableCaseSensitiveItemNames();
|
||||
Mockito.doReturn("Item").when(configurationMock).getTableNamePrefix();
|
||||
|
||||
Map<Integer, String> itemIdToItemNameMap = new HashMap<>(3);
|
||||
itemIdToItemNameMap.put(1, "First");
|
||||
itemIdToItemNameMap.put(2, "Second");
|
||||
itemIdToItemNameMap.put(3, "Third");
|
||||
|
||||
List<String> itemTables = new ArrayList<String>(3);
|
||||
itemTables.add("Item1");
|
||||
itemTables.add("Item002");
|
||||
itemTables.add("third_0003");
|
||||
|
||||
List<ItemVO> actual = namingStrategy.prepareMigration(itemTables, itemIdToItemNameMap, ITEMS_MANAGE_TABLE_NAME);
|
||||
|
||||
assertThat(actual.size(), is(3));
|
||||
assertThat(actual.get(0).getNewTableName(), is("First"));
|
||||
assertThat(actual.get(1).getNewTableName(), is("Second"));
|
||||
assertThat(actual.get(2).getNewTableName(), is("Third"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromNumberedRealNamesToCaseSensitiveRealNames() {
|
||||
final int itemId = 1;
|
||||
final String itemName = "Test";
|
||||
final String tableName = "test_0001";
|
||||
|
||||
List<ItemVO> actual = prepareMigrationRealItemNames(itemId, itemName, tableName, true);
|
||||
|
||||
assertTableName(actual, "Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromCaseSensitiveRealNamesToNumberedRealNames() {
|
||||
final int itemId = 1;
|
||||
final String itemName = "Test";
|
||||
final String tableName = "Test";
|
||||
|
||||
List<ItemVO> actual = prepareMigrationRealItemNames(itemId, itemName, tableName, false);
|
||||
|
||||
assertTableName(actual, "test_0001");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationRealNamesWithTwoItemsWithDifferentCaseToNumbered() {
|
||||
Mockito.doReturn(false).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn("Item").when(configurationMock).getTableNamePrefix();
|
||||
Mockito.doReturn(1).when(configurationMock).getTableIdDigitCount();
|
||||
|
||||
Map<Integer, String> itemIdToItemNameMap = new HashMap<>(2);
|
||||
itemIdToItemNameMap.put(1, "MyItem");
|
||||
itemIdToItemNameMap.put(2, "myItem");
|
||||
|
||||
List<String> itemTables = new ArrayList<String>(2);
|
||||
itemTables.add("MyItem");
|
||||
itemTables.add("myItem");
|
||||
|
||||
List<ItemVO> actual = namingStrategy.prepareMigration(itemTables, itemIdToItemNameMap, ITEMS_MANAGE_TABLE_NAME);
|
||||
|
||||
assertThat(actual.size(), is(2));
|
||||
assertThat(actual.get(0).getNewTableName(), is("Item1"));
|
||||
assertThat(actual.get(1).getNewTableName(), is("Item2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationNumberedWithTwoItemsWithDifferentCaseToNumberedRealNames() {
|
||||
Mockito.doReturn(true).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn("Item").when(configurationMock).getTableNamePrefix();
|
||||
Mockito.doReturn(false).when(configurationMock).getTableCaseSensitiveItemNames();
|
||||
|
||||
Map<Integer, String> itemIdToItemNameMap = new HashMap<>(2);
|
||||
itemIdToItemNameMap.put(1, "MyItem");
|
||||
itemIdToItemNameMap.put(2, "myItem");
|
||||
|
||||
List<String> itemTables = new ArrayList<String>(2);
|
||||
itemTables.add("Item1");
|
||||
itemTables.add("Item2");
|
||||
|
||||
List<ItemVO> actual = namingStrategy.prepareMigration(itemTables, itemIdToItemNameMap, ITEMS_MANAGE_TABLE_NAME);
|
||||
|
||||
assertThat(actual.size(), is(2));
|
||||
assertThat(actual.get(0).getNewTableName(), is("myitem_1"));
|
||||
assertThat(actual.get(1).getNewTableName(), is("myitem_2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationNumberedWithTwoItemsWithDifferentCaseToCaseSensitiveRealNames() {
|
||||
Mockito.doReturn(true).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn("Item").when(configurationMock).getTableNamePrefix();
|
||||
Mockito.doReturn(true).when(configurationMock).getTableCaseSensitiveItemNames();
|
||||
|
||||
Map<Integer, String> itemIdToItemNameMap = new HashMap<>(2);
|
||||
itemIdToItemNameMap.put(1, "MyItem");
|
||||
itemIdToItemNameMap.put(2, "myItem");
|
||||
|
||||
List<String> itemTables = new ArrayList<String>(2);
|
||||
itemTables.add("Item1");
|
||||
itemTables.add("Item2");
|
||||
|
||||
List<ItemVO> actual = namingStrategy.prepareMigration(itemTables, itemIdToItemNameMap, ITEMS_MANAGE_TABLE_NAME);
|
||||
|
||||
assertThat(actual.size(), is(2));
|
||||
assertThat(actual.get(0).getNewTableName(), is("MyItem"));
|
||||
assertThat(actual.get(1).getNewTableName(), is("myItem"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromNumberedRealNamesToCaseSensitiveRealNamesWhenUnknownItemIdThenSkip() {
|
||||
final int itemId = 2;
|
||||
final String itemName = "Test";
|
||||
final String tableName = "test_0001";
|
||||
|
||||
List<ItemVO> actual = prepareMigrationRealItemNames(itemId, itemName, tableName);
|
||||
|
||||
assertThat(actual.size(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromNumberedRealNamesToNumbered() {
|
||||
final int itemId = 1;
|
||||
final String itemName = "Test";
|
||||
final String tableName = "test_0001";
|
||||
|
||||
List<ItemVO> actual = prepareMigrationNumbered(itemId, itemName, tableName);
|
||||
|
||||
assertTableName(actual, "Item0001");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromNumberedToNumberedWithCorrectPadding() {
|
||||
final int itemId = 1;
|
||||
final String itemName = "Test";
|
||||
final String tableName = "Item1";
|
||||
|
||||
List<ItemVO> actual = prepareMigrationNumbered(itemId, itemName, tableName, 2);
|
||||
|
||||
assertTableName(actual, "Item01");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromNumberedToNumberedExceedingPadding() {
|
||||
final int itemId = 101;
|
||||
final String itemName = "Test";
|
||||
final String tableName = "Item0101";
|
||||
|
||||
List<ItemVO> actual = prepareMigrationNumbered(itemId, itemName, tableName, 2);
|
||||
|
||||
assertTableName(actual, "Item101");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromCaseSensitiveRealNamesToNumbered() {
|
||||
final int itemId = 1;
|
||||
final String itemName = "Test";
|
||||
final String tableName = "Test";
|
||||
|
||||
List<ItemVO> actual = prepareMigrationNumbered(itemId, itemName, tableName);
|
||||
|
||||
assertTableName(actual, "Item0001");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromCaseSensitiveRealNamesToNumberedHavingUnderscore() {
|
||||
final int itemId = 1;
|
||||
final String itemName = "My_Test";
|
||||
final String tableName = "My_Test";
|
||||
|
||||
List<ItemVO> actual = prepareMigrationNumbered(itemId, itemName, tableName);
|
||||
|
||||
assertTableName(actual, "Item0001");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromCaseSensitiveRealNamesHavingUnderscoreAndNumberToNumbered() {
|
||||
final int itemId = 2;
|
||||
final String itemName = "My_Test_1";
|
||||
final String tableName = "My_Test_1";
|
||||
|
||||
List<ItemVO> actual = prepareMigrationNumbered(itemId, itemName, tableName);
|
||||
|
||||
assertTableName(actual, "Item0002");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationFromCaseSensitiveRealNamesToNumberedShouldSwap() {
|
||||
Mockito.doReturn(false).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn("Item").when(configurationMock).getTableNamePrefix();
|
||||
|
||||
Map<Integer, String> itemIdToItemNameMap = new HashMap<>(2);
|
||||
itemIdToItemNameMap.put(1, "Item2");
|
||||
itemIdToItemNameMap.put(2, "Item1");
|
||||
|
||||
List<String> itemTables = new ArrayList<String>(2);
|
||||
itemTables.add("Item2");
|
||||
itemTables.add("Item1");
|
||||
|
||||
List<ItemVO> actual = namingStrategy.prepareMigration(itemTables, itemIdToItemNameMap, ITEMS_MANAGE_TABLE_NAME);
|
||||
|
||||
assertThat(actual.size(), is(2));
|
||||
assertThat(actual.get(0).getNewTableName(), is("Item1"));
|
||||
assertThat(actual.get(1).getNewTableName(), is("Item2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prepareMigrationWhenConflictWithItemsManageTableThenSkip() {
|
||||
final int itemId = 1;
|
||||
final String itemName = "items";
|
||||
final String tableName = "Item1";
|
||||
|
||||
List<ItemVO> actual = prepareMigrationRealItemNames(itemId, itemName, tableName);
|
||||
|
||||
assertThat(actual.size(), is(0));
|
||||
}
|
||||
|
||||
private List<ItemVO> prepareMigrationNumbered(int itemId, String itemName, String tableName) {
|
||||
return prepareMigrationNumbered(itemId, itemName, tableName, 4);
|
||||
}
|
||||
|
||||
private List<ItemVO> prepareMigrationNumbered(int itemId, String itemName, String tableName,
|
||||
int tableIdDigitCount) {
|
||||
Mockito.doReturn(tableIdDigitCount).when(configurationMock).getTableIdDigitCount();
|
||||
Mockito.doReturn(false).when(configurationMock).getTableUseRealItemNames();
|
||||
return prepareMigration(itemId, itemName, tableName);
|
||||
}
|
||||
|
||||
private List<ItemVO> prepareMigrationRealItemNames(int itemId, String itemName, String tableName) {
|
||||
return prepareMigrationRealItemNames(itemId, itemName, tableName, true);
|
||||
}
|
||||
|
||||
private List<ItemVO> prepareMigrationRealItemNames(int itemId, String itemName, String tableName,
|
||||
boolean caseSensitive) {
|
||||
Mockito.doReturn(4).when(configurationMock).getTableIdDigitCount();
|
||||
Mockito.doReturn(true).when(configurationMock).getTableUseRealItemNames();
|
||||
Mockito.doReturn(caseSensitive).when(configurationMock).getTableCaseSensitiveItemNames();
|
||||
return prepareMigration(itemId, itemName, tableName);
|
||||
}
|
||||
|
||||
private List<ItemVO> prepareMigration(int itemId, String itemName, String tableName) {
|
||||
return prepareMigration(itemId, itemName, tableName, "Item");
|
||||
}
|
||||
|
||||
private List<ItemVO> prepareMigration(int itemId, String itemName, String tableName, String prefix) {
|
||||
Mockito.doReturn(prefix).when(configurationMock).getTableNamePrefix();
|
||||
|
||||
Map<Integer, String> itemIdToItemNameMap = getItemIdToItemNameMap(itemId, itemName);
|
||||
List<String> itemTables = getItemTables(tableName);
|
||||
|
||||
return namingStrategy.prepareMigration(itemTables, itemIdToItemNameMap, ITEMS_MANAGE_TABLE_NAME);
|
||||
}
|
||||
|
||||
private Map<Integer, String> getItemIdToItemNameMap(int itemId, String itemName) {
|
||||
Map<Integer, String> itemIdToItemNameMap = new HashMap<>(1);
|
||||
itemIdToItemNameMap.put(itemId, itemName);
|
||||
return itemIdToItemNameMap;
|
||||
}
|
||||
|
||||
private List<String> getItemTables(String tableName) {
|
||||
List<String> itemTables = new ArrayList<String>(1);
|
||||
itemTables.add(tableName);
|
||||
return itemTables;
|
||||
}
|
||||
|
||||
private void assertTableName(List<ItemVO> actual, String expected) {
|
||||
assertThat(actual.size(), is(1));
|
||||
assertThat(actual.get(0).getNewTableName(), is(expected));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue