Ну типо переделал Всё под короткую таблицу солана юзерс, но теперь не надо поправить баги
This commit is contained in:
AidarKC
2025-12-30 12:39:55 +03:00
parent b6b50557a7
commit 34e8640e78
11 changed files with 439 additions and 182 deletions
@@ -34,7 +34,7 @@ public final class BlockchainStateDAO {
SELECT
blockchainName,
login,
public_key_base64,
blockchainKey,
size_limit,
file_size_bytes,
last_global_number,
@@ -81,7 +81,7 @@ public final class BlockchainStateDAO {
INSERT INTO blockchain_state (
blockchainName,
login,
public_key_base64,
blockchainKey,
size_limit,
file_size_bytes,
last_global_number,
@@ -109,7 +109,7 @@ public final class BlockchainStateDAO {
ON CONFLICT(blockchainName)
DO UPDATE SET
login = excluded.login,
public_key_base64 = excluded.public_key_base64,
blockchainKey = excluded.blockchainKey,
size_limit = excluded.size_limit,
file_size_bytes = excluded.file_size_bytes,
last_global_number = excluded.last_global_number,
@@ -138,7 +138,7 @@ public final class BlockchainStateDAO {
ps.setString(i++, e.getBlockchainName());
ps.setString(i++, nn(e.getLogin()));
ps.setString(i++, nn(e.getPublicKeyBase64()));
ps.setString(i++, nn(e.getBlockchainKey()));
ps.setLong(i++, e.getSizeLimit());
ps.setLong(i++, e.getFileSizeBytes());
@@ -156,12 +156,55 @@ public final class BlockchainStateDAO {
}
}
/**
* Атомарно увеличить file_size_bytes на deltaBytes, но только если НЕ превысим size_limit.
*
* Возвращает:
* - true если обновили (лимит не превышен)
* - false если лимит превышается или blockchainName не найден
*
* ВАЖНО: это именно тот механизм, который надо дергать при добавлении блока.
*/
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
blockchainName = ?
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);
int updated = ps.executeUpdate();
return updated > 0;
}
}
/** Удобная проверка для HEADER: запись должна быть и last_global_number должен быть -1. */
public BlockchainStateEntry requireExistingAtGenesis(Connection c, String blockchainName) throws SQLException {
BlockchainStateEntry st = getByBlockchainName(c, blockchainName);
if (st == null) {
throw new IllegalStateException("Blockchain state not found for blockchainName=" + blockchainName);
}
if (st.getLastGlobalNumber() != -1) {
throw new IllegalStateException("Blockchain state is not at genesis (-1). blockchainName=" + blockchainName +
" last_global_number=" + st.getLastGlobalNumber());
}
return st;
}
private BlockchainStateEntry mapRow(ResultSet rs) throws SQLException {
BlockchainStateEntry e = new BlockchainStateEntry();
e.setBlockchainName(rs.getString("blockchainName"));
e.setLogin(rs.getString("login"));
e.setPublicKeyBase64(rs.getString("public_key_base64"));
e.setBlockchainKey(rs.getString("blockchainKey"));
// size_limit теперь long
e.setSizeLimit(rs.getLong("size_limit"));
@@ -0,0 +1,53 @@
package shine.db.dao;
import shine.db.SqliteDbController;
import shine.db.entities.BlockchainStateEntry;
import java.sql.*;
/**
* SolanaBlockchainsDAO — таблица блокчейнов пользователя.
*
* Сейчас физически это blockchain_state, потому что:
* - у одного login может быть несколько blockchainName
* - у каждого blockchainName свой blockchainKey и size_limit
*
* Правило:
* - методы с Connection НЕ закрывают соединение
* - методы без Connection сами открывают и закрывают соединение
*/
public final class SolanaBlockchainsDAO {
private static volatile SolanaBlockchainsDAO instance;
private final SqliteDbController db = SqliteDbController.getInstance();
private final BlockchainStateDAO stateDao = BlockchainStateDAO.getInstance();
private SolanaBlockchainsDAO() {}
public static SolanaBlockchainsDAO getInstance() {
if (instance == null) {
synchronized (SolanaBlockchainsDAO.class) {
if (instance == null) instance = new SolanaBlockchainsDAO();
}
}
return instance;
}
public BlockchainStateEntry getByBlockchainName(String blockchainName) throws SQLException {
return stateDao.getByBlockchainName(blockchainName);
}
public BlockchainStateEntry getByBlockchainName(Connection c, String blockchainName) throws SQLException {
return stateDao.getByBlockchainName(c, blockchainName);
}
/** Для HEADER: проверка, что blockchain_state существует и last_global_number=-1. */
public BlockchainStateEntry requireExistingAtGenesis(Connection c, String blockchainName) throws SQLException {
return stateDao.requireExistingAtGenesis(c, blockchainName);
}
/** Для добавления блока: атомарная проверка лимита + увеличение размера файла. */
public boolean tryIncreaseFileSizeWithinLimit(Connection c, String blockchainName, long deltaBytes, long nowMs) throws SQLException {
return stateDao.tryIncreaseFileSizeWithinLimit(c, blockchainName, deltaBytes, nowMs);
}
}
@@ -42,19 +42,13 @@ public final class SolanaUsersDAO {
/** Вставка с внешним соединением. Соединение НЕ закрывает. */
public void insert(Connection c, SolanaUserEntry user) throws SQLException {
String sql = """
INSERT INTO solana_users (login, bchName, loginKey, deviceKey, bchLimit)
VALUES (?, ?, ?, ?, ?)
INSERT INTO solana_users (login, deviceKey)
VALUES (?, ?)
""";
try (PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, user.getLogin());
ps.setString(2, user.getBchName());
ps.setString(3, user.getLoginKey());
ps.setString(4, user.getDeviceKey());
if (user.getBchLimit() != null) ps.setInt(5, user.getBchLimit());
else ps.setNull(5, Types.INTEGER);
ps.setString(2, user.getDeviceKey());
ps.executeUpdate();
}
}
@@ -97,7 +91,7 @@ public final class SolanaUsersDAO {
/** Получить по login (case-insensitive) с внешним соединением. Соединение НЕ закрывает. */
public SolanaUserEntry getByLogin(Connection c, String login) throws SQLException {
String sql = """
SELECT login, bchName, loginKey, deviceKey, bchLimit
SELECT login, deviceKey
FROM solana_users
WHERE LOWER(login) = LOWER(?)
""";
@@ -121,7 +115,7 @@ public final class SolanaUsersDAO {
/** Поиск по префиксу с внешним соединением. Соединение НЕ закрывает. */
public List<SolanaUserEntry> searchByLoginPrefix(Connection c, String prefix) throws SQLException {
String sql = """
SELECT login, bchName, loginKey, deviceKey, bchLimit
SELECT login, deviceKey
FROM solana_users
WHERE LOWER(login) LIKE ?
ORDER BY login
@@ -152,10 +146,7 @@ public final class SolanaUsersDAO {
private SolanaUserEntry mapRow(ResultSet rs) throws SQLException {
return new SolanaUserEntry(
rs.getString("login"),
rs.getString("bchName"),
rs.getString("loginKey"),
rs.getString("deviceKey"),
rs.getObject("bchLimit") != null ? rs.getInt("bchLimit") : null
rs.getString("deviceKey")
);
}
}
@@ -0,0 +1,102 @@
package shine.db.dao;
import shine.db.SqliteDbController;
import shine.db.entities.BlockchainStateEntry;
import shine.db.entities.SolanaUserEntry;
import java.sql.*;
/**
* UserCreateDAO — атомарное добавление пользователя:
* - solana_users (login, deviceKey)
* - blockchain_state (blockchainName, login, blockchainKey, size_limit, ... last_global_number=-1 ...)
*
* ВАЖНО:
* - только INSERT
* - если login или blockchainName заняты — возвращаем false (пользователь уже есть/занято)
*/
public final class UserCreateDAO {
private static volatile UserCreateDAO instance;
private final SqliteDbController db = SqliteDbController.getInstance();
private final SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance();
private final BlockchainStateDAO stateDao = BlockchainStateDAO.getInstance();
private UserCreateDAO() {}
public static UserCreateDAO getInstance() {
if (instance == null) {
synchronized (UserCreateDAO.class) {
if (instance == null) instance = new UserCreateDAO();
}
}
return instance;
}
/**
* @return true если добавили; false если занято (login уже есть или blockchainName уже существует).
*/
public boolean insertUserWithBlockchain(
String login,
String deviceKey,
String blockchainName,
String blockchainKey,
long sizeLimit,
long nowMs
) throws SQLException {
try (Connection c = db.getConnection()) {
boolean oldAuto = c.getAutoCommit();
c.setAutoCommit(false);
// BEGIN IMMEDIATE — чтобы сразу взять write-lock и не ловить гонки
try (Statement st = c.createStatement()) {
st.execute("BEGIN IMMEDIATE");
}
try {
// 1) user
SolanaUserEntry u = new SolanaUserEntry(login, deviceKey);
usersDao.insert(c, u); // если login занят -> constraint
// 2) blockchain_state
BlockchainStateEntry st = new BlockchainStateEntry();
st.setBlockchainName(blockchainName);
st.setLogin(login);
st.setBlockchainKey(blockchainKey);
st.setSizeLimit(sizeLimit);
st.setFileSizeBytes(0L);
// старт: глобальных блоков ещё нет
st.setLastGlobalNumber(-1);
st.setLastGlobalHash("");
for (int line = 0; line < 8; line++) {
st.setLastLineNumber(line, 0);
st.setLastLineHash(line, "");
}
st.setUpdatedAtMs(nowMs);
stateDao.upsert(c, st); // если blockchainName занят -> constraint (PK)
c.commit();
return true;
} catch (SQLException e) {
c.rollback();
// SQLITE_CONSTRAINT -> "уже существует"
// Мы не делаем UPDATE, только insert.
String msg = e.getMessage() == null ? "" : e.getMessage().toLowerCase();
if (msg.contains("constraint")) {
return false;
}
throw e;
} finally {
c.setAutoCommit(oldAuto);
}
}
}
}