/******************************************************************************
* Copyright © 2013-2016 The Nxt Core Developers. *
* *
* See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at *
* the top-level directory of this distribution for the individual copyright *
* holder information and the developer policies on copyright and licensing. *
* *
* Unless otherwise agreed in a custom licensing agreement, no part of the *
* Nxt software, including this file, may be copied, modified, propagated, *
* or distributed except according to the terms contained in the LICENSE.txt *
* file. *
* *
* Removal or modification of this copyright notice is prohibited. *
* *
******************************************************************************/
package nxt.db;
import nxt.Nxt;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public abstract class VersionedEntityDbTable<T> extends EntityDbTable<T> {
protected VersionedEntityDbTable(String table, DbKey.Factory<T> dbKeyFactory) {
super(table, dbKeyFactory, true, null);
}
protected VersionedEntityDbTable(String table, DbKey.Factory<T> dbKeyFactory, String fullTextSearchColumns) {
super(table, dbKeyFactory, true, fullTextSearchColumns);
}
public final boolean delete(T t) {
return delete(t, false);
}
public final boolean delete(T t, boolean keepInCache) {
if (t == null) {
return false;
}
if (!db.isInTransaction()) {
throw new IllegalStateException("Not in transaction");
}
DbKey dbKey = dbKeyFactory.newKey(t);
try (Connection con = db.getConnection();
PreparedStatement pstmtCount = con.prepareStatement("SELECT COUNT(*) AS count FROM " + table + dbKeyFactory.getPKClause()
+ " AND height < ?")) {
int i = dbKey.setPK(pstmtCount);
pstmtCount.setInt(i, Nxt.getBlockchain().getHeight());
try (ResultSet rs = pstmtCount.executeQuery()) {
rs.next();
if (rs.getInt("count") > 0) {
try (PreparedStatement pstmt = con.prepareStatement("UPDATE " + table
+ " SET latest = FALSE " + dbKeyFactory.getPKClause() + " AND latest = TRUE LIMIT 1")) {
dbKey.setPK(pstmt);
pstmt.executeUpdate();
save(con, t);
pstmt.executeUpdate(); // delete after the save
}
return true;
} else {
try (PreparedStatement pstmtDelete = con.prepareStatement("DELETE FROM " + table + dbKeyFactory.getPKClause())) {
dbKey.setPK(pstmtDelete);
return pstmtDelete.executeUpdate() > 0;
}
}
}
} catch (SQLException e) {
throw new RuntimeException(e.toString(), e);
} finally {
if (!keepInCache) {
db.getCache(table).remove(dbKey);
}
}
}
static void rollback(final TransactionalDb db, final String table, final int height, final DbKey.Factory dbKeyFactory) {
if (!db.isInTransaction()) {
throw new IllegalStateException("Not in transaction");
}
try (Connection con = db.getConnection();
PreparedStatement pstmtSelectToDelete = con.prepareStatement("SELECT DISTINCT " + dbKeyFactory.getPKColumns()
+ " FROM " + table + " WHERE height > ?");
PreparedStatement pstmtDelete = con.prepareStatement("DELETE FROM " + table
+ " WHERE height > ?");
PreparedStatement pstmtSetLatest = con.prepareStatement("UPDATE " + table
+ " SET latest = TRUE " + dbKeyFactory.getPKClause() + " AND height ="
+ " (SELECT MAX(height) FROM " + table + dbKeyFactory.getPKClause() + ")")) {
pstmtSelectToDelete.setInt(1, height);
List<DbKey> dbKeys = new ArrayList<>();
try (ResultSet rs = pstmtSelectToDelete.executeQuery()) {
while (rs.next()) {
dbKeys.add(dbKeyFactory.newKey(rs));
}
}
/*
if (dbKeys.size() > 0 && Logger.isDebugEnabled()) {
Logger.logDebugMessage(String.format("rollback table %s found %d records to update to latest", table, dbKeys.size()));
}
*/
pstmtDelete.setInt(1, height);
int deletedRecordsCount = pstmtDelete.executeUpdate();
/*
if (deletedRecordsCount > 0 && Logger.isDebugEnabled()) {
Logger.logDebugMessage(String.format("rollback table %s deleting %d records", table, deletedRecordsCount));
}
*/
for (DbKey dbKey : dbKeys) {
int i = 1;
i = dbKey.setPK(pstmtSetLatest, i);
i = dbKey.setPK(pstmtSetLatest, i);
pstmtSetLatest.executeUpdate();
//Db.getCache(table).remove(dbKey);
}
} catch (SQLException e) {
throw new RuntimeException(e.toString(), e);
}
}
static void trim(final TransactionalDb db, final String table, final int height, final DbKey.Factory dbKeyFactory) {
if (!db.isInTransaction()) {
throw new IllegalStateException("Not in transaction");
}
try (Connection con = db.getConnection();
PreparedStatement pstmtSelect = con.prepareStatement("SELECT " + dbKeyFactory.getPKColumns() + ", MAX(height) AS max_height"
+ " FROM " + table + " WHERE height < ? GROUP BY " + dbKeyFactory.getPKColumns() + " HAVING COUNT(DISTINCT height) > 1");
PreparedStatement pstmtDelete = con.prepareStatement("DELETE FROM " + table + dbKeyFactory.getPKClause()
+ " AND height < ? AND height >= 0");
PreparedStatement pstmtDeleteDeleted = con.prepareStatement("DELETE FROM " + table + " WHERE height < ? AND height >= 0 AND latest = FALSE "
+ " AND (" + dbKeyFactory.getPKColumns() + ") NOT IN (SELECT (" + dbKeyFactory.getPKColumns() + ") FROM "
+ table + " WHERE height >= ?)")) {
pstmtSelect.setInt(1, height);
try (ResultSet rs = pstmtSelect.executeQuery()) {
while (rs.next()) {
DbKey dbKey = dbKeyFactory.newKey(rs);
int maxHeight = rs.getInt("max_height");
int i = 1;
i = dbKey.setPK(pstmtDelete, i);
pstmtDelete.setInt(i, maxHeight);
pstmtDelete.executeUpdate();
}
pstmtDeleteDeleted.setInt(1, height);
pstmtDeleteDeleted.setInt(2, height);
pstmtDeleteDeleted.executeUpdate();
}
} catch (SQLException e) {
throw new RuntimeException(e.toString(), e);
}
}
}