Fix deleting rows from HSQLDB repository & improve transaction tests

This commit is contained in:
catbref 2018-06-29 11:05:15 +01:00
parent fe6cb4e366
commit 70d25f24ce
8 changed files with 123 additions and 20 deletions

View File

@ -1,5 +1,7 @@
package repository;
import java.util.List;
import data.voting.PollData;
import data.voting.VoteOnPollData;
@ -17,6 +19,8 @@ public interface VotingRepository {
// Votes
public List<VoteOnPollData> getVotes(String pollName) throws DataException;
public VoteOnPollData getVote(String pollName, byte[] voterPublicKey) throws DataException;
public void save(VoteOnPollData voteOnPollData) throws DataException;

View File

@ -68,7 +68,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
public void delete(String address, long assetId) throws DataException {
try {
this.repository.checkedExecute("DELETE FROM AccountBalances WHERE account = ? and asset_id = ?", address, assetId);
this.repository.delete("AccountBalances", "account = ? and asset_id = ?", address, assetId);
} catch (SQLException e) {
throw new DataException("Unable to delete account balance from repository", e);
}

View File

@ -73,7 +73,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
public void delete(long assetId) throws DataException {
try {
this.repository.checkedExecute("DELETE FROM Assets WHERE assetId = ?", assetId);
this.repository.delete("Assets", "assetId = ?", assetId);
} catch (SQLException e) {
throw new DataException("Unable to delete asset from repository", e);
}
@ -119,7 +119,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
public void delete(byte[] orderId) throws DataException {
try {
this.repository.checkedExecute("DELETE FROM AssetOrders WHERE orderId = ?", orderId);
this.repository.delete("AssetOrders", "orderId = ?", orderId);
} catch (SQLException e) {
throw new DataException("Unable to delete asset order from repository", e);
}

View File

@ -146,7 +146,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
public void delete(BlockData blockData) throws DataException {
try {
this.repository.checkedExecute("DELETE FROM Blocks WHERE signature = ?", blockData.getSignature());
this.repository.delete("Blocks", "signature = ?", blockData.getSignature());
} catch (SQLException e) {
throw new DataException("Unable to delete Block from repository", e);
}
@ -166,7 +166,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
public void delete(BlockTransactionData blockTransactionData) throws DataException {
try {
this.repository.checkedExecute("DELETE FROM BlockTransactions WHERE block_signature = ? AND sequence = ? AND transaction_signature = ?",
this.repository.delete("BlockTransactions", "block_signature = ? AND sequence = ? AND transaction_signature = ?",
blockTransactionData.getBlockSignature(), blockTransactionData.getSequence(), blockTransactionData.getTransactionSignature());
} catch (SQLException e) {
throw new DataException("Unable to delete BlockTransaction from repository", e);

View File

@ -112,7 +112,26 @@ public class HSQLDBRepository implements Repository {
public ResultSet checkedExecute(String sql, Object... objects) throws SQLException {
PreparedStatement preparedStatement = this.connection.prepareStatement(sql);
return this.checkedExecute(preparedStatement, objects);
return this.checkedExecuteResultSet(preparedStatement, objects);
}
/**
* Bind objects to placeholders in prepared statement.
* <p>
* Special treatment for BigDecimals so that they retain their "scale".
*
* @param preparedStatement
* @param objects
* @throws SQLException
*/
private void prepareExecute(PreparedStatement preparedStatement, Object... objects) throws SQLException {
for (int i = 0; i < objects.length; ++i)
// Special treatment for BigDecimals so that they retain their "scale",
// which would otherwise be assumed as 0.
if (objects[i] instanceof BigDecimal)
preparedStatement.setBigDecimal(i + 1, (BigDecimal) objects[i]);
else
preparedStatement.setObject(i + 1, objects[i]);
}
/**
@ -125,14 +144,8 @@ public class HSQLDBRepository implements Repository {
* @return ResultSet, or null if there are no found rows
* @throws SQLException
*/
public ResultSet checkedExecute(PreparedStatement preparedStatement, Object... objects) throws SQLException {
for (int i = 0; i < objects.length; ++i)
// Special treatment for BigDecimals so that they retain their "scale",
// which would otherwise be assumed as 0.
if (objects[i] instanceof BigDecimal)
preparedStatement.setBigDecimal(i + 1, (BigDecimal) objects[i]);
else
preparedStatement.setObject(i + 1, objects[i]);
private ResultSet checkedExecuteResultSet(PreparedStatement preparedStatement, Object... objects) throws SQLException {
prepareExecute(preparedStatement, objects);
if (!preparedStatement.execute())
throw new SQLException("Fetching from database produced no results");
@ -147,6 +160,27 @@ public class HSQLDBRepository implements Repository {
return resultSet;
}
/**
* Execute PreparedStatement and return changed row count.
*
* @param preparedStatement
* @param objects
* @return number of changed rows
* @throws SQLException
*/
private int checkedExecuteUpdateCount(PreparedStatement preparedStatement, Object... objects) throws SQLException {
prepareExecute(preparedStatement, objects);
if (preparedStatement.execute())
throw new SQLException("Database produced results, not row count");
int rowCount = preparedStatement.getUpdateCount();
if (rowCount == -1)
throw new SQLException("Database returned invalid row count");
return rowCount;
}
/**
* Fetch last value of IDENTITY column after an INSERT statement.
* <p>
@ -159,7 +193,7 @@ public class HSQLDBRepository implements Repository {
*/
public Long callIdentity() throws SQLException {
PreparedStatement preparedStatement = this.connection.prepareStatement("CALL IDENTITY()");
ResultSet resultSet = this.checkedExecute(preparedStatement);
ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement);
if (resultSet == null)
return null;
@ -185,11 +219,24 @@ public class HSQLDBRepository implements Repository {
*/
public boolean exists(String tableName, String whereClause, Object... objects) throws SQLException {
PreparedStatement preparedStatement = this.connection.prepareStatement("SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " LIMIT 1");
ResultSet resultSet = this.checkedExecute(preparedStatement, objects);
ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects);
if (resultSet == null)
return false;
return true;
}
/**
* Delete rows from database table.
*
* @param tableName
* @param whereClause
* @param objects
* @throws SQLException
*/
public void delete(String tableName, String whereClause, Object... objects) throws SQLException {
PreparedStatement preparedStatement = this.connection.prepareStatement("DELETE FROM " + tableName + " WHERE " + whereClause);
this.checkedExecuteUpdateCount(preparedStatement, objects);
}
}

View File

@ -20,7 +20,7 @@ public class HSQLDBVotingRepository implements VotingRepository {
this.repository = repository;
}
// Votes
// Polls
public PollData fromPollName(String pollName) throws DataException {
try {
@ -93,7 +93,7 @@ public class HSQLDBVotingRepository implements VotingRepository {
// NOTE: The corresponding rows in PollOptions are deleted automatically by the database thanks to "ON DELETE CASCADE" in the PollOptions' FOREIGN KEY
// definition.
try {
this.repository.checkedExecute("DELETE FROM Polls WHERE poll_name = ?", pollName);
this.repository.delete("Polls", "poll_name = ?", pollName);
} catch (SQLException e) {
throw new DataException("Unable to delete poll from repository", e);
}
@ -101,6 +101,28 @@ public class HSQLDBVotingRepository implements VotingRepository {
// Votes
public List<VoteOnPollData> getVotes(String pollName) throws DataException {
List<VoteOnPollData> votes = new ArrayList<VoteOnPollData>();
try {
ResultSet resultSet = this.repository.checkedExecute("SELECT voter, option_index FROM PollVotes WHERE poll_name = ?", pollName);
if (resultSet == null)
return votes;
// NOTE: do-while because checkedExecute() above has already called rs.next() for us
do {
byte[] voterPublicKey = resultSet.getBytes(1);
int optionIndex = resultSet.getInt(2);
votes.add(new VoteOnPollData(pollName, voterPublicKey, optionIndex));
} while (resultSet.next());
return votes;
} catch (SQLException e) {
throw new DataException("Unable to fetch poll votes from repository", e);
}
}
public VoteOnPollData getVote(String pollName, byte[] voterPublicKey) throws DataException {
try {
ResultSet resultSet = this.repository.checkedExecute("SELECT option_index FROM PollVotes WHERE poll_name = ? AND voter = ?", pollName,
@ -131,7 +153,7 @@ public class HSQLDBVotingRepository implements VotingRepository {
public void delete(String pollName, byte[] voterPublicKey) throws DataException {
try {
this.repository.checkedExecute("DELETE FROM PollVotes WHERE poll_name = ? AND voter = ?", pollName, voterPublicKey);
this.repository.delete("PollVotes", "poll_name = ? AND voter = ?", pollName, voterPublicKey);
} catch (SQLException e) {
throw new DataException("Unable to delete poll vote from repository", e);
}

View File

@ -262,7 +262,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
// NOTE: The corresponding row in sub-table is deleted automatically by the database thanks to "ON DELETE CASCADE" in the sub-table's FOREIGN KEY
// definition.
try {
this.repository.checkedExecute("DELETE FROM Transactions WHERE signature = ?", transactionData.getSignature());
this.repository.delete("Transactions", "signature = ?", transactionData.getSignature());
} catch (SQLException e) {
throw new DataException("Unable to delete transaction from repository", e);
}

View File

@ -20,6 +20,7 @@ import data.transaction.PaymentTransactionData;
import data.transaction.VoteOnPollTransactionData;
import data.voting.PollData;
import data.voting.PollOptionData;
import data.voting.VoteOnPollData;
import qora.account.Account;
import qora.account.PrivateKeyAccount;
import qora.account.PublicKeyAccount;
@ -245,11 +246,40 @@ public class TransactionTests {
block.process();
repository.saveChanges();
// Check vote was registered properly
VoteOnPollData actualVoteOnPollData = repository.getVotingRepository().getVote(pollName, sender.getPublicKey());
assertNotNull(actualVoteOnPollData);
assertEquals(optionIndex, actualVoteOnPollData.getOptionIndex());
// update variables for next round
previousBlockData = block.getBlockData();
timestamp += 1_000;
reference = voteOnPollTransaction.getTransactionData().getSignature();
}
// Check poll's votes
List<VoteOnPollData> votes = repository.getVotingRepository().getVotes(pollName);
assertNotNull(votes);
assertEquals("Only one vote expected", 1, votes.size());
assertEquals("Wrong vote option index", pollOptionsSize - 1, votes.get(0).getOptionIndex());
assertTrue("Wrong voter public key", Arrays.equals(sender.getPublicKey(), votes.get(0).getVoterPublicKey()));
// Orphan last block
BlockData lastBlockData = repository.getBlockRepository().getLastBlock();
Block lastBlock = new Block(repository, lastBlockData);
lastBlock.orphan();
repository.saveChanges();
// Recheck poll's votes
votes = repository.getVotingRepository().getVotes(pollName);
assertNotNull(votes);
assertEquals("Only one vote expected", 1, votes.size());
assertEquals("Wrong vote option index", pollOptionsSize - 1 - 1, votes.get(0).getOptionIndex());
assertTrue("Wrong voter public key", Arrays.equals(sender.getPublicKey(), votes.get(0).getVoterPublicKey()));
}
}