More work on repository

No need for database.DB class as the code is specific to HSQLDB so moved into relevant repository.hsqldb classes.

Top-level Repository instance (e.g. HSQLDBRepository) is used within subclasses, e.g. HSQLDBBlockRepository, so they
can share the same repository state, like underlying SQL Connection for easier transactional support.

HSQLDBRepository subclasses now call checkedExecute() on top-level repository instance, instead of passing Connection
to obsolete DB.checkedExecute.

No need for qora.block.BlockFactory any more as those methods are now in repository.

More work on Blocks and Transactions in general.
This commit is contained in:
catbref 2018-06-12 10:21:03 +01:00
parent 6a2a172ee8
commit d45c33fe90
20 changed files with 375 additions and 633 deletions

View File

@ -0,0 +1,32 @@
package data.block;
public class BlockTransactionData {
// Properties
protected byte[] blockSignature;
protected int sequence;
protected byte[] transactionSignature;
// Constructors
public BlockTransactionData(byte[] blockSignature, int sequence, byte[] transactionSignature) {
this.blockSignature = blockSignature;
this.sequence = sequence;
this.transactionSignature = transactionSignature;
}
// Getters/setters
public byte[] getBlockSignature() {
return this.blockSignature;
}
public int getSequence() {
return this.sequence;
}
public byte[] getTransactionSignature() {
return this.transactionSignature;
}
}

View File

@ -1,273 +0,0 @@
package database;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;
import org.hsqldb.jdbc.JDBCPool;
/**
* Helper methods for common database actions.
*
*/
public abstract class DB {
private static JDBCPool connectionPool;
private static String connectionUrl;
private static ThreadLocal<Connection> local = new ThreadLocal<Connection>() {
@Override
protected Connection initialValue() {
Connection conn = null;
try {
conn = connectionPool.getConnection();
} catch (SQLException e) {
}
return conn;
}
};
/**
* Open connection pool to database using prior set connection URL.
* <p>
* The connection URL <b>must</b> be set via {@link DB#setUrl(String)} before using this call.
*
* @throws SQLException
* @see DB#setUrl(String)
*/
public static void open() throws SQLException {
connectionPool = new JDBCPool();
connectionPool.setUrl(connectionUrl);
}
/**
* Set the database connection URL.
* <p>
* Typical example:
* <p>
* {@code setUrl("jdbc:hsqldb:file:db/qora")}
*
* @param url
*/
public static void setUrl(String url) {
connectionUrl = url;
}
/**
* Return thread-local Connection from connection pool.
* <p>
* By default HSQLDB will wait up to 30 seconds for a pooled connection to become free.
*
* @return Connection
*/
public static Connection getConnection() {
return local.get();
}
public static Connection getPoolConnection() throws SQLException {
return connectionPool.getConnection();
}
public static void releaseConnection() {
Connection connection = local.get();
if (connection != null)
try {
connection.close();
} catch (SQLException e) {
}
local.remove();
}
public static void startTransaction() throws SQLException {
Connection connection = DB.getConnection();
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
connection.setAutoCommit(false);
}
public static void commit() throws SQLException {
Connection connection = DB.getConnection();
connection.commit();
connection.setAutoCommit(true);
}
public static void rollback() throws SQLException {
Connection connection = DB.getConnection();
connection.rollback();
connection.setAutoCommit(true);
}
public static Savepoint createSavepoint(String savepointName) throws SQLException {
return DB.getConnection().setSavepoint(savepointName);
}
public static void rollbackToSavepoint(Savepoint savepoint) throws SQLException {
DB.getConnection().rollback(savepoint);
}
/**
* Shutdown database and close all connections in connection pool.
* <p>
* Note: any attempts to use an existing connection after this point will fail. Also, any attempts to request a connection using {@link DB#getConnection()}
* will fail.
* <p>
* After this method returns, the database <i>can</i> be reopened using {@link DB#open()}.
*
* @throws SQLException
*/
public static void shutdown() throws SQLException {
DB.getConnection().createStatement().execute("SHUTDOWN");
DB.releaseConnection();
connectionPool.close(0);
}
/**
* Shutdown and delete database, then rebuild it.
* <p>
* See {@link DB#shutdown()} for warnings about connections.
* <p>
* Note that this only rebuilds the database schema, not the data itself.
*
* @throws SQLException
*/
public static void rebuild() throws SQLException {
// Shutdown database and close any access
DB.shutdown();
// Wipe files (if any)
// TODO
// Re-open clean database
DB.open();
// Apply schema updates
DatabaseUpdates.updateDatabase();
}
/**
* Convert InputStream, from ResultSet.getBinaryStream(), into byte[].
*
* @param inputStream
* @return byte[]
*/
public static byte[] getResultSetBytes(InputStream inputStream) {
// inputStream could be null if database's column's value is null
if (inputStream == null)
return null;
try {
int length = inputStream.available();
byte[] result = new byte[length];
if (inputStream.read(result) == length)
return result;
} catch (IOException e) {
// Fall-through to return null
}
return null;
}
/**
* Execute SQL and return ResultSet with but added checking.
* <p>
* <b>Note: calls ResultSet.next()</b> therefore returned ResultSet is already pointing to first row.
*
* @param sql
* @param objects
* @return ResultSet, or null if there are no found rows
* @throws SQLException
*/
public static ResultSet checkedExecute(String sql, Object... objects) throws SQLException {
Connection connection = DB.getConnection();
return checkedExecute(connection, sql, objects);
}
public static ResultSet checkedExecute(Connection connection, String sql, Object... objects) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement(sql);
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]);
return DB.checkedExecute(preparedStatement);
}
/**
* Execute PreparedStatement and return ResultSet with but added checking.
* <p>
* <b>Note: calls ResultSet.next()</b> therefore returned ResultSet is already pointing to first row.
*
* @param preparedStatement
* @return ResultSet, or null if there are no found rows
* @throws SQLException
*/
public static ResultSet checkedExecute(PreparedStatement preparedStatement) throws SQLException {
if (!preparedStatement.execute())
throw new SQLException("Fetching from database produced no results");
ResultSet resultSet = preparedStatement.getResultSet();
if (resultSet == null)
throw new SQLException("Fetching results from database produced no ResultSet");
if (!resultSet.next())
return null;
return resultSet;
}
/**
* Fetch last value of IDENTITY column after an INSERT statement.
* <p>
* Performs "CALL IDENTITY()" SQL statement to retrieve last value used when INSERTing into a table that has an IDENTITY column.
* <p>
* Typically used after INSERTing NULL as the IDENTIY column's value to fetch what value was actually stored by HSQLDB.
*
* @return Long
* @throws SQLException
*/
public static Long callIdentity() throws SQLException {
PreparedStatement preparedStatement = DB.getConnection().prepareStatement("CALL IDENTITY()");
ResultSet resultSet = DB.checkedExecute(preparedStatement);
if (resultSet == null)
return null;
return resultSet.getLong(1);
}
/**
* Efficiently query database for existing of matching row.
* <p>
* {@code whereClause} is SQL "WHERE" clause containing "?" placeholders suitable for use with PreparedStatements.
* <p>
* Example call:
* <p>
* {@code String manufacturer = "Lamborghini";}<br>
* {@code int maxMileage = 100_000;}<br>
* {@code boolean isAvailable = DB.exists("Cars", "manufacturer = ? AND mileage <= ?", manufacturer, maxMileage);}
*
* @param tableName
* @param whereClause
* @param objects
* @return true if matching row found in database, false otherwise
* @throws SQLException
*/
public static boolean exists(String tableName, String whereClause, Object... objects) throws SQLException {
PreparedStatement preparedStatement = DB.getConnection()
.prepareStatement("SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " ORDER BY NULL LIMIT 1");
ResultSet resultSet = DB.checkedExecute(preparedStatement);
if (resultSet == null)
return false;
return true;
}
}

