[jdbc] Add console maintenance commands (#13662)
* Add console command for listing tables * Query row counts only when needed and while generating output * Add cleanup command * Add documentation Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
parent
248ca1830a
commit
159054a99c
|
@ -29,6 +29,7 @@ The following databases are currently supported and tested:
|
|||
- [Database Table Schema](#database-table-schema)
|
||||
- [Number Precision](#number-precision)
|
||||
- [Rounding results](#rounding-results)
|
||||
- [Maintenance](#maintenance)
|
||||
- [For Developers](#for-developers)
|
||||
- [Performance Tests](#performance-tests)
|
||||
|
||||
|
@ -138,6 +139,62 @@ The results of database queries of number items are rounded to three decimal pla
|
|||
With `numberDecimalcount` decimals can be changed.
|
||||
Especially if sql types `DECIMAL` or `NUMERIC` are used for `sqltype.NUMBER`, rounding can be disabled by setting `numberDecimalcount=-1`.
|
||||
|
||||
### Maintenance
|
||||
|
||||
Some maintenance tools are provided as console commands.
|
||||
|
||||
#### List Tables
|
||||
|
||||
Tables and corresponding items can be listed with the command `jdbc tables list`.
|
||||
Per default only tables with some kind of problem are listed.
|
||||
To list all tables, use the command `jdbc tables list all`.
|
||||
|
||||
The list contains table name, item name, row count and status, which can be one of:
|
||||
|
||||
- **Valid:** Table is consistent.
|
||||
- **Item missing:** Table has no corresponding item.
|
||||
- **Table missing:** Referenced table does not exist.
|
||||
- **Item and table missing:** Referenced table does not exist nor has corresponding item.
|
||||
- **Orphan table:** Mapping for table does not exist in index.
|
||||
|
||||
#### Clean Inconsistent Items
|
||||
|
||||
Some issues can be fixed automatically using the command `jdbc clean` (all items having issues) or `jdbc clean <itemName>` (single item).
|
||||
This cleanup operation will remove items from the index (table `Items`) if the referenced table does not exist.
|
||||
|
||||
If the item does not exist, the table will be physically deleted, but only if it's empty.
|
||||
This precaution is taken because items may have existed previously, and the data might still be valuable.
|
||||
For example, an item for a lost or repurposed sensor could have been deleted from the system while preserving persisted data.
|
||||
To skip this check for a single item, use `jdbc clean <itemName> force` with care.
|
||||
|
||||
Prior to performing a `jdbc clean` operation, it's recommended to review the result of `jdbc tables list`.
|
||||
|
||||
Fixing integrity issues can be useful before performing a migration to another naming scheme.
|
||||
For example, when migrating to `tableCaseSensitiveItemNames`, an index will no longer exist after the migration:
|
||||
|
||||
**Before migration:**
|
||||
|
||||
| Table | Row count | Item | Status |
|
||||
|-------------------|---------: |--------|---------------|
|
||||
| ActualItem | 0 | | Orphan table |
|
||||
| TableNotBelonging | 0 | | Orphan table |
|
||||
| item0077 | 0 | MyItem | Table missing |
|
||||
|
||||
**After migration:**
|
||||
|
||||
| Table | Row count | Item | Status |
|
||||
|-------------------|---------: |-------------------|---------------|
|
||||
| ActualItem | 0 | ActualItem | Valid |
|
||||
| TableNotBelonging | 0 | TableNotBelonging | Item missing |
|
||||
|
||||
This happened:
|
||||
|
||||
- `ActualItem` was missing in the index and became valid because it was left untouched, not being a part of the migration. After the migration, it happened to match the name of an existing item, thus it became valid.
|
||||
- `TableNotBelonging` was also not part of the migration, but since now assumed to match an item, status changed since no item with that name exists.
|
||||
- `item0077`, being the only correct table name according to previous naming scheme, disappeared from the list since it didn't have a corresponding table, and is now no longer part of any index.
|
||||
|
||||
In other words, extracting this information from the index before removing it, can be beneficial in order to understand the issues and possible causes.
|
||||
|
||||
### For Developers
|
||||
|
||||
* Clearly separated source files for the database-specific part of openHAB logic.
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.persistence.jdbc;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This class represents a checked item/table relation.
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ItemTableCheckEntry {
|
||||
private String itemName;
|
||||
private String tableName;
|
||||
private ItemTableCheckEntryStatus status;
|
||||
|
||||
public ItemTableCheckEntry(String itemName, String tableName, ItemTableCheckEntryStatus status) {
|
||||
this.itemName = itemName;
|
||||
this.tableName = tableName;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getItemName() {
|
||||
return itemName;
|
||||
}
|
||||
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
|
||||
public ItemTableCheckEntryStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.persistence.jdbc;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* This class represents status for an {@link ItemTableCheckEntry}.
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ItemTableCheckEntryStatus {
|
||||
/**
|
||||
* Table is consistent.
|
||||
*/
|
||||
VALID {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Valid";
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Table has no corresponding item.
|
||||
*/
|
||||
ITEM_MISSING {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Item missing";
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Referenced table does not exist.
|
||||
*/
|
||||
TABLE_MISSING {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Table missing";
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Referenced table does not exist nor has corresponding item.
|
||||
*/
|
||||
ITEM_AND_TABLE_MISSING {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Item and table missing";
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Mapping for table does not exist in index.
|
||||
*/
|
||||
ORPHAN_TABLE {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Orphan table";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.persistence.jdbc.console;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
import org.openhab.core.io.console.StringsCompleter;
|
||||
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.openhab.core.persistence.PersistenceService;
|
||||
import org.openhab.core.persistence.PersistenceServiceRegistry;
|
||||
import org.openhab.persistence.jdbc.ItemTableCheckEntry;
|
||||
import org.openhab.persistence.jdbc.ItemTableCheckEntryStatus;
|
||||
import org.openhab.persistence.jdbc.internal.JdbcPersistenceService;
|
||||
import org.openhab.persistence.jdbc.internal.JdbcPersistenceServiceConstants;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link JdbcCommandExtension} is responsible for handling console commands
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ConsoleCommandExtension.class)
|
||||
public class JdbcCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
|
||||
|
||||
private static final String CMD_TABLES = "tables";
|
||||
private static final String SUBCMD_TABLES_LIST = "list";
|
||||
private static final String SUBCMD_TABLES_CLEAN = "clean";
|
||||
private static final String PARAMETER_ALL = "all";
|
||||
private static final String PARAMETER_FORCE = "force";
|
||||
private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(List.of(CMD_TABLES), false);
|
||||
private static final StringsCompleter SUBCMD_TABLES_COMPLETER = new StringsCompleter(
|
||||
List.of(SUBCMD_TABLES_LIST, SUBCMD_TABLES_CLEAN), false);
|
||||
|
||||
private final PersistenceServiceRegistry persistenceServiceRegistry;
|
||||
|
||||
@Activate
|
||||
public JdbcCommandExtension(final @Reference PersistenceServiceRegistry persistenceServiceRegistry) {
|
||||
super(JdbcPersistenceServiceConstants.SERVICE_ID, "Interact with the JDBC persistence service.");
|
||||
this.persistenceServiceRegistry = persistenceServiceRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String[] args, Console console) {
|
||||
if (args.length < 2 || args.length > 4 || !CMD_TABLES.equals(args[0])) {
|
||||
printUsage(console);
|
||||
return;
|
||||
}
|
||||
JdbcPersistenceService persistenceService = getPersistenceService();
|
||||
if (persistenceService == null) {
|
||||
return;
|
||||
}
|
||||
if (SUBCMD_TABLES_LIST.equalsIgnoreCase(args[1])) {
|
||||
listTables(persistenceService, console, args.length == 3 && PARAMETER_ALL.equalsIgnoreCase(args[2]));
|
||||
return;
|
||||
} else if (SUBCMD_TABLES_CLEAN.equalsIgnoreCase(args[1])) {
|
||||
if (args.length == 3) {
|
||||
cleanupItem(persistenceService, console, args[2], false);
|
||||
return;
|
||||
} else if (args.length == 4 && PARAMETER_FORCE.equalsIgnoreCase(args[3])) {
|
||||
cleanupItem(persistenceService, console, args[2], true);
|
||||
return;
|
||||
} else {
|
||||
cleanupTables(persistenceService, console);
|
||||
return;
|
||||
}
|
||||
}
|
||||
printUsage(console);
|
||||
}
|
||||
|
||||
private @Nullable JdbcPersistenceService getPersistenceService() {
|
||||
for (PersistenceService persistenceService : persistenceServiceRegistry.getAll()) {
|
||||
if (persistenceService instanceof JdbcPersistenceService) {
|
||||
return (JdbcPersistenceService) persistenceService;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void listTables(JdbcPersistenceService persistenceService, Console console, Boolean all) {
|
||||
List<ItemTableCheckEntry> entries = persistenceService.getCheckedEntries();
|
||||
if (!all) {
|
||||
entries.removeIf(t -> t.getStatus() == ItemTableCheckEntryStatus.VALID);
|
||||
}
|
||||
entries.sort(Comparator.comparing(ItemTableCheckEntry::getTableName));
|
||||
int itemNameMaxLength = Math
|
||||
.max(entries.stream().map(t -> t.getItemName().length()).max(Integer::compare).get(), 4);
|
||||
int tableNameMaxLength = Math
|
||||
.max(entries.stream().map(t -> t.getTableName().length()).max(Integer::compare).get(), 5);
|
||||
int statusMaxLength = Stream.of(ItemTableCheckEntryStatus.values()).map(t -> t.toString().length())
|
||||
.max(Integer::compare).get();
|
||||
console.println(String.format(
|
||||
"%1$-" + (tableNameMaxLength + 2) + "sRow Count %2$-" + (itemNameMaxLength + 2) + "s%3$s", "Table",
|
||||
"Item", "Status"));
|
||||
console.println("-".repeat(tableNameMaxLength) + " " + "--------- " + "-".repeat(itemNameMaxLength) + " "
|
||||
+ "-".repeat(statusMaxLength));
|
||||
for (ItemTableCheckEntry entry : entries) {
|
||||
String tableName = entry.getTableName();
|
||||
ItemTableCheckEntryStatus status = entry.getStatus();
|
||||
long rowCount = status == ItemTableCheckEntryStatus.VALID
|
||||
|| status == ItemTableCheckEntryStatus.ITEM_MISSING ? persistenceService.getRowCount(tableName) : 0;
|
||||
console.println(String.format(
|
||||
"%1$-" + (tableNameMaxLength + 2) + "s%2$9d %3$-" + (itemNameMaxLength + 2) + "s%4$s", tableName,
|
||||
rowCount, entry.getItemName(), status));
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupTables(JdbcPersistenceService persistenceService, Console console) {
|
||||
console.println("Cleaning up all inconsistent items...");
|
||||
List<ItemTableCheckEntry> entries = persistenceService.getCheckedEntries();
|
||||
entries.removeIf(t -> t.getStatus() == ItemTableCheckEntryStatus.VALID || t.getItemName().isEmpty());
|
||||
for (ItemTableCheckEntry entry : entries) {
|
||||
console.print(entry.getItemName() + " -> ");
|
||||
if (persistenceService.cleanupItem(entry)) {
|
||||
console.println("done.");
|
||||
} else {
|
||||
console.println("skipped/failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupItem(JdbcPersistenceService persistenceService, Console console, String itemName,
|
||||
boolean force) {
|
||||
console.print("Cleaning up item " + itemName + "... ");
|
||||
if (persistenceService.cleanupItem(itemName, force)) {
|
||||
console.println("done.");
|
||||
} else {
|
||||
console.println("skipped/failed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getUsages() {
|
||||
return Arrays.asList(
|
||||
buildCommandUsage(CMD_TABLES + " " + SUBCMD_TABLES_LIST + " [" + PARAMETER_ALL + "]",
|
||||
"list tables (all = include valid)"),
|
||||
buildCommandUsage(
|
||||
CMD_TABLES + " " + SUBCMD_TABLES_CLEAN + " [<itemName>]" + " [" + PARAMETER_FORCE + "]",
|
||||
"clean inconsistent items (remove from index and drop tables)"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConsoleCommandCompleter getCompleter() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
|
||||
if (cursorArgumentIndex <= 0) {
|
||||
return CMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
} else if (cursorArgumentIndex == 1) {
|
||||
if (CMD_TABLES.equalsIgnoreCase(args[0])) {
|
||||
return SUBCMD_TABLES_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
}
|
||||
} else if (cursorArgumentIndex == 2) {
|
||||
if (CMD_TABLES.equalsIgnoreCase(args[0])) {
|
||||
if (SUBCMD_TABLES_CLEAN.equalsIgnoreCase(args[1])) {
|
||||
JdbcPersistenceService persistenceService = getPersistenceService();
|
||||
if (persistenceService != null) {
|
||||
return new StringsCompleter(persistenceService.getItemNames(), true).complete(args,
|
||||
cursorArgumentIndex, cursorPosition, candidates);
|
||||
}
|
||||
} else if (SUBCMD_TABLES_LIST.equalsIgnoreCase(args[1])) {
|
||||
new StringsCompleter(List.of(PARAMETER_ALL), false).complete(args, cursorArgumentIndex,
|
||||
cursorPosition, candidates);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -85,11 +85,13 @@ public class JdbcBaseDAO {
|
|||
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 sqlDropTable = "DROP TABLE #tableName#";
|
||||
protected String sqlDeleteItemsEntry = "DELETE FROM #itemsManageTable# WHERE ItemName='#itemname#'";
|
||||
protected String sqlGetItemIDTableNames = "SELECT ItemId, ItemName FROM #itemsManageTable#";
|
||||
protected String sqlGetItemTables = "SELECT table_name FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_schema='#jdbcUriDatabaseName#' AND NOT table_name='#itemsManageTable#'";
|
||||
protected String sqlCreateItemTable = "CREATE TABLE IF NOT EXISTS #tableName# (time #tablePrimaryKey# NOT NULL, value #dbType#, PRIMARY KEY(time))";
|
||||
protected String sqlInsertItemValue = "INSERT INTO #tableName# (TIME, VALUE) VALUES( #tablePrimaryValue#, ? ) ON DUPLICATE KEY UPDATE VALUE= ?";
|
||||
protected String sqlGetRowCount = "SELECT COUNT(*) FROM #tableName#";
|
||||
|
||||
/********
|
||||
* INIT *
|
||||
|
@ -264,6 +266,14 @@ public class JdbcBaseDAO {
|
|||
return Objects.nonNull(result);
|
||||
}
|
||||
|
||||
public boolean doIfTableExists(String tableName) {
|
||||
String sql = StringUtilsExt.replaceArrayMerge(sqlIfTableExists, new String[] { "#searchTable#" },
|
||||
new String[] { tableName });
|
||||
logger.debug("JDBC::doIfTableExists sql={}", sql);
|
||||
final @Nullable String result = Yank.queryScalar(sql, String.class, null);
|
||||
return Objects.nonNull(result);
|
||||
}
|
||||
|
||||
public Long doCreateNewEntryInItemsTable(ItemsVO vo) {
|
||||
String sql = StringUtilsExt.replaceArrayMerge(sqlCreateNewEntryInItemsTable,
|
||||
new String[] { "#itemsManageTable#", "#itemname#" },
|
||||
|
@ -289,9 +299,17 @@ public class JdbcBaseDAO {
|
|||
return vo;
|
||||
}
|
||||
|
||||
public void doDropTable(String tableName) {
|
||||
String sql = StringUtilsExt.replaceArrayMerge(sqlDropTable, new String[] { "#tableName#" },
|
||||
new String[] { tableName });
|
||||
logger.debug("JDBC::doDropTable sql={}", sql);
|
||||
Yank.execute(sql, null);
|
||||
}
|
||||
|
||||
public void doDeleteItemsEntry(ItemsVO vo) {
|
||||
String sql = StringUtilsExt.replaceArrayMerge(sqlDeleteItemsEntry, new String[] { "#itemname#" },
|
||||
new String[] { vo.getItemName() });
|
||||
String sql = StringUtilsExt.replaceArrayMerge(sqlDeleteItemsEntry,
|
||||
new String[] { "#itemsManageTable#", "#itemname#" },
|
||||
new String[] { vo.getItemsManageTable(), vo.getItemName() });
|
||||
logger.debug("JDBC::doDeleteItemsEntry sql={}", sql);
|
||||
Yank.execute(sql, null);
|
||||
}
|
||||
|
@ -373,6 +391,14 @@ public class JdbcBaseDAO {
|
|||
Yank.execute(sql, null);
|
||||
}
|
||||
|
||||
public long doGetRowCount(String tableName) {
|
||||
final String sql = StringUtilsExt.replaceArrayMerge(sqlGetRowCount, new String[] { "#tableName#" },
|
||||
new String[] { tableName });
|
||||
logger.debug("JDBC::doGetRowCount sql={}", sql);
|
||||
final @Nullable Long result = Yank.queryScalar(sql, Long.class, null);
|
||||
return Objects.requireNonNullElse(result, 0L);
|
||||
}
|
||||
|
||||
/*************
|
||||
* Providers *
|
||||
*************/
|
||||
|
|
|
@ -106,6 +106,14 @@ public class JdbcMapper {
|
|||
return res;
|
||||
}
|
||||
|
||||
public boolean ifTableExists(String tableName) {
|
||||
logger.debug("JDBC::ifTableExists");
|
||||
long timerStart = System.currentTimeMillis();
|
||||
boolean res = conf.getDBDAO().doIfTableExists(tableName);
|
||||
logTime("doIfTableExists", timerStart, System.currentTimeMillis());
|
||||
return res;
|
||||
}
|
||||
|
||||
public ItemsVO createNewEntryInItemsTable(ItemsVO vo) {
|
||||
logger.debug("JDBC::createNewEntryInItemsTable");
|
||||
long timerStart = System.currentTimeMillis();
|
||||
|
@ -131,6 +139,13 @@ public class JdbcMapper {
|
|||
return true;
|
||||
}
|
||||
|
||||
public void dropTable(String tableName) {
|
||||
logger.debug("JDBC::dropTable");
|
||||
long timerStart = System.currentTimeMillis();
|
||||
conf.getDBDAO().doDropTable(tableName);
|
||||
logTime("doDropTable", timerStart, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public ItemsVO deleteItemsEntry(ItemsVO vo) {
|
||||
logger.debug("JDBC::deleteItemsEntry");
|
||||
long timerStart = System.currentTimeMillis();
|
||||
|
@ -189,6 +204,10 @@ public class JdbcMapper {
|
|||
return item;
|
||||
}
|
||||
|
||||
public long getRowCount(String tableName) {
|
||||
return conf.getDBDAO().doGetRowCount(tableName);
|
||||
}
|
||||
|
||||
public List<HistoricItem> getHistItemFilterQuery(FilterCriteria filter, int numberDecimalcount, String table,
|
||||
Item item) {
|
||||
logger.debug(
|
||||
|
@ -350,7 +369,7 @@ public class JdbcMapper {
|
|||
}
|
||||
|
||||
List<ItemsVO> itemIdTableNames = ifItemsTableExists() ? getItemIDTableNames() : new ArrayList<ItemsVO>();
|
||||
List<String> itemTables = getItemTables().stream().map(t -> t.getTableName()).collect(Collectors.toList());
|
||||
var itemTables = getItemTables().stream().map(ItemsVO::getTableName).collect(Collectors.toList());
|
||||
List<ItemVO> oldNewTableNames;
|
||||
|
||||
if (itemIdTableNames.isEmpty()) {
|
||||
|
|
|
@ -13,11 +13,15 @@
|
|||
package org.openhab.persistence.jdbc.internal;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
@ -36,6 +40,9 @@ import org.openhab.core.persistence.QueryablePersistenceService;
|
|||
import org.openhab.core.persistence.strategy.PersistenceStrategy;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.openhab.persistence.jdbc.ItemTableCheckEntry;
|
||||
import org.openhab.persistence.jdbc.ItemTableCheckEntryStatus;
|
||||
import org.openhab.persistence.jdbc.dto.ItemsVO;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.Constants;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
|
@ -55,13 +62,9 @@ import org.slf4j.LoggerFactory;
|
|||
@Component(service = { PersistenceService.class,
|
||||
QueryablePersistenceService.class }, configurationPid = "org.openhab.jdbc", //
|
||||
property = Constants.SERVICE_PID + "=org.openhab.jdbc")
|
||||
@ConfigurableService(category = "persistence", label = "JDBC Persistence Service", description_uri = JdbcPersistenceService.CONFIG_URI)
|
||||
@ConfigurableService(category = "persistence", label = "JDBC Persistence Service", description_uri = JdbcPersistenceServiceConstants.CONFIG_URI)
|
||||
public class JdbcPersistenceService extends JdbcMapper implements ModifiablePersistenceService {
|
||||
|
||||
private static final String SERVICE_ID = "jdbc";
|
||||
private static final String SERVICE_LABEL = "JDBC";
|
||||
protected static final String CONFIG_URI = "persistence:jdbc";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(JdbcPersistenceService.class);
|
||||
|
||||
private final ItemRegistry itemRegistry;
|
||||
|
@ -116,12 +119,12 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
|
|||
@Override
|
||||
public String getId() {
|
||||
logger.debug("JDBC::getName: returning name 'jdbc' for queryable persistence service.");
|
||||
return SERVICE_ID;
|
||||
return JdbcPersistenceServiceConstants.SERVICE_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel(@Nullable Locale locale) {
|
||||
return SERVICE_LABEL;
|
||||
return JdbcPersistenceServiceConstants.SERVICE_LABEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -275,4 +278,122 @@ public class JdbcPersistenceService extends JdbcMapper implements ModifiablePers
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of names of persisted items.
|
||||
*/
|
||||
public Collection<String> getItemNames() {
|
||||
return itemNameToTableNameMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all items with corresponding tables and an {@link ItemTableCheckEntryStatus} indicating
|
||||
* its condition.
|
||||
*
|
||||
* @return list of {@link ItemTableCheckEntry}
|
||||
*/
|
||||
public List<ItemTableCheckEntry> getCheckedEntries() {
|
||||
List<ItemTableCheckEntry> entries = new ArrayList<>();
|
||||
|
||||
if (!checkDBAccessability()) {
|
||||
logger.warn("JDBC::getCheckedEntries: database not connected");
|
||||
return entries;
|
||||
}
|
||||
|
||||
var orphanTables = getItemTables().stream().map(ItemsVO::getTableName).collect(Collectors.toSet());
|
||||
for (Entry<String, String> entry : itemNameToTableNameMap.entrySet()) {
|
||||
String itemName = entry.getKey();
|
||||
String tableName = entry.getValue();
|
||||
entries.add(getCheckedEntry(itemName, tableName, orphanTables.contains(tableName)));
|
||||
orphanTables.remove(tableName);
|
||||
}
|
||||
for (String orphanTable : orphanTables) {
|
||||
entries.add(new ItemTableCheckEntry("", orphanTable, ItemTableCheckEntryStatus.ORPHAN_TABLE));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
private ItemTableCheckEntry getCheckedEntry(String itemName, String tableName, boolean tableExists) {
|
||||
boolean itemExists;
|
||||
try {
|
||||
itemRegistry.getItem(itemName);
|
||||
itemExists = true;
|
||||
} catch (ItemNotFoundException e) {
|
||||
itemExists = false;
|
||||
}
|
||||
|
||||
ItemTableCheckEntryStatus status;
|
||||
if (!tableExists) {
|
||||
if (itemExists) {
|
||||
status = ItemTableCheckEntryStatus.TABLE_MISSING;
|
||||
} else {
|
||||
status = ItemTableCheckEntryStatus.ITEM_AND_TABLE_MISSING;
|
||||
}
|
||||
} else if (itemExists) {
|
||||
status = ItemTableCheckEntryStatus.VALID;
|
||||
} else {
|
||||
status = ItemTableCheckEntryStatus.ITEM_MISSING;
|
||||
}
|
||||
return new ItemTableCheckEntry(itemName, tableName, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up inconsistent item: Remove from index and drop table.
|
||||
* Tables with any rows are skipped, unless force is set.
|
||||
*
|
||||
* @param itemName Name of item to clean
|
||||
* @param force If true, non-empty tables will be dropped too
|
||||
* @return true if item was cleaned up
|
||||
*/
|
||||
public boolean cleanupItem(String itemName, boolean force) {
|
||||
String tableName = itemNameToTableNameMap.get(itemName);
|
||||
if (tableName == null) {
|
||||
return false;
|
||||
}
|
||||
ItemTableCheckEntry entry = getCheckedEntry(itemName, tableName, ifTableExists(tableName));
|
||||
return cleanupItem(entry, force);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up inconsistent item: Remove from index and drop table.
|
||||
* Tables with any rows are skipped.
|
||||
*
|
||||
* @param entry
|
||||
* @return true if item was cleaned up
|
||||
*/
|
||||
public boolean cleanupItem(ItemTableCheckEntry entry) {
|
||||
return cleanupItem(entry, false);
|
||||
}
|
||||
|
||||
private boolean cleanupItem(ItemTableCheckEntry entry, boolean force) {
|
||||
if (!checkDBAccessability()) {
|
||||
logger.warn("JDBC::cleanupItem: database not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemTableCheckEntryStatus status = entry.getStatus();
|
||||
String tableName = entry.getTableName();
|
||||
switch (status) {
|
||||
case ITEM_MISSING:
|
||||
if (!force && getRowCount(tableName) > 0) {
|
||||
return false;
|
||||
}
|
||||
dropTable(tableName);
|
||||
// Fall through to remove from index.
|
||||
case TABLE_MISSING:
|
||||
case ITEM_AND_TABLE_MISSING:
|
||||
if (!conf.getTableUseRealCaseSensitiveItemNames()) {
|
||||
ItemsVO itemsVo = new ItemsVO();
|
||||
itemsVo.setItemName(entry.getItemName());
|
||||
deleteItemsEntry(itemsVo);
|
||||
}
|
||||
itemNameToTableNameMap.remove(entry.getItemName());
|
||||
return true;
|
||||
case ORPHAN_TABLE:
|
||||
case VALID:
|
||||
default:
|
||||
// Nothing to clean.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.persistence.jdbc.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link JdbcPersistenceServiceConstants} class defines common constants, which are
|
||||
* used across the whole persistence service.
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JdbcPersistenceServiceConstants {
|
||||
|
||||
public static final String SERVICE_ID = "jdbc";
|
||||
public static final String SERVICE_LABEL = "JDBC";
|
||||
public static final String CONFIG_URI = "persistence:jdbc";
|
||||
}
|
Loading…
Reference in New Issue