SHA256
237 lines
8.3 KiB
Java
237 lines
8.3 KiB
Java
package shine.db.dao;
|
|
|
|
import shine.db.DbController;
|
|
import shine.db.entities.BlockchainStateEntry;
|
|
|
|
import java.sql.*;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
public final class BlockchainStateDAO {
|
|
|
|
private static volatile BlockchainStateDAO instance;
|
|
private final DbController db = DbController.getInstance();
|
|
|
|
private BlockchainStateDAO() {}
|
|
|
|
public static BlockchainStateDAO getInstance() {
|
|
if (instance == null) {
|
|
synchronized (BlockchainStateDAO.class) {
|
|
if (instance == null) instance = new BlockchainStateDAO();
|
|
}
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
/** Получить по blockchainName без внешнего соединения. Сам открывает/закрывает. */
|
|
public BlockchainStateEntry getByBlockchainName(String blockchainName) throws SQLException {
|
|
try (Connection c = db.getConnection()) {
|
|
return getByBlockchainName(c, blockchainName);
|
|
}
|
|
}
|
|
|
|
/** Получить по blockchainName с внешним соединением. Соединение НЕ закрывает. */
|
|
public BlockchainStateEntry getByBlockchainName(Connection c, String blockchainName) throws SQLException {
|
|
String sql = """
|
|
SELECT
|
|
blockchain_name,
|
|
login,
|
|
blockchain_key,
|
|
size_limit,
|
|
file_size_bytes,
|
|
last_block_number,
|
|
last_block_hash,
|
|
updated_at_ms
|
|
FROM blockchain_state
|
|
WHERE blockchain_name = ?
|
|
""";
|
|
|
|
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
|
ps.setString(1, blockchainName);
|
|
try (ResultSet rs = ps.executeQuery()) {
|
|
if (!rs.next()) return null;
|
|
return mapRow(rs);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Получить все blockchain_state записи. */
|
|
public List<BlockchainStateEntry> listAll() throws SQLException {
|
|
try (Connection c = db.getConnection()) {
|
|
return listAll(c);
|
|
}
|
|
}
|
|
|
|
/** Получить все blockchain_state записи с внешним соединением. Соединение НЕ закрывает. */
|
|
public List<BlockchainStateEntry> listAll(Connection c) throws SQLException {
|
|
String sql = """
|
|
SELECT
|
|
blockchain_name,
|
|
login,
|
|
blockchain_key,
|
|
size_limit,
|
|
file_size_bytes,
|
|
last_block_number,
|
|
last_block_hash,
|
|
updated_at_ms
|
|
FROM blockchain_state
|
|
ORDER BY LOWER(blockchain_name)
|
|
""";
|
|
|
|
List<BlockchainStateEntry> result = new ArrayList<>();
|
|
try (PreparedStatement ps = c.prepareStatement(sql);
|
|
ResultSet rs = ps.executeQuery()) {
|
|
while (rs.next()) {
|
|
result.add(mapRow(rs));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** UPSERT без внешнего соединения. Сам открывает/закрывает. */
|
|
public void upsert(BlockchainStateEntry e) throws SQLException {
|
|
try (Connection c = db.getConnection()) {
|
|
upsert(c, e);
|
|
}
|
|
}
|
|
|
|
/** UPSERT с внешним соединением. Соединение НЕ закрывает. */
|
|
public void upsert(Connection c, BlockchainStateEntry e) throws SQLException {
|
|
String sql = """
|
|
INSERT INTO blockchain_state (
|
|
blockchain_name,
|
|
login,
|
|
blockchain_key,
|
|
size_limit,
|
|
file_size_bytes,
|
|
last_block_number,
|
|
last_block_hash,
|
|
updated_at_ms
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT(blockchain_name)
|
|
DO UPDATE SET
|
|
login = excluded.login,
|
|
blockchain_key = excluded.blockchain_key,
|
|
size_limit = excluded.size_limit,
|
|
file_size_bytes = excluded.file_size_bytes,
|
|
last_block_number= excluded.last_block_number,
|
|
last_block_hash = excluded.last_block_hash,
|
|
updated_at_ms = excluded.updated_at_ms
|
|
""";
|
|
|
|
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
|
int i = 1;
|
|
|
|
ps.setString(i++, e.getBlockchainName());
|
|
ps.setString(i++, nn(e.getLogin()));
|
|
ps.setString(i++, nn(e.getBlockchainKey()));
|
|
|
|
ps.setLong(i++, e.getSizeLimit());
|
|
ps.setLong(i++, e.getFileSizeBytes());
|
|
|
|
ps.setInt(i++, e.getLastBlockNumber());
|
|
setBytesNullable(ps, i++, e.getLastBlockHash());
|
|
|
|
ps.setLong(i++, e.getUpdatedAtMs());
|
|
|
|
ps.executeUpdate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Строгая вставка state только если записи ещё нет.
|
|
*
|
|
* Нужна для recovery / resync:
|
|
* - runtime-проекция пользователя уже может существовать в current users слое;
|
|
* - в таком случае нам надо восстановить только blockchain_state;
|
|
* - если запись уже есть, метод просто ничего не меняет.
|
|
*/
|
|
public boolean insertIfMissing(Connection c, BlockchainStateEntry e) throws SQLException {
|
|
String sql = """
|
|
INSERT INTO blockchain_state (
|
|
blockchain_name,
|
|
login,
|
|
blockchain_key,
|
|
size_limit,
|
|
file_size_bytes,
|
|
last_block_number,
|
|
last_block_hash,
|
|
updated_at_ms
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT(blockchain_name) DO NOTHING
|
|
""";
|
|
|
|
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
|
int i = 1;
|
|
|
|
ps.setString(i++, e.getBlockchainName());
|
|
ps.setString(i++, nn(e.getLogin()));
|
|
ps.setString(i++, nn(e.getBlockchainKey()));
|
|
|
|
ps.setLong(i++, e.getSizeLimit());
|
|
ps.setLong(i++, e.getFileSizeBytes());
|
|
|
|
ps.setInt(i++, e.getLastBlockNumber());
|
|
setBytesNullable(ps, i++, e.getLastBlockHash());
|
|
|
|
ps.setLong(i++, e.getUpdatedAtMs());
|
|
|
|
return ps.executeUpdate() > 0;
|
|
}
|
|
}
|
|
|
|
public boolean insertIfMissing(BlockchainStateEntry e) throws SQLException {
|
|
try (Connection c = db.getConnection()) {
|
|
return insertIfMissing(c, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Атомарно увеличить file_size_bytes на deltaBytes, но только если НЕ превысим size_limit.
|
|
*/
|
|
public boolean tryIncreaseFileSizeWithinLimit(Connection c, String blockchainName, long deltaBytes, long nowMs) throws SQLException {
|
|
String sql = """
|
|
UPDATE blockchain_state
|
|
SET
|
|
file_size_bytes = file_size_bytes + ?,
|
|
updated_at_ms = ?
|
|
WHERE
|
|
blockchain_name = ?
|
|
AND (file_size_bytes + ?) <= size_limit
|
|
""";
|
|
|
|
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
|
ps.setLong(1, deltaBytes);
|
|
ps.setLong(2, nowMs);
|
|
ps.setString(3, blockchainName);
|
|
ps.setLong(4, deltaBytes);
|
|
return ps.executeUpdate() > 0;
|
|
}
|
|
}
|
|
|
|
private BlockchainStateEntry mapRow(ResultSet rs) throws SQLException {
|
|
BlockchainStateEntry e = new BlockchainStateEntry();
|
|
|
|
e.setBlockchainName(rs.getString("blockchain_name"));
|
|
e.setLogin(rs.getString("login"));
|
|
e.setBlockchainKey(rs.getString("blockchain_key"));
|
|
|
|
e.setSizeLimit(rs.getLong("size_limit"));
|
|
e.setFileSizeBytes(rs.getLong("file_size_bytes"));
|
|
|
|
e.setLastBlockNumber(rs.getInt("last_block_number"));
|
|
e.setLastBlockHash(rs.getBytes("last_block_hash")); // nullable
|
|
|
|
e.setUpdatedAtMs(rs.getLong("updated_at_ms"));
|
|
|
|
return e;
|
|
}
|
|
|
|
private static void setBytesNullable(PreparedStatement ps, int index, byte[] b) throws SQLException {
|
|
if (b != null) ps.setBytes(index, b);
|
|
else ps.setNull(index, Types.BINARY);
|
|
}
|
|
|
|
private static String nn(String s) { return s == null ? "" : s; }
|
|
}
|