View File

@ -1,49 +1,28 @@
package qora.block; package qora.block;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.google.common.hash.HashCode;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import data.block.BlockData; import data.block.BlockData;
import data.block.BlockTransactionData;
import data.transaction.TransactionData; import data.transaction.TransactionData;
import database.DB;
import database.NoDataFoundException;
import qora.account.PrivateKeyAccount; import qora.account.PrivateKeyAccount;
import qora.account.PublicKeyAccount; import qora.account.PublicKeyAccount;
import qora.assets.Asset; import qora.assets.Asset;
import qora.assets.Order;
import qora.assets.Trade;
import qora.transaction.CreateOrderTransaction;
import qora.transaction.GenesisTransaction; import qora.transaction.GenesisTransaction;
import qora.transaction.Transaction; import qora.transaction.Transaction;
import repository.BlockRepository; import repository.BlockRepository;
import repository.DataException; import repository.DataException;
import repository.RepositoryManager; import repository.Repository;
import repository.hsqldb.HSQLDBSaver;
import transform.TransformationException; import transform.TransformationException;
import transform.block.BlockTransformer; import transform.block.BlockTransformer;
import transform.transaction.TransactionTransformer; import transform.transaction.TransactionTransformer;
import utils.Base58;
import utils.NTP; import utils.NTP;
import utils.Serialization;
/* /*
* Typical use-case scenarios: * Typical use-case scenarios:
@ -70,6 +49,7 @@ import utils.Serialization;
public class Block { public class Block {
// Properties // Properties
private Repository repository;
private BlockData blockData; private BlockData blockData;
private PublicKeyAccount generator; private PublicKeyAccount generator;
@ -96,7 +76,8 @@ public class Block {
// Constructors // Constructors
public Block(BlockData blockData) { public Block(Repository repository, BlockData blockData) {
this.repository = repository;
this.blockData = blockData; this.blockData = blockData;
this.generator = new PublicKeyAccount(blockData.getGeneratorPublicKey()); this.generator = new PublicKeyAccount(blockData.getGeneratorPublicKey());
} }
@ -159,7 +140,7 @@ public class Block {
// Navigate back to first block in previous interval: // Navigate back to first block in previous interval:
// XXX: why can't we simply load using block height? // XXX: why can't we simply load using block height?
BlockRepository blockRepo = RepositoryManager.getBlockRepository(); BlockRepository blockRepo = this.repository.getBlockRepository();
BlockData firstBlock = this.blockData; BlockData firstBlock = this.blockData;
try { try {
@ -212,7 +193,7 @@ public class Block {
return this.transactions; return this.transactions;
// Allocate cache for results // Allocate cache for results
List<TransactionData> transactionsData = RepositoryManager.getBlockRepository().getTransactionsFromSignature(this.blockData.getSignature()); List<TransactionData> transactionsData = this.repository.getBlockRepository().getTransactionsFromSignature(this.blockData.getSignature());
// The number of transactions fetched from repository should correspond with Block's transactionCount // The number of transactions fetched from repository should correspond with Block's transactionCount
if (transactionsData.size() != this.blockData.getTransactionCount()) if (transactionsData.size() != this.blockData.getTransactionCount())
@ -348,11 +329,11 @@ public class Block {
if (this.blockData.getReference() == null) if (this.blockData.getReference() == null)
return false; return false;
BlockData parentBlockData = RepositoryManager.getBlockRepository().fromSignature(this.blockData.getReference()); BlockData parentBlockData = this.repository.getBlockRepository().fromSignature(this.blockData.getReference());
if (parentBlockData == null) if (parentBlockData == null)
return false; return false;
Block parentBlock = new Block(parentBlockData); Block parentBlock = new Block(this.repository, parentBlockData);
// Check timestamp is valid, i.e. later than parent timestamp and not in the future, within ~500ms margin // Check timestamp is valid, i.e. later than parent timestamp and not in the future, within ~500ms margin
if (this.blockData.getTimestamp() < parentBlockData.getTimestamp() || this.blockData.getTimestamp() - BLOCK_TIMESTAMP_MARGIN > NTP.getTime()) if (this.blockData.getTimestamp() < parentBlockData.getTimestamp() || this.blockData.getTimestamp() - BLOCK_TIMESTAMP_MARGIN > NTP.getTime())
@ -387,7 +368,6 @@ public class Block {
} }
// Check transactions // Check transactions
Savepoint savepoint = DB.createSavepoint("BLOCK_TRANSACTIONS");
try { try {
for (Transaction transaction : this.getTransactions()) { for (Transaction transaction : this.getTransactions()) {
// GenesisTransactions are not allowed (GenesisBlock overrides isValid() to allow them) // GenesisTransactions are not allowed (GenesisBlock overrides isValid() to allow them)
@ -395,7 +375,8 @@ public class Block {
return false; return false;
// Check timestamp and deadline // Check timestamp and deadline
if (transaction.getTransactionData().getTimestamp() > this.blockData.getTimestamp() || transaction.getDeadline() <= this.blockData.getTimestamp()) if (transaction.getTransactionData().getTimestamp() > this.blockData.getTimestamp()
|| transaction.getDeadline() <= this.blockData.getTimestamp())
return false; return false;
// Check transaction is even valid // Check transaction is even valid
@ -416,11 +397,11 @@ public class Block {
} finally { } finally {
// Revert back to savepoint // Revert back to savepoint
try { try {
DB.rollbackToSavepoint(savepoint); this.repository.discardChanges();
} catch (SQLException e) { } catch (DataException e) {
/* /*
* Rollback failure most likely due to prior SQLException, so catch rollback's SQLException and discard. A "return false" in try-block will * Rollback failure most likely due to prior DataException, so catch rollback's DataException and discard. A "return false" in try-block will
* still return false, prior SQLException propagates to caller and successful completion of try-block continues on after rollback. * still return false, prior DataException propagates to caller and successful completion of try-block continues on after rollback.
*/ */
} }
} }
@ -442,20 +423,20 @@ public class Block {
// Link block into blockchain by fetching signature of highest block and setting that as our reference // Link block into blockchain by fetching signature of highest block and setting that as our reference
int blockchainHeight = BlockChain.getHeight(); int blockchainHeight = BlockChain.getHeight();
BlockData latestBlockData = RepositoryManager.getBlockRepository().fromHeight(blockchainHeight); BlockData latestBlockData = this.repository.getBlockRepository().fromHeight(blockchainHeight);
if (latestBlockData != null) if (latestBlockData != null)
this.blockData.setReference(latestBlockData.getSignature()); this.blockData.setReference(latestBlockData.getSignature());
this.blockData.setHeight(blockchainHeight + 1); this.blockData.setHeight(blockchainHeight + 1);
RepositoryManager.getBlockRepository().save(this.blockData); this.repository.getBlockRepository().save(this.blockData);
// Link transactions to this block, thus removing them from unconfirmed transactions list. // Link transactions to this block, thus removing them from unconfirmed transactions list.
for (int sequence = 0; sequence < transactions.size(); ++sequence) { for (int sequence = 0; sequence < transactions.size(); ++sequence) {
Transaction transaction = transactions.get(sequence); Transaction transaction = transactions.get(sequence);
// Link transaction to this block // Link transaction to this block
BlockTransaction blockTransaction = new BlockTransaction(this.getSignature(), sequence, transaction.getTransactionData().getSignature()); BlockTransactionData blockTransactionData = new BlockTransactionData(this.getSignature(), sequence, transaction.getTransactionData().getSignature());
blockTransaction.save(); this.repository.getBlockTransactionRepository().save(blockTransactionData);
} }
} }

