- [Database Table Schema](#database-table-schema)
- [Number Precision](#number-precision)
- [Rounding results](#rounding-results)
+ - [Maintenance](#maintenance)
- [For Developers](#for-developers)
- [Performance Tests](#performance-tests)
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.
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
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 *
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#" },
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);
}
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 *
*************/
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();
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();
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(
}
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()) {
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;
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;
@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;
@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
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;
+ }
+ }
}
--- /dev/null
+/**
+ * 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";
+}