qortal/src/database/DB.java
catbref b90a486039 More work on Blocks, refactor to using public key in DB, etc.
Added brokenmd160.java as command-line support for producing broken MD160 digests.

Transactions, and sub-classes, now use/store public key instead of Qora address.
(Qora address can be derived from public key and they take up about the same space in DB).

Loads more JavaDoc for lovely mouseover help in Eclipse IDE.

Crypto.verify() and Crypto.sign() moved into PublicKeyAccount and PrivateKeyAccount
as appropriate.

Fleshed out Block, added BlockTransactions support.

Added TODO comments as Eclipse helpfully lists these for later implementation.

Made loading-from-DB-constructors protected/private and also throw NoDataFoundException
if unable to load from DB. Public methods can call respective constructors, catch the above
exception and return null if they like. Load-from-DB-constructors are to allow sub-classes
to load some data from sub-tables and super-class to load from another table.
(See PaymentTransaction/Transaction for example). Using public methods allows similar argument
lists but with different names,
e.g. DBObject.fromSignature(Connection, byte[]) and DBObject.fromReference(Connection, byte[])

Saving into DB maybe still a bit untidy. Looking for a way to close-couple column names with
place-holder bind Objects.
Less of:
connection.prepareStatement("INSERT INTO table (column) VALUES (?)")
DB.bindInsertPlaceholders(PreparedStatement, Object...);
More like:
DB.insertUpdate(String tableName, SomeMagicCloseCoupledPairs...)
called like:
DB.insertUpdate("Cats", {"name", "Tiddles"}, {"age", 3});
2018-05-17 17:39:55 +01:00

185 lines
5.1 KiB
Java

package database;
import java.io.ByteArrayInputStream;
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.util.Arrays;
import com.google.common.primitives.Bytes;
public class DB {
public static void startTransaction(Connection c) throws SQLException {
c.prepareStatement("START TRANSACTION").execute();
}
public static void commit(Connection c) throws SQLException {
c.prepareStatement("COMMIT").execute();
}
public static void rollback(Connection c) throws SQLException {
c.prepareStatement("ROLLBACK").execute();
}
/**
* Convert InputStream, from ResultSet.getBinaryStream(), into byte[] of set length.
*
* @param inputStream
* @param length
* @return byte[length]
*/
public static byte[] getResultSetBytes(InputStream inputStream, int length) {
if (inputStream == null)
return null;
byte[] result = new byte[length];
try {
if (inputStream.read(result) == length)
return result;
} catch (IOException e) {
// Fall-through to return null
}
return null;
}
/**
* Convert InputStream, from ResultSet.getBinaryStream(), into byte[] of unknown length.
*
* @param inputStream
* @return byte[]
*/
public static byte[] getResultSetBytes(InputStream inputStream) {
final int BYTE_BUFFER_LENGTH = 1024;
if (inputStream == null)
return null;
byte[] result = new byte[0];
while (true) {
try {
byte[] buffer = new byte[BYTE_BUFFER_LENGTH];
int length = inputStream.read(buffer);
result = Bytes.concat(result, Arrays.copyOf(buffer, length));
} catch (IOException e) {
// No more bytes
break;
}
}
return result;
}
/**
* Format table and column names into an INSERT INTO ... SQL statement.
* <p>
* Full form is:
* <p>
* INSERT INTO <I>table</I> (<I>column</I>, ...) VALUES (?, ...) ON DUPLICATE KEY UPDATE <I>column</I>=?, ...
* <p>
* Note that HSQLDB needs to put into mySQL compatibility mode first via "SET DATABASE SQL SYNTAX MYS TRUE".
*
* @param table
* @param columns
* @return String
*/
public static String formatInsertWithPlaceholders(String table, String... columns) {
String[] placeholders = new String[columns.length];
Arrays.setAll(placeholders, (int i) -> "?");
StringBuilder output = new StringBuilder();
output.append("INSERT INTO ");
output.append(table);
output.append(" (");
output.append(String.join(", ", columns));
output.append(") VALUES (");
output.append(String.join(", ", placeholders));
output.append(") ON DUPLICATE KEY UPDATE ");
output.append(String.join("=?, ", columns));
output.append("=?");
return output.toString();
}
/**
* Binds Objects to PreparedStatement based on INSERT INTO ... ON DUPLICATE KEY UPDATE ...
* <p>
* Note that each object is bound to <b>two</b> place-holders based on this SQL syntax:
* <p>
* INSERT INTO <I>table</I> (<I>column</I>, ...) VALUES (<b>?</b>, ...) ON DUPLICATE KEY UPDATE <I>column</I>=<b>?</b>, ...
*
* @param preparedStatement
* @param objects
* @throws SQLException
*/
public static void bindInsertPlaceholders(PreparedStatement preparedStatement, Object... objects) throws SQLException {
for (int i = 0; i < objects.length; ++i) {
Object object = objects[i];
if (object instanceof BigDecimal) {
preparedStatement.setBigDecimal(i + 1, (BigDecimal) object);
preparedStatement.setBigDecimal(i + objects.length + 1, (BigDecimal) object);
} else {
preparedStatement.setObject(i + 1, object);
preparedStatement.setObject(i + objects.length + 1, object);
}
}
}
/**
* Execute SQL using byte[] as 1st placeholder
* <p>
* Typically used to fetch Blocks or Transactions using signature or reference.
* <p>
*
* @param connection
* @param sql
* @param bytes
* @return ResultSet, or null if no matching rows found
* @throws SQLException
*/
public static ResultSet executeUsingBytes(Connection connection, String sql, byte[] bytes) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setBinaryStream(1, new ByteArrayInputStream(bytes));
if (!preparedStatement.execute())
throw new SQLException("Fetching from database produced no results");
ResultSet rs = preparedStatement.getResultSet();
if (rs == null)
throw new SQLException("Fetching results from database produced no ResultSet");
if (!rs.next())
return null;
return rs;
}
/**
* Execute PreparedStatement and return ResultSet with but added checking
*
* @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;
}
}