View File

@ -1,77 +0,0 @@
package qora.block;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import data.block.BlockData;
import database.DB;
import database.NoDataFoundException;
import qora.account.PublicKeyAccount;
import qora.transaction.Transaction;
import qora.transaction.TransactionFactory;
import repository.BlockRepository;
import repository.hsqldb.HSQLDBBlockRepository;
public class BlockFactory {
// XXX repository should be pushed here from the root entry, no need to know the repository type
private static BlockRepository repository = new HSQLDBBlockRepository();
/**
* Load Block from DB using block signature.
*
* @param signature
* @return ? extends Block, or null if not found
* @throws SQLException
*/
public static Block fromSignature(byte[] signature) throws SQLException {
Block block = Block.fromSignature(signature);
if (block == null)
return null;
// Can we promote to a GenesisBlock?
if (GenesisBlock.isGenesisBlock(block))
return GenesisBlock.getInstance();
// Standard block
return block;
}
/**
* Load Block from DB using block height
*
* @param height
* @return ? extends Block, or null if not found
* @throws SQLException
*/
public static Block fromHeight(int height) {
if (height == 1)
return GenesisBlock.getInstance();
try {
BlockData data = repository.fromHeight(height);
// TODO fill this list from TransactionFactory
List<Transaction> transactions = new ArrayList<Transaction>();
// TODO fetch account for data.getGeneratorPublicKey()
PublicKeyAccount generator = null;
return new Block(data.getVersion(), data.getReference(), data.getTimestamp(), data.getGeneratingBalance(),
generator,data.getGeneratorSignature(),data.getTransactionsSignature(),
data.getAtBytes(), data.getAtFees(), transactions);
} catch (Exception e) { // XXX move NoDataFoundException to repository domain and use it here?
return null;
}
}
// Navigation
// Converters
// Processing
}

View File

