forked from Qortal/qortal
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:
parent
6a2a172ee8
commit
d45c33fe90
32
src/data/block/BlockTransactionData.java
Normal file
32
src/data/block/BlockTransactionData.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -1,49 +1,28 @@
|
||||
package qora.block;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Savepoint;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import data.block.BlockData;
|
||||
import data.block.BlockTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import database.DB;
|
||||
import database.NoDataFoundException;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
import qora.assets.Order;
|
||||
import qora.assets.Trade;
|
||||
import qora.transaction.CreateOrderTransaction;
|
||||
import qora.transaction.GenesisTransaction;
|
||||
import qora.transaction.Transaction;
|
||||
import repository.BlockRepository;
|
||||
import repository.DataException;
|
||||
import repository.RepositoryManager;
|
||||
import repository.hsqldb.HSQLDBSaver;
|
||||
import repository.Repository;
|
||||
import transform.TransformationException;
|
||||
import transform.block.BlockTransformer;
|
||||
import transform.transaction.TransactionTransformer;
|
||||
import utils.Base58;
|
||||
import utils.NTP;
|
||||
import utils.Serialization;
|
||||
|
||||
/*
|
||||
* Typical use-case scenarios:
|
||||
@ -70,6 +49,7 @@ import utils.Serialization;
|
||||
public class Block {
|
||||
|
||||
// Properties
|
||||
private Repository repository;
|
||||
private BlockData blockData;
|
||||
private PublicKeyAccount generator;
|
||||
|
||||
@ -96,7 +76,8 @@ public class Block {
|
||||
|
||||
// Constructors
|
||||
|
||||
public Block(BlockData blockData) {
|
||||
public Block(Repository repository, BlockData blockData) {
|
||||
this.repository = repository;
|
||||
this.blockData = blockData;
|
||||
this.generator = new PublicKeyAccount(blockData.getGeneratorPublicKey());
|
||||
}
|
||||
@ -159,7 +140,7 @@ public class Block {
|
||||
|
||||
// Navigate back to first block in previous interval:
|
||||
// XXX: why can't we simply load using block height?
|
||||
BlockRepository blockRepo = RepositoryManager.getBlockRepository();
|
||||
BlockRepository blockRepo = this.repository.getBlockRepository();
|
||||
BlockData firstBlock = this.blockData;
|
||||
|
||||
try {
|
||||
@ -212,7 +193,7 @@ public class Block {
|
||||
return this.transactions;
|
||||
|
||||
// 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
|
||||
if (transactionsData.size() != this.blockData.getTransactionCount())
|
||||
@ -348,11 +329,11 @@ public class Block {
|
||||
if (this.blockData.getReference() == null)
|
||||
return false;
|
||||
|
||||
BlockData parentBlockData = RepositoryManager.getBlockRepository().fromSignature(this.blockData.getReference());
|
||||
BlockData parentBlockData = this.repository.getBlockRepository().fromSignature(this.blockData.getReference());
|
||||
if (parentBlockData == null)
|
||||
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
|
||||
if (this.blockData.getTimestamp() < parentBlockData.getTimestamp() || this.blockData.getTimestamp() - BLOCK_TIMESTAMP_MARGIN > NTP.getTime())
|
||||
@ -387,7 +368,6 @@ public class Block {
|
||||
}
|
||||
|
||||
// Check transactions
|
||||
Savepoint savepoint = DB.createSavepoint("BLOCK_TRANSACTIONS");
|
||||
try {
|
||||
for (Transaction transaction : this.getTransactions()) {
|
||||
// GenesisTransactions are not allowed (GenesisBlock overrides isValid() to allow them)
|
||||
@ -395,7 +375,8 @@ public class Block {
|
||||
return false;
|
||||
|
||||
// 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;
|
||||
|
||||
// Check transaction is even valid
|
||||
@ -416,11 +397,11 @@ public class Block {
|
||||
} finally {
|
||||
// Revert back to savepoint
|
||||
try {
|
||||
DB.rollbackToSavepoint(savepoint);
|
||||
} catch (SQLException e) {
|
||||
this.repository.discardChanges();
|
||||
} 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
|
||||
* still return false, prior SQLException propagates to caller and successful completion of try-block continues on after rollback.
|
||||
* 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 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
|
||||
int blockchainHeight = BlockChain.getHeight();
|
||||
BlockData latestBlockData = RepositoryManager.getBlockRepository().fromHeight(blockchainHeight);
|
||||
BlockData latestBlockData = this.repository.getBlockRepository().fromHeight(blockchainHeight);
|
||||
if (latestBlockData != null)
|
||||
this.blockData.setReference(latestBlockData.getSignature());
|
||||
|
||||
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.
|
||||
for (int sequence = 0; sequence < transactions.size(); ++sequence) {
|
||||
Transaction transaction = transactions.get(sequence);
|
||||
|
||||
// Link transaction to this block
|
||||
BlockTransaction blockTransaction = new BlockTransaction(this.getSignature(), sequence, transaction.getTransactionData().getSignature());
|
||||
blockTransaction.save();
|
||||
BlockTransactionData blockTransactionData = new BlockTransactionData(this.getSignature(), sequence, transaction.getTransactionData().getSignature());
|
||||
this.repository.getBlockTransactionRepository().save(blockTransactionData);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
}
|
@ -1,126 +1,5 @@
|
||||
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 {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
9
src/repository/BlockTransactionRepository.java
Normal file
9
src/repository/BlockTransactionRepository.java
Normal file
@ -0,0 +1,9 @@
|
||||
package repository;
|
||||
|
||||
import data.block.BlockTransactionData;
|
||||
|
||||
public interface BlockTransactionRepository {
|
||||
|
||||
public void save(BlockTransactionData blockTransactionData) throws DataException;
|
||||
|
||||
}
|
@ -1,19 +1,17 @@
|
||||
package repository;
|
||||
|
||||
public abstract class Repository {
|
||||
public interface Repository {
|
||||
|
||||
protected TransactionRepository transactionRepository;
|
||||
protected BlockRepository blockRepository;
|
||||
public BlockRepository getBlockRepository();
|
||||
|
||||
public abstract void saveChanges() throws DataException ;
|
||||
public abstract void discardChanges() throws DataException ;
|
||||
public abstract void close() throws DataException ;
|
||||
|
||||
public TransactionRepository getTransactionRepository() {
|
||||
return this.transactionRepository;
|
||||
}
|
||||
public BlockTransactionRepository getBlockTransactionRepository();
|
||||
|
||||
public TransactionRepository getTransactionRepository();
|
||||
|
||||
public void saveChanges() throws DataException;
|
||||
|
||||
public void discardChanges() throws DataException;
|
||||
|
||||
public void close() throws DataException;
|
||||
|
||||
public BlockRepository getBlockRepository() {
|
||||
return this.blockRepository;
|
||||
}
|
||||
}
|
||||
|
7
src/repository/RepositoryFactory.java
Normal file
7
src/repository/RepositoryFactory.java
Normal file
@ -0,0 +1,7 @@
|
||||
package repository;
|
||||
|
||||
public interface RepositoryFactory {
|
||||
|
||||
public Repository getRepository() throws DataException;
|
||||
|
||||
}
|
@ -2,18 +2,14 @@ package repository;
|
||||
|
||||
public abstract class RepositoryManager {
|
||||
|
||||
private static Repository repository;
|
||||
private static RepositoryFactory repositoryFactory;
|
||||
|
||||
public static void setRepository(Repository newRepository) {
|
||||
repository = newRepository;
|
||||
public static void setRepositoryFactory(RepositoryFactory newRepositoryFactory) {
|
||||
repositoryFactory = newRepositoryFactory;
|
||||
}
|
||||
|
||||
public static TransactionRepository getTransactionRepository() {
|
||||
return repository.transactionRepository;
|
||||
}
|
||||
|
||||
public static BlockRepository getBlockRepository() {
|
||||
return repository.blockRepository;
|
||||
public static Repository getRepository() throws DataException {
|
||||
return repositoryFactory.getRepository();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,12 +9,12 @@ public interface TransactionRepository {
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
@ -9,29 +9,24 @@ import java.util.List;
|
||||
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
import database.DB;
|
||||
import repository.BlockRepository;
|
||||
import repository.DataException;
|
||||
import repository.RepositoryManager;
|
||||
import repository.TransactionRepository;
|
||||
|
||||
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, "
|
||||
+ "transactions_signature, height, generation, generating_balance, generator, generator_signature, AT_data, AT_fees";
|
||||
|
||||
protected HSQLDBRepository repository;
|
||||
|
||||
|
||||
public HSQLDBBlockRepository(HSQLDBRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public BlockData fromSignature(byte[] signature) throws DataException {
|
||||
public BlockData fromSignature(byte[] signature) throws DataException {
|
||||
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);
|
||||
} catch (SQLException 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 {
|
||||
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);
|
||||
} catch (SQLException 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 {
|
||||
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);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Error loading data from DB", e);
|
||||
@ -62,21 +57,21 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
||||
|
||||
try {
|
||||
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);
|
||||
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);
|
||||
long timestamp = rs.getTimestamp(7).getTime();
|
||||
BigDecimal generatingBalance = rs.getBigDecimal(8);
|
||||
byte[] generatorPublicKey = DB.getResultSetBytes(rs.getBinaryStream(9));
|
||||
byte[] generatorSignature = DB.getResultSetBytes(rs.getBinaryStream(10));
|
||||
byte[] atBytes = DB.getResultSetBytes(rs.getBinaryStream(11));
|
||||
byte[] generatorPublicKey = this.repository.getResultSetBytes(rs.getBinaryStream(9));
|
||||
byte[] generatorSignature = this.repository.getResultSetBytes(rs.getBinaryStream(10));
|
||||
byte[] atBytes = this.repository.getResultSetBytes(rs.getBinaryStream(11));
|
||||
BigDecimal atFees = rs.getBigDecimal(12);
|
||||
|
||||
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance, generatorPublicKey,
|
||||
generatorSignature, atBytes, atFees);
|
||||
} catch(SQLException e) {
|
||||
|
||||
return new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp, generatingBalance,
|
||||
generatorPublicKey, generatorSignature, atBytes, atFees);
|
||||
} catch (SQLException 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>();
|
||||
|
||||
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)
|
||||
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 {
|
||||
byte[] transactionSignature = DB.getResultSetBytes(rs.getBinaryStream(1));
|
||||
byte[] transactionSignature = this.repository.getResultSetBytes(rs.getBinaryStream(1));
|
||||
transactions.add(transactionRepo.fromSignature(transactionSignature));
|
||||
} while (rs.next());
|
||||
} catch (SQLException e) {
|
||||
@ -107,13 +102,14 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Blocks");
|
||||
|
||||
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("height", blockData.getHeight()).bind("generation", new Timestamp(blockData.getTimestamp())).bind("generating_balance", blockData.getGeneratingBalance())
|
||||
.bind("generator", blockData.getGeneratorPublicKey()).bind("generator_signature", blockData.getGeneratorSignature()).bind("AT_data", blockData.getAtBytes())
|
||||
.bind("AT_fees", blockData.getAtFees());
|
||||
.bind("transaction_count", blockData.getTransactionCount()).bind("total_fees", blockData.getTotalFees())
|
||||
.bind("transactions_signature", blockData.getTransactionsSignature()).bind("height", blockData.getHeight())
|
||||
.bind("generation", new Timestamp(blockData.getTimestamp())).bind("generating_balance", blockData.getGeneratingBalance())
|
||||
.bind("generator", blockData.getGeneratorPublicKey()).bind("generator_signature", blockData.getGeneratorSignature())
|
||||
.bind("AT_data", blockData.getAtBytes()).bind("AT_fees", blockData.getAtFees());
|
||||
|
||||
try {
|
||||
saveHelper.execute();
|
||||
saveHelper.execute(this.repository.connection);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save Block into repository", e);
|
||||
}
|
||||
|
29
src/repository/hsqldb/HSQLDBBlockTransactionRepository.java
Normal file
29
src/repository/hsqldb/HSQLDBBlockTransactionRepository.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +1,20 @@
|
||||
package database;
|
||||
package repository.hsqldb;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
public class DatabaseUpdates {
|
||||
public class HSQLDBDatabaseUpdates {
|
||||
|
||||
/**
|
||||
* Apply any incremental changes to database schema.
|
||||
*
|
||||
* @throws SQLException
|
||||
*/
|
||||
public static void updateDatabase() throws SQLException {
|
||||
while (databaseUpdating())
|
||||
incrementDatabaseVersion();
|
||||
public static void updateDatabase(Connection connection) throws SQLException {
|
||||
while (databaseUpdating(connection))
|
||||
incrementDatabaseVersion(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -21,8 +22,8 @@ public class DatabaseUpdates {
|
||||
*
|
||||
* @throws SQLException
|
||||
*/
|
||||
private static void incrementDatabaseVersion() throws SQLException {
|
||||
DB.getConnection().createStatement().execute("UPDATE DatabaseInfo SET version = version + 1");
|
||||
private static void incrementDatabaseVersion(Connection connection) throws SQLException {
|
||||
connection.createStatement().execute("UPDATE DatabaseInfo SET version = version + 1");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,11 +32,11 @@ public class DatabaseUpdates {
|
||||
* @return int, 0 if no schema yet
|
||||
* @throws SQLException
|
||||
*/
|
||||
private static int fetchDatabaseVersion() throws SQLException {
|
||||
private static int fetchDatabaseVersion(Connection connection) throws SQLException {
|
||||
int databaseVersion = 0;
|
||||
|
||||
try {
|
||||
Statement stmt = DB.getConnection().createStatement();
|
||||
Statement stmt = connection.createStatement();
|
||||
if (stmt.execute("SELECT version FROM DatabaseInfo")) {
|
||||
ResultSet rs = stmt.getResultSet();
|
||||
|
||||
@ -55,10 +56,10 @@ public class DatabaseUpdates {
|
||||
* @return true - if a schema update happened, false otherwise
|
||||
* @throws SQLException
|
||||
*/
|
||||
private static boolean databaseUpdating() throws SQLException {
|
||||
int databaseVersion = fetchDatabaseVersion();
|
||||
private static boolean databaseUpdating(Connection connection) throws SQLException {
|
||||
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
|
@ -6,7 +6,6 @@ import java.sql.SQLException;
|
||||
|
||||
import data.transaction.GenesisTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import database.DB;
|
||||
import repository.DataException;
|
||||
|
||||
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) {
|
||||
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)
|
||||
return null;
|
||||
|
||||
@ -39,7 +38,7 @@ public class HSQLDBGenesisTransactionRepository extends HSQLDBTransactionReposit
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("GenesisTransactions");
|
||||
saveHelper.bind("signature", genesisTransaction.getSignature()).bind("recipient", genesisTransaction.getRecipient()).bind("amount", genesisTransaction.getAmount());
|
||||
try {
|
||||
saveHelper.execute();
|
||||
saveHelper.execute(this.repository.connection);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException(e);
|
||||
}
|
||||
|
@ -1,52 +1,59 @@
|
||||
package repository.hsqldb;
|
||||
|
||||
import java.lang.ref.PhantomReference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
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 database.DB;
|
||||
import repository.BlockRepository;
|
||||
import repository.BlockTransactionRepository;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
import repository.TransactionRepository;
|
||||
|
||||
public class HSQLDBRepository extends Repository {
|
||||
public class HSQLDBRepository implements Repository {
|
||||
|
||||
Connection connection;
|
||||
|
||||
public HSQLDBRepository() throws DataException {
|
||||
try {
|
||||
initialize();
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("initialization error", e);
|
||||
}
|
||||
|
||||
this.transactionRepository = new HSQLDBTransactionRepository(this);
|
||||
|
||||
// NB: no visibility modifier so only callable from within same package
|
||||
HSQLDBRepository(Connection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
private void initialize() throws SQLException {
|
||||
connection = DB.getPoolConnection();
|
||||
|
||||
// start transaction
|
||||
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
|
||||
connection.setAutoCommit(false);
|
||||
@Override
|
||||
public BlockRepository getBlockRepository() {
|
||||
return new HSQLDBBlockRepository(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockTransactionRepository getBlockTransactionRepository() {
|
||||
return new HSQLDBBlockTransactionRepository(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionRepository getTransactionRepository() {
|
||||
return new HSQLDBTransactionRepository(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveChanges() throws DataException {
|
||||
try {
|
||||
connection.commit();
|
||||
this.connection.commit();
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("commit error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void discardChanges() throws DataException {
|
||||
try {
|
||||
connection.rollback();
|
||||
this.connection.rollback();
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("rollback error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO prevent leaking of connections if .close() is not called before garbage collection of the repository.
|
||||
@ -55,11 +62,128 @@ public class HSQLDBRepository extends Repository {
|
||||
public void close() throws DataException {
|
||||
try {
|
||||
// give connection back to the pool
|
||||
connection.close();
|
||||
connection = null;
|
||||
this.connection.close();
|
||||
this.connection = null;
|
||||
} catch (SQLException 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
50
src/repository/hsqldb/HSQLDBRepositoryFactory.java
Normal file
50
src/repository/hsqldb/HSQLDBRepositoryFactory.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -8,8 +8,6 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import database.DB;
|
||||
|
||||
/**
|
||||
* Database helper for building, and executing, INSERT INTO ... ON DUPLICATE KEY UPDATE ... statements.
|
||||
* <p>
|
||||
@ -51,15 +49,11 @@ public class HSQLDBSaver {
|
||||
|
||||
/**
|
||||
* Build PreparedStatement using bound column-value pairs then execute it.
|
||||
*
|
||||
*
|
||||
* @param connection
|
||||
* @return the result from {@link PreparedStatement#execute()}
|
||||
* @throws SQLException
|
||||
*/
|
||||
public boolean execute() throws SQLException {
|
||||
Connection connection = DB.getConnection();
|
||||
return execute(connection);
|
||||
}
|
||||
|
||||
public boolean execute(Connection connection) throws SQLException {
|
||||
String sql = this.formatInsertWithPlaceholders();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(sql);
|
||||
|
@ -1,7 +1,6 @@
|
||||
package repository.hsqldb;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
@ -9,9 +8,7 @@ import java.sql.Timestamp;
|
||||
import data.block.BlockData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
import database.DB;
|
||||
import repository.DataException;
|
||||
import repository.RepositoryManager;
|
||||
import repository.TransactionRepository;
|
||||
|
||||
public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
@ -26,13 +23,13 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
|
||||
public TransactionData fromSignature(byte[] signature) {
|
||||
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)
|
||||
return null;
|
||||
|
||||
TransactionType type = TransactionType.valueOf(rs.getInt(1));
|
||||
byte[] reference = DB.getResultSetBytes(rs.getBinaryStream(2));
|
||||
byte[] creator = DB.getResultSetBytes(rs.getBinaryStream(3));
|
||||
byte[] reference = this.repository.getResultSetBytes(rs.getBinaryStream(2));
|
||||
byte[] creator = this.repository.getResultSetBytes(rs.getBinaryStream(3));
|
||||
long timestamp = rs.getTimestamp(4).getTime();
|
||||
BigDecimal fee = rs.getBigDecimal(5).setScale(8);
|
||||
|
||||
@ -44,13 +41,13 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
|
||||
public TransactionData fromReference(byte[] reference) {
|
||||
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)
|
||||
return null;
|
||||
|
||||
TransactionType type = TransactionType.valueOf(rs.getInt(1));
|
||||
byte[] signature = DB.getResultSetBytes(rs.getBinaryStream(2));
|
||||
byte[] creator = DB.getResultSetBytes(rs.getBinaryStream(3));
|
||||
byte[] signature = this.repository.getResultSetBytes(rs.getBinaryStream(2));
|
||||
byte[] creator = this.repository.getResultSetBytes(rs.getBinaryStream(3));
|
||||
long timestamp = rs.getTimestamp(4).getTime();
|
||||
BigDecimal fee = rs.getBigDecimal(5).setScale(8);
|
||||
|
||||
@ -78,7 +75,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
|
||||
// in one go?
|
||||
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)
|
||||
return 0;
|
||||
@ -97,13 +96,13 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
|
||||
// Fetch block signature (if any)
|
||||
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)
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
@ -113,23 +112,23 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
public void save(TransactionData transactionData) throws DataException {
|
||||
HSQLDBSaver saver = new HSQLDBSaver("Transactions");
|
||||
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("milestone_block", null);
|
||||
.bind("creator", transactionData.getCreatorPublicKey()).bind("creation", new Timestamp(transactionData.getTimestamp()))
|
||||
.bind("fee", transactionData.getFee()).bind("milestone_block", null);
|
||||
try {
|
||||
saver.execute(repository.connection);
|
||||
saver.execute(this.repository.connection);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
// definition.
|
||||
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) {
|
||||
// XXX do what?
|
||||
throw new DataException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
@ -5,7 +5,6 @@ import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import qora.account.PublicKeyAccount;
|
||||
import transform.TransformationException;
|
||||
import transform.Transformer;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user