diff --git a/src/data/block/BlockTransactionData.java b/src/data/block/BlockTransactionData.java new file mode 100644 index 00000000..fb62079a --- /dev/null +++ b/src/data/block/BlockTransactionData.java @@ -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; + } + +} diff --git a/src/database/DB.java b/src/database/DB.java deleted file mode 100644 index 6f74b486..00000000 --- a/src/database/DB.java +++ /dev/null @@ -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 local = new ThreadLocal() { - @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. - *

- * The connection URL must 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. - *

- * Typical example: - *

- * {@code setUrl("jdbc:hsqldb:file:db/qora")} - * - * @param url - */ - public static void setUrl(String url) { - connectionUrl = url; - } - - /** - * Return thread-local Connection from connection pool. - *

- * 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. - *

- * 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. - *

- * After this method returns, the database can 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. - *

- * See {@link DB#shutdown()} for warnings about connections. - *

- * 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. - *

- * Note: calls ResultSet.next() 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. - *

- * Note: calls ResultSet.next() 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. - *

- * Performs "CALL IDENTITY()" SQL statement to retrieve last value used when INSERTing into a table that has an IDENTITY column. - *

- * 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. - *

- * {@code whereClause} is SQL "WHERE" clause containing "?" placeholders suitable for use with PreparedStatements. - *

- * Example call: - *

- * {@code String manufacturer = "Lamborghini";}
- * {@code int maxMileage = 100_000;}
- * {@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; - } - -} diff --git a/src/qora/block/Block.java b/src/qora/block/Block.java index a107f573..9094afe5 100644 --- a/src/qora/block/Block.java +++ b/src/qora/block/Block.java @@ -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 transactionsData = RepositoryManager.getBlockRepository().getTransactionsFromSignature(this.blockData.getSignature()); + List 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); } } diff --git a/src/qora/block/BlockFactory.java b/src/qora/block/BlockFactory.java deleted file mode 100644 index bc5351db..00000000 --- a/src/qora/block/BlockFactory.java +++ /dev/null @@ -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 transactions = new ArrayList(); - - // 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 - -} diff --git a/src/qora/block/BlockTransaction.java b/src/qora/block/BlockTransaction.java index af30ee93..99ef0915 100644 --- a/src/qora/block/BlockTransaction.java +++ b/src/qora/block/BlockTransaction.java @@ -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; - } - } diff --git a/src/repository/BlockTransactionRepository.java b/src/repository/BlockTransactionRepository.java new file mode 100644 index 00000000..0c1fc0eb --- /dev/null +++ b/src/repository/BlockTransactionRepository.java @@ -0,0 +1,9 @@ +package repository; + +import data.block.BlockTransactionData; + +public interface BlockTransactionRepository { + + public void save(BlockTransactionData blockTransactionData) throws DataException; + +} diff --git a/src/repository/Repository.java b/src/repository/Repository.java index 9807ba84..70b5d905 100644 --- a/src/repository/Repository.java +++ b/src/repository/Repository.java @@ -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; - } } diff --git a/src/repository/RepositoryFactory.java b/src/repository/RepositoryFactory.java new file mode 100644 index 00000000..d79f4768 --- /dev/null +++ b/src/repository/RepositoryFactory.java @@ -0,0 +1,7 @@ +package repository; + +public interface RepositoryFactory { + + public Repository getRepository() throws DataException; + +} diff --git a/src/repository/RepositoryManager.java b/src/repository/RepositoryManager.java index 74260444..3b25308b 100644 --- a/src/repository/RepositoryManager.java +++ b/src/repository/RepositoryManager.java @@ -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(); } } diff --git a/src/repository/TransactionRepository.java b/src/repository/TransactionRepository.java index 0a46c5d6..b01b0ec6 100644 --- a/src/repository/TransactionRepository.java +++ b/src/repository/TransactionRepository.java @@ -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; } diff --git a/src/repository/hsqldb/HSQLDBBlockRepository.java b/src/repository/hsqldb/HSQLDBBlockRepository.java index 64336867..4ddb930c 100644 --- a/src/repository/hsqldb/HSQLDBBlockRepository.java +++ b/src/repository/hsqldb/HSQLDBBlockRepository.java @@ -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 transactions = new ArrayList(); 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); } diff --git a/src/repository/hsqldb/HSQLDBBlockTransactionRepository.java b/src/repository/hsqldb/HSQLDBBlockTransactionRepository.java new file mode 100644 index 00000000..4873209e --- /dev/null +++ b/src/repository/hsqldb/HSQLDBBlockTransactionRepository.java @@ -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); + } + } + +} diff --git a/src/database/DatabaseUpdates.java b/src/repository/hsqldb/HSQLDBDatabaseUpdates.java similarity index 94% rename from src/database/DatabaseUpdates.java rename to src/repository/hsqldb/HSQLDBDatabaseUpdates.java index d14ff6f3..a25030f2 100644 --- a/src/database/DatabaseUpdates.java +++ b/src/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -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 diff --git a/src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java b/src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java index 6b10e92f..5a97639b 100644 --- a/src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java +++ b/src/repository/hsqldb/HSQLDBGenesisTransactionRepository.java @@ -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); } diff --git a/src/repository/hsqldb/HSQLDBRepository.java b/src/repository/hsqldb/HSQLDBRepository.java index a2fdaeb2..44bfd76f 100644 --- a/src/repository/hsqldb/HSQLDBRepository.java +++ b/src/repository/hsqldb/HSQLDBRepository.java @@ -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. + *

+ * Note: calls ResultSet.next() 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. + *

+ * Note: calls ResultSet.next() 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. + *

+ * Performs "CALL IDENTITY()" SQL statement to retrieve last value used when INSERTing into a table that has an IDENTITY column. + *

+ * 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. + *

+ * {@code whereClause} is SQL "WHERE" clause containing "?" placeholders suitable for use with PreparedStatements. + *

+ * Example call: + *

+ * {@code String manufacturer = "Lamborghini";}
+ * {@code int maxMileage = 100_000;}
+ * {@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; } } diff --git a/src/repository/hsqldb/HSQLDBRepositoryFactory.java b/src/repository/hsqldb/HSQLDBRepositoryFactory.java new file mode 100644 index 00000000..16c08a3d --- /dev/null +++ b/src/repository/hsqldb/HSQLDBRepositoryFactory.java @@ -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; + } + +} diff --git a/src/repository/hsqldb/HSQLDBSaver.java b/src/repository/hsqldb/HSQLDBSaver.java index c3742aad..705ee81b 100644 --- a/src/repository/hsqldb/HSQLDBSaver.java +++ b/src/repository/hsqldb/HSQLDBSaver.java @@ -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. *

@@ -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); diff --git a/src/repository/hsqldb/HSQLDBTransactionRepository.java b/src/repository/hsqldb/HSQLDBTransactionRepository.java index 403d98af..c4e60ce2 100644 --- a/src/repository/hsqldb/HSQLDBTransactionRepository.java +++ b/src/repository/hsqldb/HSQLDBTransactionRepository.java @@ -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); } } diff --git a/src/transform/block/BlockTransformer.java b/src/transform/block/BlockTransformer.java index 2781839b..289eb48d 100644 --- a/src/transform/block/BlockTransformer.java +++ b/src/transform/block/BlockTransformer.java @@ -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; diff --git a/src/utils/Serialization.java b/src/utils/Serialization.java index 551461ac..bb308926 100644 --- a/src/utils/Serialization.java +++ b/src/utils/Serialization.java @@ -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;