@ -1,126 +1,5 @@
package qora.block; package qora.block;
import java.sql.ResultSet;
import java.sql.SQLException;
import database.DB;
import database.NoDataFoundException;
import qora.transaction.Transaction;
import repository.hsqldb.HSQLDBSaver;
public class BlockTransaction { public class BlockTransaction {
// Database properties shared with all transaction types
protected byte[] blockSignature;
protected int sequence;
protected byte[] transactionSignature;
// Constructors
public BlockTransaction(byte[] blockSignature, int sequence, byte[] transactionSignature) {
this.blockSignature = blockSignature;
this.sequence = sequence;
this.transactionSignature = transactionSignature;
}
// Getters/setters
public byte[] getBlockSignature() {
return this.blockSignature;
}
public int getSequence() {
return this.sequence;
}
public byte[] getTransactionSignature() {
return this.transactionSignature;
}
// More information
// Load/Save
protected BlockTransaction(byte[] blockSignature, int sequence) throws SQLException {
ResultSet rs = DB.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ? and sequence = ?", blockSignature,
sequence);
if (rs == null)
throw new NoDataFoundException();
this.blockSignature = blockSignature;
this.sequence = sequence;
this.transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
}
protected BlockTransaction(byte[] transactionSignature) throws SQLException {
ResultSet rs = DB.checkedExecute("SELECT block_signature, sequence FROM BlockTransactions WHERE transaction_signature = ?", transactionSignature);
if (rs == null)
throw new NoDataFoundException();
this.blockSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
this.sequence = rs.getInt(2);
this.transactionSignature = transactionSignature;
}
/**
* Load BlockTransaction from DB using block signature and tx-in-block sequence.
*
* @param blockSignature
* @param sequence
* @return BlockTransaction, or null if not found
* @throws SQLException
*/
public static BlockTransaction fromBlockSignature(byte[] blockSignature, int sequence) throws SQLException {
try {
return new BlockTransaction(blockSignature, sequence);
} catch (NoDataFoundException e) {
return null;
}
}
/**
* Load BlockTransaction from DB using transaction signature.
*
* @param transactionSignature
* @return BlockTransaction, or null if not found
* @throws SQLException
*/
public static BlockTransaction fromTransactionSignature(byte[] transactionSignature) throws SQLException {
try {
return new BlockTransaction(transactionSignature);
} catch (NoDataFoundException e) {
return null;
}
}
protected void save() throws SQLException {
HSQLDBSaver saveHelper = new HSQLDBSaver("BlockTransactions");
saveHelper.bind("block_signature", this.blockSignature).bind("sequence", this.sequence).bind("transaction_signature", this.transactionSignature);
saveHelper.execute();
}
// Navigation
/**
* Load corresponding Block from DB.
*
* @return Block, or null if not found (which should never happen)
* @throws SQLException
*/
public Block getBlock() throws SQLException {
return Block.fromSignature(this.blockSignature);
}
/**
* Load corresponding Transaction from DB.
*
* @return Transaction, or null if not found (which should never happen)
* @throws SQLException
*/
public Transaction getTransaction() throws SQLException {
// XXX
// return TransactionFactory.fromSignature(this.transactionSignature);
return null;
}
} }

View File

@ -0,0 +1,9 @@
package repository;
import data.block.BlockTransactionData;
public interface BlockTransactionRepository {
public void save(BlockTransactionData blockTransactionData) throws DataException;
}

View File

@ -1,19 +1,17 @@
package repository; package repository;
public abstract class Repository { public interface Repository {
protected TransactionRepository transactionRepository; public BlockRepository getBlockRepository();
protected BlockRepository blockRepository;
public abstract void saveChanges() throws DataException ; public BlockTransactionRepository getBlockTransactionRepository();
public abstract void discardChanges() throws DataException ;
public abstract void close() throws DataException ;
public TransactionRepository getTransactionRepository() { public TransactionRepository getTransactionRepository();
return this.transactionRepository;
} public void saveChanges() throws DataException;
public void discardChanges() throws DataException;
public void close() throws DataException;
public BlockRepository getBlockRepository() {
return this.blockRepository;
}
} }

View File

@ -0,0 +1,7 @@
package repository;
public interface RepositoryFactory {
public Repository getRepository() throws DataException;
}

View File

@ -2,18 +2,14 @@ package repository;
public abstract class RepositoryManager { public abstract class RepositoryManager {
private static Repository repository; private static RepositoryFactory repositoryFactory;
public static void setRepository(Repository newRepository) { public static void setRepositoryFactory(RepositoryFactory newRepositoryFactory) {
repository = newRepository; repositoryFactory = newRepositoryFactory;
} }
public static TransactionRepository getTransactionRepository() { public static Repository getRepository() throws DataException {
return repository.transactionRepository; return repositoryFactory.getRepository();
}
public static BlockRepository getBlockRepository() {
return repository.blockRepository;
} }
} }

View File

@ -9,12 +9,12 @@ public interface TransactionRepository {
public TransactionData fromReference(byte[] reference); public TransactionData fromReference(byte[] reference);
public int getHeight(TransactionData transaction); public int getHeight(TransactionData transactionData);
public BlockData toBlock(TransactionData transaction); public BlockData toBlock(TransactionData transactionData);
public void save(TransactionData transaction) throws DataException; public void save(TransactionData transactionData) throws DataException;
public void delete(TransactionData transaction) throws DataException; public void delete(TransactionData transactionData) throws DataException;
} }

View File

@ -9,16 +9,11 @@ import java.util.List;
import data.block.BlockData; import data.block.BlockData;
import data.transaction.TransactionData; import data.transaction.TransactionData;
import database.DB;
import repository.BlockRepository; import repository.BlockRepository;
import repository.DataException; import repository.DataException;
import repository.RepositoryManager;
import repository.TransactionRepository; import repository.TransactionRepository;
public class HSQLDBBlockRepository implements BlockRepository { public class HSQLDBBlockRepository implements BlockRepository {
protected static final int TRANSACTIONS_SIGNATURE_LENGTH = 64;
protected static final int GENERATOR_SIGNATURE_LENGTH = 64;
protected static final int REFERENCE_LENGTH = GENERATOR_SIGNATURE_LENGTH + TRANSACTIONS_SIGNATURE_LENGTH;
private static final String BLOCK_DB_COLUMNS = "version, reference, transaction_count, total_fees, " private static final String BLOCK_DB_COLUMNS = "version, reference, transaction_count, total_fees, "
+ "transactions_signature, height, generation, generating_balance, generator, generator_signature, AT_data, AT_fees"; + "transactions_signature, height, generation, generating_balance, generator, generator_signature, AT_data, AT_fees";
@ -29,9 +24,9 @@ public class HSQLDBBlockRepository implements BlockRepository {
this.repository = repository; this.repository = repository;
} }
public BlockData fromSignature(byte[] signature) throws DataException { public BlockData fromSignature(byte[] signature) throws DataException {
try { try {
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature); ResultSet rs = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE signature = ?", signature);
return getBlockFromResultSet(rs); return getBlockFromResultSet(rs);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Error loading data from DB", e); throw new DataException("Error loading data from DB", e);
@ -40,7 +35,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
public BlockData fromReference(byte[] reference) throws DataException { public BlockData fromReference(byte[] reference) throws DataException {
try { try {
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height); ResultSet rs = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", reference);
return getBlockFromResultSet(rs); return getBlockFromResultSet(rs);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Error loading data from DB", e); throw new DataException("Error loading data from DB", e);
@ -49,7 +44,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
public BlockData fromHeight(int height) throws DataException { public BlockData fromHeight(int height) throws DataException {
try { try {
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height); ResultSet rs = this.repository.checkedExecute("SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE height = ?", height);
return getBlockFromResultSet(rs); return getBlockFromResultSet(rs);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Error loading data from DB", e); throw new DataException("Error loading data from DB", e);
@ -62,21 +57,21 @@ public class HSQLDBBlockRepository implements BlockRepository {
try { try {
int version = rs.getInt(1); int version = rs.getInt(1);
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2)); byte[] reference = this.repository.getResultSetBytes(rs.getBinaryStream(2));
int transactionCount = rs.getInt(3); int transactionCount = rs.getInt(3);
BigDecimal totalFees = rs.getBigDecimal(4); BigDecimal totalFees = rs.getBigDecimal(4);
byte[] transactionsSignature = DB.getResultSetBytes(rs.getBinaryStream(5)); byte[] transactionsSignature = this.repository.getResultSetBytes(rs.getBinaryStream(5));
int height = rs.getInt(6); int height = rs.getInt(6);
long timestamp = rs.getTimestamp(7).getTime(); long timestamp = rs.getTimestamp(7).getTime();
BigDecimal generatingBalance = rs.getBigDecimal(8); BigDecimal generatingBalance = rs.getBigDecimal(8);
byte[] generatorPublicKey = DB.getResultSetBytes(rs.getBinaryStream(9)); byte[] generatorPublicKey = this.repository.getResultSetBytes(rs.getBinaryStream(9));
byte[] generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10)); byte[] generatorSignature = this.repository.getResultSetBytes(rs.getBinaryStream(10));
byte[] atBytes = DB.getResultSetBytes(rs.getBinaryStream(11)); byte[] atBytes = this.repository.getResultSetBytes(rs.getBinaryStream(11));
BigDecimal atFees = rs.getBigDecimal(12); BigDecimal atFees = rs.getBigDecimal(12);
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance, generatorPublicKey, return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
generatorSignature, atBytes, atFees); generatorPublicKey, generatorSignature, atBytes, atFees);
} catch(SQLException e) { } catch (SQLException e) {
throw new DataException("Error extracting data from result set", e); throw new DataException("Error extracting data from result set", e);
} }
} }
@ -85,15 +80,15 @@ public class HSQLDBBlockRepository implements BlockRepository {
List<TransactionData> transactions = new ArrayList<TransactionData>(); List<TransactionData> transactions = new ArrayList<TransactionData>();
try { try {
ResultSet rs = DB.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", signature); ResultSet rs = this.repository.checkedExecute("SELECT transaction_signature FROM BlockTransactions WHERE block_signature = ?", signature);
if (rs == null) if (rs == null)
return transactions; // No transactions in this block return transactions; // No transactions in this block
TransactionRepository transactionRepo = RepositoryManager.getTransactionRepository(); TransactionRepository transactionRepo = this.repository.getTransactionRepository();
// NB: do-while loop because DB.checkedExecute() implicitly calls ResultSet.next() for us // NB: do-while loop because .checkedExecute() implicitly calls ResultSet.next() for us
do { do {
byte[] transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1)); byte[] transactionSignature = this.repository.getResultSetBytes(rs.getBinaryStream(1));
transactions.add(transactionRepo.fromSignature(transactionSignature)); transactions.add(transactionRepo.fromSignature(transactionSignature));
} while (rs.next()); } while (rs.next());
} catch (SQLException e) { } catch (SQLException e) {
@ -107,13 +102,14 @@ public class HSQLDBBlockRepository implements BlockRepository {
HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks"); HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks");
saveHelper.bind("signature", blockData.getSignature()).bind("version", blockData.getVersion()).bind("reference", blockData.getReference()) saveHelper.bind("signature", blockData.getSignature()).bind("version", blockData.getVersion()).bind("reference", blockData.getReference())
.bind("transaction_count", blockData.getTransactionCount()).bind("total_fees", blockData.getTotalFees()).bind("transactions_signature", blockData.getTransactionsSignature()) .bind("transaction_count", blockData.getTransactionCount()).bind("total_fees", blockData.getTotalFees())
.bind("height", blockData.getHeight()).bind("generation", new Timestamp(blockData.getTimestamp())).bind("generating_balance", blockData.getGeneratingBalance()) .bind("transactions_signature", blockData.getTransactionsSignature()).bind("height", blockData.getHeight())
.bind("generator", blockData.getGeneratorPublicKey()).bind("generator_signature", blockData.getGeneratorSignature()).bind("AT_data", blockData.getAtBytes()) .bind("generation", new Timestamp(blockData.getTimestamp())).bind("generating_balance", blockData.getGeneratingBalance())
.bind("AT_fees", blockData.getAtFees()); .bind("generator", blockData.getGeneratorPublicKey()).bind("generator_signature", blockData.getGeneratorSignature())
.bind("AT_data", blockData.getAtBytes()).bind("AT_fees", blockData.getAtFees());
try { try {
saveHelper.execute(); saveHelper.execute(this.repository.connection);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to save Block into repository", e); throw new DataException("Unable to save Block into repository", e);
} }

View File

@ -0,0 +1,29 @@
package repository.hsqldb;
import java.sql.SQLException;
import data.block.BlockTransactionData;
import repository.BlockTransactionRepository;
import repository.DataException;
public class HSQLDBBlockTransactionRepository implements BlockTransactionRepository {
protected HSQLDBRepository repository;
public HSQLDBBlockTransactionRepository(HSQLDBRepository repository) {
this.repository = repository;
}
public void save(BlockTransactionData blockTransactionData) throws DataException {
HSQLDBSaver saveHelper = new HSQLDBSaver("BlockTransactions");
saveHelper.bind("block_signature", blockTransactionData.getBlockSignature()).bind("sequence", blockTransactionData.getSequence())
.bind("transaction_signature", blockTransactionData.getTransactionSignature());
try {
saveHelper.execute(this.repository.connection);
} catch (SQLException e) {
throw new DataException("Unable to save BlockTransaction into repository", e);
}
}
}

View File

@ -1,19 +1,20 @@
package database; package repository.hsqldb;
import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
public class DatabaseUpdates { public class HSQLDBDatabaseUpdates {
/** /**
* Apply any incremental changes to database schema. * Apply any incremental changes to database schema.
* *
* @throws SQLException * @throws SQLException
*/ */
public static void updateDatabase() throws SQLException { public static void updateDatabase(Connection connection) throws SQLException {
while (databaseUpdating()) while (databaseUpdating(connection))
incrementDatabaseVersion(); incrementDatabaseVersion(connection);
} }
/** /**
@ -21,8 +22,8 @@ public class DatabaseUpdates {
* *
* @throws SQLException * @throws SQLException
*/ */
private static void incrementDatabaseVersion() throws SQLException { private static void incrementDatabaseVersion(Connection connection) throws SQLException {
DB.getConnection().createStatement().execute("UPDATE DatabaseInfo SET version = version + 1"); connection.createStatement().execute("UPDATE DatabaseInfo SET version = version + 1");
} }
/** /**
@ -31,11 +32,11 @@ public class DatabaseUpdates {
* @return int, 0 if no schema yet * @return int, 0 if no schema yet
* @throws SQLException * @throws SQLException
*/ */
private static int fetchDatabaseVersion() throws SQLException { private static int fetchDatabaseVersion(Connection connection) throws SQLException {
int databaseVersion = 0; int databaseVersion = 0;
try { try {
Statement stmt = DB.getConnection().createStatement(); Statement stmt = connection.createStatement();
if (stmt.execute("SELECT version FROM DatabaseInfo")) { if (stmt.execute("SELECT version FROM DatabaseInfo")) {
ResultSet rs = stmt.getResultSet(); ResultSet rs = stmt.getResultSet();
@ -55,10 +56,10 @@ public class DatabaseUpdates {
* @return true - if a schema update happened, false otherwise * @return true - if a schema update happened, false otherwise
* @throws SQLException * @throws SQLException
*/ */
private static boolean databaseUpdating() throws SQLException { private static boolean databaseUpdating(Connection connection) throws SQLException {
int databaseVersion = fetchDatabaseVersion(); int databaseVersion = fetchDatabaseVersion(connection);
Statement stmt = DB.getConnection().createStatement(); Statement stmt = connection.createStatement();
/* /*
* Try not to add too many constraints as much of these checks will be performed during transaction validation. Also some constraints might be too harsh * Try not to add too many constraints as much of these checks will be performed during transaction validation. Also some constraints might be too harsh

View File

@ -6,7 +6,6 @@ import java.sql.SQLException;
import data.transaction.GenesisTransactionData; import data.transaction.GenesisTransactionData;
import data.transaction.TransactionData; import data.transaction.TransactionData;
import database.DB;
import repository.DataException; import repository.DataException;
public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionRepository { public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionRepository {
@ -17,7 +16,7 @@ public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionReposit
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creator, long timestamp, BigDecimal fee) { TransactionData fromBase(byte[] signature, byte[] reference, byte[] creator, long timestamp, BigDecimal fee) {
try { try {
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature); ResultSet rs = this.repository.checkedExecute("SELECT recipient, amount FROM GenesisTransactions WHERE signature = ?", signature);
if (rs == null) if (rs == null)
return null; return null;
@ -39,7 +38,7 @@ public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionReposit
HSQLDBSaver saveHelper = new HSQLDBSaver("GenesisTransactions"); HSQLDBSaver saveHelper = new HSQLDBSaver("GenesisTransactions");
saveHelper.bind("signature", genesisTransaction.getSignature()).bind("recipient", genesisTransaction.getRecipient()).bind("amount", genesisTransaction.getAmount()); saveHelper.bind("signature", genesisTransaction.getSignature()).bind("recipient", genesisTransaction.getRecipient()).bind("amount", genesisTransaction.getAmount());
try { try {
saveHelper.execute(); saveHelper.execute(this.repository.connection);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException(e); throw new DataException(e);
} }

View File

@ -1,40 +1,47 @@
package repository.hsqldb; package repository.hsqldb;
import java.lang.ref.PhantomReference; import java.io.IOException;
import java.lang.ref.ReferenceQueue; import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import database.DB; import repository.BlockRepository;
import repository.BlockTransactionRepository;
import repository.DataException; import repository.DataException;
import repository.Repository; import repository.Repository;
import repository.TransactionRepository;
public class HSQLDBRepository extends Repository { public class HSQLDBRepository implements Repository {
Connection connection; Connection connection;
public HSQLDBRepository() throws DataException { // NB: no visibility modifier so only callable from within same package
try { HSQLDBRepository(Connection connection) {
initialize(); this.connection = connection;
} catch (SQLException e) {
throw new DataException("initialization error", e);
}
this.transactionRepository = new HSQLDBTransactionRepository(this);
} }
private void initialize() throws SQLException { @Override
connection = DB.getPoolConnection(); public BlockRepository getBlockRepository() {
return new HSQLDBBlockRepository(this);
}
// start transaction @Override
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); public BlockTransactionRepository getBlockTransactionRepository() {
connection.setAutoCommit(false); return new HSQLDBBlockTransactionRepository(this);
}
@Override
public TransactionRepository getTransactionRepository() {
return new HSQLDBTransactionRepository(this);
} }
@Override @Override
public void saveChanges() throws DataException { public void saveChanges() throws DataException {
try { try {
connection.commit(); this.connection.commit();
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("commit error", e); throw new DataException("commit error", e);
} }
@ -43,7 +50,7 @@ public class HSQLDBRepository extends Repository {
@Override @Override
public void discardChanges() throws DataException { public void discardChanges() throws DataException {
try { try {
connection.rollback(); this.connection.rollback();
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("rollback error", e); throw new DataException("rollback error", e);
} }
@ -55,11 +62,128 @@ public class HSQLDBRepository extends Repository {
public void close() throws DataException { public void close() throws DataException {
try { try {
// give connection back to the pool // give connection back to the pool
connection.close(); this.connection.close();
connection = null; this.connection = null;
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("close error", e); throw new DataException("close error", e);
} }
} }
/**
* Convert InputStream, from ResultSet.getBinaryStream(), into byte[].
*
* @param inputStream
* @return byte[]
*/
byte[] getResultSetBytes(InputStream inputStream) {
// inputStream could be null if database's column's value is null
if (inputStream == null)
return null;
try {
int length = inputStream.available();
byte[] result = new byte[length];
if (inputStream.read(result) == length)
return result;
} catch (IOException e) {
// Fall-through to return null
}
return null;
}
/**
* Execute SQL and return ResultSet with but added checking.
* <p>
* <b>Note: calls ResultSet.next()</b> therefore returned ResultSet is already pointing to first row.
*
* @param sql
* @param objects
* @return ResultSet, or null if there are no found rows
* @throws SQLException
*/
ResultSet checkedExecute(String sql, Object... objects) throws SQLException {
PreparedStatement preparedStatement = this.connection.prepareStatement(sql);
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]);
return this.checkedExecute(preparedStatement);
}
/**
* Execute PreparedStatement and return ResultSet with but added checking.
* <p>
* <b>Note: calls ResultSet.next()</b> therefore returned ResultSet is already pointing to first row.
*
* @param preparedStatement
* @return ResultSet, or null if there are no found rows
* @throws SQLException
*/
ResultSet checkedExecute(PreparedStatement preparedStatement) throws SQLException {
if (!preparedStatement.execute())
throw new SQLException("Fetching from database produced no results");
ResultSet resultSet = preparedStatement.getResultSet();
if (resultSet == null)
throw new SQLException("Fetching results from database produced no ResultSet");
if (!resultSet.next())
return null;
return resultSet;
}
/**
* Fetch last value of IDENTITY column after an INSERT statement.
* <p>
* Performs "CALL IDENTITY()" SQL statement to retrieve last value used when INSERTing into a table that has an IDENTITY column.
* <p>
* Typically used after INSERTing NULL as the IDENTIY column's value to fetch what value was actually stored by HSQLDB.
*
* @return Long
* @throws SQLException
*/
Long callIdentity() throws SQLException {
PreparedStatement preparedStatement = this.connection.prepareStatement("CALL IDENTITY()");
ResultSet resultSet = this.checkedExecute(preparedStatement);
if (resultSet == null)
return null;
return resultSet.getLong(1);
}
/**
* Efficiently query database for existing of matching row.
* <p>
* {@code whereClause} is SQL "WHERE" clause containing "?" placeholders suitable for use with PreparedStatements.
* <p>
* Example call:
* <p>
* {@code String manufacturer = "Lamborghini";}<br>
* {@code int maxMileage = 100_000;}<br>
* {@code boolean isAvailable = DB.exists("Cars", "manufacturer = ? AND mileage <= ?", manufacturer, maxMileage);}
*
* @param tableName
* @param whereClause
* @param objects
* @return true if matching row found in database, false otherwise
* @throws SQLException
*/
boolean exists(String tableName, String whereClause, Object... objects) throws SQLException {
PreparedStatement preparedStatement = this.connection
.prepareStatement("SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " ORDER BY NULL LIMIT 1");
ResultSet resultSet = this.checkedExecute(preparedStatement);
if (resultSet == null)
return false;
return true;
}
} }

View File

@ -0,0 +1,50 @@
package repository.hsqldb;
import java.sql.Connection;
import java.sql.SQLException;
import org.hsqldb.jdbc.JDBCPool;
import repository.DataException;
import repository.Repository;
import repository.RepositoryFactory;
public class HSQLDBRepositoryFactory implements RepositoryFactory {
private String connectionUrl;
private JDBCPool connectionPool;
public HSQLDBRepositoryFactory(String connectionUrl) throws DataException {
// one-time initialization goes in here
this.connectionUrl = connectionUrl;
connectionPool = new JDBCPool();
connectionPool.setUrl(this.connectionUrl);
// Perform DB updates?
try (final Connection connection = connectionPool.getConnection()) {
HSQLDBDatabaseUpdates.updateDatabase(connection);
} catch (SQLException e) {
throw new DataException("Repository initialization error", e);
}
}
public Repository getRepository() throws DataException {
try {
return new HSQLDBRepository(this.getConnection());
} catch (SQLException e) {
throw new DataException("Repository initialization error", e);
}
}
private Connection getConnection() throws SQLException {
Connection connection = this.connectionPool.getConnection();
// start transaction
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
connection.setAutoCommit(false);
return connection;
}
}

View File

@ -8,8 +8,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import database.DB;
/** /**
* Database helper for building, and executing, INSERT INTO ... ON DUPLICATE KEY UPDATE ... statements. * Database helper for building, and executing, INSERT INTO ... ON DUPLICATE KEY UPDATE ... statements.
* <p> * <p>
@ -52,14 +50,10 @@ public class HSQLDBSaver {
/** /**
* Build PreparedStatement using bound column-value pairs then execute it. * Build PreparedStatement using bound column-value pairs then execute it.
* *
* @param connection
* @return the result from {@link PreparedStatement#execute()} * @return the result from {@link PreparedStatement#execute()}
* @throws SQLException * @throws SQLException
*/ */
public boolean execute() throws SQLException {
Connection connection = DB.getConnection();
return execute(connection);
}
public boolean execute(Connection connection) throws SQLException { public boolean execute(Connection connection) throws SQLException {
String sql = this.formatInsertWithPlaceholders(); String sql = this.formatInsertWithPlaceholders();
PreparedStatement preparedStatement = connection.prepareStatement(sql); PreparedStatement preparedStatement = connection.prepareStatement(sql);

View File

@ -1,7 +1,6 @@
package repository.hsqldb; package repository.hsqldb;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
@ -9,9 +8,7 @@ import java.sql.Timestamp;
import data.block.BlockData; import data.block.BlockData;
import data.transaction.TransactionData; import data.transaction.TransactionData;
import qora.transaction.Transaction.TransactionType; import qora.transaction.Transaction.TransactionType;
import database.DB;
import repository.DataException; import repository.DataException;
import repository.RepositoryManager;
import repository.TransactionRepository; import repository.TransactionRepository;
public class HSQLDBTransactionRepository implements TransactionRepository { public class HSQLDBTransactionRepository implements TransactionRepository {
@ -26,13 +23,13 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
public TransactionData fromSignature(byte[] signature) { public TransactionData fromSignature(byte[] signature) {
try { try {
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature); ResultSet rs = this.repository.checkedExecute("SELECT type, reference, creator, creation, fee FROM Transactions WHERE signature = ?", signature);
if (rs == null) if (rs == null)
return null; return null;
TransactionType type = TransactionType.valueOf(rs.getInt(1)); TransactionType type = TransactionType.valueOf(rs.getInt(1));
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2)); byte[] reference = this.repository.getResultSetBytes(rs.getBinaryStream(2));
byte[] creator = DB.getResultSetBytes(rs.getBinaryStream(3)); byte[] creator = this.repository.getResultSetBytes(rs.getBinaryStream(3));
long timestamp = rs.getTimestamp(4).getTime(); long timestamp = rs.getTimestamp(4).getTime();
BigDecimal fee = rs.getBigDecimal(5).setScale(8); BigDecimal fee = rs.getBigDecimal(5).setScale(8);
@ -44,13 +41,13 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
public TransactionData fromReference(byte[] reference) { public TransactionData fromReference(byte[] reference) {
try { try {
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", reference); ResultSet rs = this.repository.checkedExecute("SELECT type, signature, creator, creation, fee FROM Transactions WHERE reference = ?", reference);
if (rs == null) if (rs == null)
return null; return null;
TransactionType type = TransactionType.valueOf(rs.getInt(1)); TransactionType type = TransactionType.valueOf(rs.getInt(1));
byte[] signature = DB.getResultSetBytes(rs.getBinaryStream(2)); byte[] signature = this.repository.getResultSetBytes(rs.getBinaryStream(2));
byte[] creator = DB.getResultSetBytes(rs.getBinaryStream(3)); byte[] creator = this.repository.getResultSetBytes(rs.getBinaryStream(3));
long timestamp = rs.getTimestamp(4).getTime(); long timestamp = rs.getTimestamp(4).getTime();
BigDecimal fee = rs.getBigDecimal(5).setScale(8); BigDecimal fee = rs.getBigDecimal(5).setScale(8);
@ -78,7 +75,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
// in one go? // in one go?
try { try {
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT height from BlockTransactions JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature WHERE transaction_signature = ? LIMIT 1", signature); ResultSet rs = this.repository.checkedExecute(
"SELECT height from BlockTransactions JOIN Blocks ON Blocks.signature = BlockTransactions.block_signature WHERE transaction_signature = ? LIMIT 1",
signature);
if (rs == null) if (rs == null)
return 0; return 0;
@ -97,13 +96,13 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
// Fetch block signature (if any) // Fetch block signature (if any)
try { try {
ResultSet rs = DB.checkedExecute(repository.connection, "SELECT block_signature from BlockTransactions WHERE transaction_signature = ? LIMIT 1", signature); ResultSet rs = this.repository.checkedExecute("SELECT block_signature from BlockTransactions WHERE transaction_signature = ? LIMIT 1", signature);
if (rs == null) if (rs == null)
return null; return null;
byte[] blockSignature = DB.getResultSetBytes(rs.getBinaryStream(1)); byte[] blockSignature = this.repository.getResultSetBytes(rs.getBinaryStream(1));
return RepositoryManager.getBlockRepository().fromSignature(blockSignature); return this.repository.getBlockRepository().fromSignature(blockSignature);
} catch (SQLException | DataException e) { } catch (SQLException | DataException e) {
return null; return null;
} }
@ -113,23 +112,23 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
public void save(TransactionData transactionData) throws DataException { public void save(TransactionData transactionData) throws DataException {
HSQLDBSaver saver = new HSQLDBSaver("Transactions"); HSQLDBSaver saver = new HSQLDBSaver("Transactions");
saver.bind("signature", transactionData.getSignature()).bind("reference", transactionData.getReference()).bind("type", transactionData.getType().value) saver.bind("signature", transactionData.getSignature()).bind("reference", transactionData.getReference()).bind("type", transactionData.getType().value)
.bind("creator", transactionData.getCreatorPublicKey()).bind("creation", new Timestamp(transactionData.getTimestamp())).bind("fee", transactionData.getFee()) .bind("creator", transactionData.getCreatorPublicKey()).bind("creation", new Timestamp(transactionData.getTimestamp()))
.bind("milestone_block", null); .bind("fee", transactionData.getFee()).bind("milestone_block", null);
try { try {
saver.execute(repository.connection); saver.execute(this.repository.connection);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException(e); throw new DataException(e);
} }
} }
@Override @Override
public void delete(TransactionData transactionData) { public void delete(TransactionData transactionData) throws DataException {
// 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 // 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. // definition.
try { try {
DB.checkedExecute(repository.connection, "DELETE FROM Transactions WHERE signature = ?", transaction.getSignature()); this.repository.checkedExecute("DELETE FROM Transactions WHERE signature = ?", transactionData.getSignature());
} catch (SQLException e) { } catch (SQLException e) {
// XXX do what? throw new DataException(e);
} }
} }

View File

@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;

View File

@ -5,7 +5,6 @@ import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import qora.account.PublicKeyAccount;
import transform.TransformationException; import transform.TransformationException;
import transform.Transformer; import transform.Transformer;