mirror of
https://github.com/magefree/mage.git
synced 2025-12-28 14:32:06 -08:00
server: database improves:
- fixed broken database in some use cases (example: AI and choose name dialog, related to #11285); - added docs and debug tools for sql queries, caches and memory analyse (see DebugUtil); - refactor code to use shared settings; - deleted outdated and un-used code (db logs, stats, etc);
This commit is contained in:
parent
c448612c97
commit
bf3f26ccc1
18 changed files with 376 additions and 394 deletions
|
|
@ -2,6 +2,7 @@ package mage.cards.repository;
|
|||
|
||||
import com.j256.ormlite.dao.Dao;
|
||||
import com.j256.ormlite.dao.DaoManager;
|
||||
import com.j256.ormlite.dao.GenericRawResults;
|
||||
import com.j256.ormlite.jdbc.JdbcConnectionSource;
|
||||
import com.j256.ormlite.stmt.QueryBuilder;
|
||||
import com.j256.ormlite.stmt.SelectArg;
|
||||
|
|
@ -19,6 +20,7 @@ import java.io.File;
|
|||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author North, JayDi85
|
||||
|
|
@ -31,16 +33,15 @@ public enum CardRepository {
|
|||
|
||||
// fixes limit for out of memory problems
|
||||
private static final AtomicInteger databaseFixes = new AtomicInteger();
|
||||
private static final int MAX_DATABASE_FIXES = 3;
|
||||
|
||||
private static final String JDBC_URL = "jdbc:h2:file:./db/cards.h2;AUTO_SERVER=TRUE;IGNORECASE=TRUE";
|
||||
private static final int MAX_DATABASE_FIXES = 10;
|
||||
|
||||
// TODO: delete db version from cards and expansions due un-used (cause dbs re-created on each update now)
|
||||
private static final String VERSION_ENTITY_NAME = "card";
|
||||
// raise this if db structure was changed
|
||||
private static final long CARD_DB_VERSION = 54;
|
||||
// raise this if new cards were added to the server
|
||||
private static final long CARD_CONTENT_VERSION = 241;
|
||||
private Dao<CardInfo, Object> cardDao;
|
||||
private Set<String> classNames;
|
||||
private static final long CARD_DB_VERSION = 54; // raise this if db structure was changed
|
||||
private static final long CARD_CONTENT_VERSION = 241; // raise this if new cards were added to the server
|
||||
|
||||
private Dao<CardInfo, Object> cardsDao;
|
||||
|
||||
// sets with exclusively snow basics
|
||||
public static final Set<String> snowLandSetCodes = new HashSet<>(Arrays.asList(
|
||||
|
|
@ -55,7 +56,7 @@ public enum CardRepository {
|
|||
file.mkdirs();
|
||||
}
|
||||
try {
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL);
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_CARDS, true));
|
||||
|
||||
boolean isObsolete = RepositoryUtil.isDatabaseObsolete(connectionSource, VERSION_ENTITY_NAME, CARD_DB_VERSION);
|
||||
boolean isNewBuild = RepositoryUtil.isNewBuildRun(connectionSource, VERSION_ENTITY_NAME, CardRepository.class); // recreate db on new build
|
||||
|
|
@ -65,7 +66,7 @@ public enum CardRepository {
|
|||
}
|
||||
|
||||
TableUtils.createTableIfNotExists(connectionSource, CardInfo.class);
|
||||
cardDao = DaoManager.createDao(connectionSource, CardInfo.class);
|
||||
cardsDao = DaoManager.createDao(connectionSource, CardInfo.class);
|
||||
} catch (SQLException e) {
|
||||
Logger.getLogger(CardRepository.class).error("Error creating card repository - " + e, e);
|
||||
processMemoryErrors(e);
|
||||
|
|
@ -95,26 +96,22 @@ public enum CardRepository {
|
|||
}
|
||||
|
||||
public void saveCards(final List<CardInfo> newCards, long newContentVersion) {
|
||||
if (newCards == null || newCards.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
cardDao.callBatchTasks(() -> {
|
||||
// add
|
||||
if (newCards != null && !newCards.isEmpty()) {
|
||||
logger.info("DB: need to add " + newCards.size() + " new cards");
|
||||
try {
|
||||
for (CardInfo card : newCards) {
|
||||
cardDao.create(card);
|
||||
if (classNames != null) {
|
||||
classNames.add(card.getClassName());
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
Logger.getLogger(CardRepository.class).error("Error adding cards to DB - " + e, e);
|
||||
processMemoryErrors(e);
|
||||
cardsDao.callBatchTasks(() -> {
|
||||
// only add new cards (no updates)
|
||||
logger.info("DB: need to add " + newCards.size() + " new cards");
|
||||
try {
|
||||
for (CardInfo card : newCards) {
|
||||
cardsDao.create(card);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
Logger.getLogger(CardRepository.class).error("Error adding cards to DB - " + e, e);
|
||||
processMemoryErrors(e);
|
||||
}
|
||||
|
||||
// no card updates
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
|
|
@ -158,9 +155,9 @@ public enum CardRepository {
|
|||
public Set<String> getNames() {
|
||||
Set<String> names = new TreeSet<>();
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
|
||||
List<CardInfo> results = cardDao.query(qb.prepare());
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
addNewNames(card, names);
|
||||
}
|
||||
|
|
@ -174,10 +171,10 @@ public enum CardRepository {
|
|||
public Set<String> getNonLandNames() {
|
||||
Set<String> names = new TreeSet<>();
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
|
||||
qb.where().not().like("types", new SelectArg('%' + CardType.LAND.name() + '%'));
|
||||
List<CardInfo> results = cardDao.query(qb.prepare());
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
addNewNames(card, names);
|
||||
}
|
||||
|
|
@ -191,14 +188,14 @@ public enum CardRepository {
|
|||
public Set<String> getNonbasicLandNames() {
|
||||
Set<String> names = new TreeSet<>();
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
|
||||
Where<CardInfo, Object> where = qb.where();
|
||||
where.and(
|
||||
where.not().like("supertypes", '%' + SuperType.BASIC.name() + '%'),
|
||||
where.like("types", '%' + CardType.LAND.name() + '%')
|
||||
);
|
||||
List<CardInfo> results = cardDao.query(qb.prepare());
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
addNewNames(card, names);
|
||||
}
|
||||
|
|
@ -212,10 +209,10 @@ public enum CardRepository {
|
|||
public Set<String> getNotBasicLandNames() {
|
||||
Set<String> names = new TreeSet<>();
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
|
||||
qb.where().not().like("supertypes", new SelectArg('%' + SuperType.BASIC.name() + '%'));
|
||||
List<CardInfo> results = cardDao.query(qb.prepare());
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
addNewNames(card, names);
|
||||
}
|
||||
|
|
@ -229,10 +226,10 @@ public enum CardRepository {
|
|||
public Set<String> getCreatureNames() {
|
||||
Set<String> names = new TreeSet<>();
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
|
||||
qb.where().like("types", new SelectArg('%' + CardType.CREATURE.name() + '%'));
|
||||
List<CardInfo> results = cardDao.query(qb.prepare());
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
addNewNames(card, names);
|
||||
}
|
||||
|
|
@ -246,10 +243,10 @@ public enum CardRepository {
|
|||
public Set<String> getArtifactNames() {
|
||||
Set<String> names = new TreeSet<>();
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
|
||||
qb.where().like("types", new SelectArg('%' + CardType.ARTIFACT.name() + '%'));
|
||||
List<CardInfo> results = cardDao.query(qb.prepare());
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
addNewNames(card, names);
|
||||
}
|
||||
|
|
@ -263,14 +260,14 @@ public enum CardRepository {
|
|||
public Set<String> getNonLandAndNonCreatureNames() {
|
||||
Set<String> names = new TreeSet<>();
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
|
||||
Where<CardInfo, Object> where = qb.where();
|
||||
where.and(
|
||||
where.not().like("types", '%' + CardType.CREATURE.name() + '%'),
|
||||
where.not().like("types", '%' + CardType.LAND.name() + '%')
|
||||
);
|
||||
List<CardInfo> results = cardDao.query(qb.prepare());
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
addNewNames(card, names);
|
||||
}
|
||||
|
|
@ -284,14 +281,14 @@ public enum CardRepository {
|
|||
public Set<String> getNonArtifactAndNonLandNames() {
|
||||
Set<String> names = new TreeSet<>();
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
|
||||
Where<CardInfo, Object> where = qb.where();
|
||||
where.and(
|
||||
where.not().like("types", '%' + CardType.ARTIFACT.name() + '%'),
|
||||
where.not().like("types", '%' + CardType.LAND.name() + '%')
|
||||
);
|
||||
List<CardInfo> results = cardDao.query(qb.prepare());
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
addNewNames(card, names);
|
||||
}
|
||||
|
|
@ -308,7 +305,7 @@ public enum CardRepository {
|
|||
|
||||
public CardInfo findCard(String setCode, String cardNumber, boolean ignoreNightCards) {
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> queryBuilder = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> queryBuilder = cardsDao.queryBuilder();
|
||||
if (ignoreNightCards) {
|
||||
queryBuilder.limit(1L).where()
|
||||
.eq("setCode", new SelectArg(setCode))
|
||||
|
|
@ -323,7 +320,7 @@ public enum CardRepository {
|
|||
// (example: vow - 65 - Jacob Hauken, Inspector), so make priority for main side first
|
||||
queryBuilder.orderBy("nightCard", true);
|
||||
}
|
||||
List<CardInfo> result = cardDao.query(queryBuilder.prepare());
|
||||
List<CardInfo> result = cardsDao.query(queryBuilder.prepare());
|
||||
if (!result.isEmpty()) {
|
||||
return result.get(0);
|
||||
}
|
||||
|
|
@ -337,7 +334,7 @@ public enum CardRepository {
|
|||
public List<String> getClassNames() {
|
||||
List<String> names = new ArrayList<>();
|
||||
try {
|
||||
List<CardInfo> results = cardDao.queryForAll();
|
||||
List<CardInfo> results = cardsDao.queryForAll();
|
||||
for (CardInfo card : results) {
|
||||
names.add(card.getClassName());
|
||||
}
|
||||
|
|
@ -350,10 +347,10 @@ public enum CardRepository {
|
|||
|
||||
public List<CardInfo> getMissingCards(List<String> classNames) {
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> queryBuilder = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> queryBuilder = cardsDao.queryBuilder();
|
||||
queryBuilder.where().not().in("className", classNames);
|
||||
|
||||
return cardDao.query(queryBuilder.prepare());
|
||||
return cardsDao.query(queryBuilder.prepare());
|
||||
} catch (SQLException e) {
|
||||
Logger.getLogger(CardRepository.class).error("Error getting missing cards from DB: " + e, e);
|
||||
processMemoryErrors(e);
|
||||
|
|
@ -424,12 +421,12 @@ public enum CardRepository {
|
|||
* Used for building cubes, packs, and for ensuring that dual faces and split cards have sides/halves from
|
||||
* the same set and variant art.
|
||||
*
|
||||
* @param name name of the card, or side of the card, to find
|
||||
* @param expansion the set name from which to find the card
|
||||
* @param cardNumber the card number for variant arts in one set
|
||||
* @param returnSplitCardHalf whether to return a half of a split card or the corresponding full card.
|
||||
* Want this `false` when user is searching by either names in a split card so that
|
||||
* the full card can be found by either name.
|
||||
* @param name name of the card, or side of the card, to find
|
||||
* @param expansion the set name from which to find the card
|
||||
* @param cardNumber the card number for variant arts in one set
|
||||
* @param returnSplitCardHalf whether to return a half of a split card or the corresponding full card.
|
||||
* Want this `false` when user is searching by either names in a split card so that
|
||||
* the full card can be found by either name.
|
||||
* @return
|
||||
*/
|
||||
public CardInfo findCardWithPreferredSetAndNumber(String name, String expansion, String cardNumber, boolean returnSplitCardHalf) {
|
||||
|
|
@ -461,27 +458,27 @@ public enum CardRepository {
|
|||
* Find a card's reprints from all sets.
|
||||
* It allows for cards to be searched by their full name, or in the case of multi-name cards of the type "A // B"
|
||||
* To search for them using "A", "B", or "A // B".
|
||||
*
|
||||
* <p>
|
||||
* Note of how the function works:
|
||||
* Out of all card types (Split, MDFC, Adventure, Flip, Transform)
|
||||
* ONLY Split cards (Fire // Ice) MUST be queried in the DB by the full name when querying by "name".
|
||||
* Searching for it by either half will return an incorrect result.
|
||||
* ALL the others MUST be queried for by the first half of their full name (i.e. "A" from "A // B")
|
||||
* when querying by "name".
|
||||
* Out of all card types (Split, MDFC, Adventure, Flip, Transform)
|
||||
* ONLY Split cards (Fire // Ice) MUST be queried in the DB by the full name when querying by "name".
|
||||
* Searching for it by either half will return an incorrect result.
|
||||
* ALL the others MUST be queried for by the first half of their full name (i.e. "A" from "A // B")
|
||||
* when querying by "name".
|
||||
*
|
||||
* @param name the name of the card to search for
|
||||
* @param limitByMaxAmount return max amount of different cards (if 0 then return card from all sets)
|
||||
* @param returnSplitCardHalf whether to return a half of a split card or the corresponding full card.
|
||||
* Want this `false` when user is searching by either names in a split card so that
|
||||
* the full card can be found by either name.
|
||||
* Want this `true` when the client is searching for info on both halves to display it.
|
||||
* @canCheckDatabaseHealth try to fix database on any errors (use true anytime except fix methods itself)
|
||||
* @return a list of the reprints of the card if it was found (up to limitByMaxAmount number),
|
||||
* or an empty list if the card was not found.
|
||||
* @param name the name of the card to search for
|
||||
* @param limitByMaxAmount return max amount of different cards (if 0 then return card from all sets)
|
||||
* @param returnSplitCardHalf whether to return a half of a split card or the corresponding full card.
|
||||
* Want this `false` when user is searching by either names in a split card so that
|
||||
* the full card can be found by either name.
|
||||
* Want this `true` when the client is searching for info on both halves to display it.
|
||||
* @return a list of the reprints of the card if it was found (up to limitByMaxAmount number),
|
||||
* or an empty list if the card was not found.
|
||||
* @canCheckDatabaseHealth try to fix database on any errors (use true anytime except fix methods itself)
|
||||
*/
|
||||
public List<CardInfo> findCards(String name, long limitByMaxAmount, boolean returnSplitCardHalf, boolean canCheckDatabaseHealth) {
|
||||
List<CardInfo> results;
|
||||
QueryBuilder<CardInfo, Object> queryBuilder = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> queryBuilder = cardsDao.queryBuilder();
|
||||
if (limitByMaxAmount > 0) {
|
||||
queryBuilder.limit(limitByMaxAmount);
|
||||
}
|
||||
|
|
@ -492,27 +489,27 @@ public enum CardRepository {
|
|||
// Could be made faster by searching assuming it's NOT a split card and first searching by the first
|
||||
// half of the name, but this is easier to understand.
|
||||
queryBuilder.where().eq("name", new SelectArg(name));
|
||||
results = cardDao.query(queryBuilder.prepare());
|
||||
results = cardsDao.query(queryBuilder.prepare());
|
||||
|
||||
// Result comes back empty, try to search using the first half (could be Adventure, MDFC, etc.)
|
||||
if (results.isEmpty()) {
|
||||
String mainCardName = name.split(" // ", 2)[0];
|
||||
queryBuilder.where().eq("name", new SelectArg(mainCardName));
|
||||
results = cardDao.query(queryBuilder.prepare()); // If still empty, then card can't be found
|
||||
results = cardsDao.query(queryBuilder.prepare()); // If still empty, then card can't be found
|
||||
}
|
||||
} else { // Cannot tell if string represents the full name of a card or only part of it.
|
||||
// Assume it is the full card name
|
||||
queryBuilder.where().eq("name", new SelectArg(name));
|
||||
results = cardDao.query(queryBuilder.prepare());
|
||||
results = cardsDao.query(queryBuilder.prepare());
|
||||
|
||||
if (results.isEmpty()) {
|
||||
// Nothing found when looking for main name, try looking under the other names
|
||||
queryBuilder.where()
|
||||
.eq("flipCardName", new SelectArg(name)).or()
|
||||
.eq("secondSideName", new SelectArg(name)).or()
|
||||
.eq("adventureSpellName", new SelectArg(name)).or()
|
||||
.eq("modalDoubleFacedSecondSideName", new SelectArg(name));
|
||||
results = cardDao.query(queryBuilder.prepare());
|
||||
.eq("flipCardName", new SelectArg(name)).or()
|
||||
.eq("secondSideName", new SelectArg(name)).or()
|
||||
.eq("adventureSpellName", new SelectArg(name)).or()
|
||||
.eq("modalDoubleFacedSecondSideName", new SelectArg(name));
|
||||
results = cardsDao.query(queryBuilder.prepare());
|
||||
} else {
|
||||
// Check that a full card was found and not a SplitCardHalf
|
||||
// Can be caused by searching for "Fire" instead of "Fire // Ice"
|
||||
|
|
@ -522,7 +519,7 @@ public enum CardRepository {
|
|||
queryBuilder.where()
|
||||
.eq("setCode", new SelectArg(firstCardInfo.setCode)).and()
|
||||
.eq("cardNumber", new SelectArg(firstCardInfo.cardNumber));
|
||||
List<CardInfo> tmpResults = cardDao.query(queryBuilder.prepare());
|
||||
List<CardInfo> tmpResults = cardsDao.query(queryBuilder.prepare());
|
||||
|
||||
String fullSplitCardName = null;
|
||||
for (CardInfo cardInfo : tmpResults) {
|
||||
|
|
@ -536,7 +533,7 @@ public enum CardRepository {
|
|||
}
|
||||
|
||||
queryBuilder.where().eq("name", new SelectArg(fullSplitCardName));
|
||||
results = cardDao.query(queryBuilder.prepare());
|
||||
results = cardsDao.query(queryBuilder.prepare());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -557,9 +554,9 @@ public enum CardRepository {
|
|||
|
||||
public List<CardInfo> findCardsByClass(String canonicalClassName) {
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> queryBuilder = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> queryBuilder = cardsDao.queryBuilder();
|
||||
queryBuilder.where().eq("className", new SelectArg(canonicalClassName));
|
||||
return cardDao.query(queryBuilder.prepare());
|
||||
return cardsDao.query(queryBuilder.prepare());
|
||||
} catch (SQLException e) {
|
||||
Logger.getLogger(CardRepository.class).error("Error during execution of raw sql statement" + e, e);
|
||||
processMemoryErrors(e);
|
||||
|
|
@ -570,7 +567,7 @@ public enum CardRepository {
|
|||
/**
|
||||
* Warning, don't use db functions in card's code - it generates heavy db loading in AI simulations. If you
|
||||
* need that feature then check for simulation mode. See https://github.com/magefree/mage/issues/7014
|
||||
*
|
||||
* <p>
|
||||
* Ignoring night cards by default
|
||||
*
|
||||
* @param criteria
|
||||
|
|
@ -578,10 +575,10 @@ public enum CardRepository {
|
|||
*/
|
||||
public List<CardInfo> findCards(CardCriteria criteria) {
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> queryBuilder = cardDao.queryBuilder();
|
||||
QueryBuilder<CardInfo, Object> queryBuilder = cardsDao.queryBuilder();
|
||||
criteria.buildQuery(queryBuilder);
|
||||
|
||||
return cardDao.query(queryBuilder.prepare());
|
||||
return cardsDao.query(queryBuilder.prepare());
|
||||
} catch (SQLException e) {
|
||||
Logger.getLogger(CardRepository.class).error("Error during execution of card repository query statement: " + e, e);
|
||||
processMemoryErrors(e);
|
||||
|
|
@ -622,7 +619,7 @@ public enum CardRepository {
|
|||
|
||||
public long getContentVersionFromDB() {
|
||||
try {
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL);
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_CARDS, false));
|
||||
return RepositoryUtil.getDatabaseVersion(connectionSource, VERSION_ENTITY_NAME + "Content");
|
||||
} catch (SQLException e) {
|
||||
Logger.getLogger(CardRepository.class).error("Error getting content version from DB - " + e, e);
|
||||
|
|
@ -633,7 +630,7 @@ public enum CardRepository {
|
|||
|
||||
public void setContentVersion(long version) {
|
||||
try {
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL);
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_CARDS, false));
|
||||
RepositoryUtil.updateVersion(connectionSource, VERSION_ENTITY_NAME + "Content", version);
|
||||
} catch (SQLException e) {
|
||||
Logger.getLogger(CardRepository.class).error("Error setting content version - " + e, e);
|
||||
|
|
@ -645,25 +642,84 @@ public enum CardRepository {
|
|||
return CARD_CONTENT_VERSION;
|
||||
}
|
||||
|
||||
public void closeDB() {
|
||||
public void closeDB(boolean writeCompact) {
|
||||
try {
|
||||
if (cardDao != null && cardDao.getConnectionSource() != null) {
|
||||
DatabaseConnection conn = cardDao.getConnectionSource().getReadWriteConnection(cardDao.getTableName());
|
||||
conn.executeStatement("shutdown compact", 0);
|
||||
if (cardsDao != null && cardsDao.getConnectionSource() != null) {
|
||||
DatabaseConnection conn = cardsDao.getConnectionSource().getReadWriteConnection(cardsDao.getTableName());
|
||||
if (writeCompact) {
|
||||
conn.executeStatement("SHUTDOWN COMPACT", 0); // compact data and rewrite whole db
|
||||
} else {
|
||||
conn.executeStatement("SHUTDOWN IMMEDIATELY", 0); // close without any writes
|
||||
}
|
||||
cardsDao.getConnectionSource().releaseConnection(conn);
|
||||
}
|
||||
} catch (SQLException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
private void openDB() {
|
||||
public void openDB() {
|
||||
try {
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL);
|
||||
cardDao = DaoManager.createDao(connectionSource, CardInfo.class);
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_CARDS, true));
|
||||
cardsDao = DaoManager.createDao(connectionSource, CardInfo.class);
|
||||
} catch (SQLException e) {
|
||||
Logger.getLogger(CardRepository.class).error("Error opening card repository - " + e, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void printDatabaseStats(String info) {
|
||||
List<List<String>> allSettings = querySQL("SELECT NAME, VALUE FROM INFORMATION_SCHEMA.SETTINGS");
|
||||
if (allSettings == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// cache
|
||||
logger.info("Database cache settings (" + info + "):");
|
||||
allSettings.stream().filter(values -> values.get(0).equals("CACHE_SIZE")).forEach(values -> {
|
||||
logger.info(" - cache size, setup: " + values.get(1) + " kb");
|
||||
});
|
||||
allSettings.stream().filter(values -> values.get(0).equals("info.CACHE_MAX_SIZE")).forEach(values -> {
|
||||
logger.info(" - cache size, max: " + values.get(1) + " mb");
|
||||
});
|
||||
allSettings.stream().filter(values -> values.get(0).equals("info.CACHE_SIZE")).forEach(values -> {
|
||||
logger.info(" - cache size, current: " + values.get(1) + " mb");
|
||||
});
|
||||
|
||||
// memory
|
||||
allSettings = querySQL("SELECT MEMORY_FREE(), MEMORY_USED()");
|
||||
if (allSettings == null) {
|
||||
return;
|
||||
}
|
||||
logger.info("Database memory stats (" + info + "):");
|
||||
logger.info(" - free: " + allSettings.get(0).get(0) + " kb");
|
||||
logger.info(" - used: " + allSettings.get(0).get(1) + " kb");
|
||||
}
|
||||
|
||||
/**
|
||||
* Exec any SQL query and return result table as string values
|
||||
*/
|
||||
public List<List<String>> querySQL(String sql) {
|
||||
try {
|
||||
GenericRawResults<String[]> query = cardsDao.queryRaw(sql);
|
||||
return query.getResults().stream()
|
||||
.map(Arrays::asList)
|
||||
.collect(Collectors.toList());
|
||||
} catch (SQLException e) {
|
||||
logger.error("Can't query sql due error: " + sql + " - " + e, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exec any SQL code without result. Can be used to change db settings like SET xxx = YYY
|
||||
*/
|
||||
public void execSQL(String sql) {
|
||||
try {
|
||||
cardsDao.executeRaw(sql);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Can't exec sql due error: " + sql + " - " + e, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static CardInfo safeFindKnownCard() {
|
||||
// safe find of known card with memory/db fixes
|
||||
return instance.findCards("Silvercoat Lion", 1, false, false)
|
||||
|
|
@ -690,7 +746,7 @@ public enum CardRepository {
|
|||
}
|
||||
|
||||
// DB seems to have a problem - try to restart the DB (useless in 99% due out of memory problems)
|
||||
instance.closeDB();
|
||||
instance.closeDB(false);
|
||||
instance.openDB();
|
||||
cardInfo = safeFindKnownCard();
|
||||
if (cardInfo != null) {
|
||||
|
|
|
|||
69
Mage/src/main/java/mage/cards/repository/DatabaseUtils.java
Normal file
69
Mage/src/main/java/mage/cards/repository/DatabaseUtils.java
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package mage.cards.repository;
|
||||
|
||||
import mage.util.DebugUtil;
|
||||
|
||||
/**
|
||||
* Helper class for database
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class DatabaseUtils {
|
||||
|
||||
// warning, do not change names or db format
|
||||
// h2
|
||||
public static final String DB_NAME_FEEDBACK = "feedback.h2";
|
||||
public static final String DB_NAME_USERS = "authorized_user.h2";
|
||||
public static final String DB_NAME_CARDS = "cards.h2";
|
||||
// sqlite (usage reason: h2 database works bad with 1GB+ files and can break it)
|
||||
public static final String DB_NAME_RECORDS = "table_record.db";
|
||||
public static final String DB_NAME_STATS = "user_stats.db";
|
||||
|
||||
/**
|
||||
* Prepare JDBC connection string and setup additional params for H2 databases
|
||||
*
|
||||
* @param dbName database name like "cards.h2"
|
||||
* @param improveCaches use memory optimizations for cards database (no needs for other dbs)
|
||||
*/
|
||||
public static String prepareH2Connection(String dbName, boolean improveCaches) {
|
||||
// example: jdbc:h2:file:./db/cards.h2;AUTO_SERVER=TRUE;IGNORECASE=TRUE
|
||||
String res = String.format("jdbc:h2:file:./db/%s", dbName);
|
||||
|
||||
// shared params
|
||||
res += ";AUTO_SERVER=TRUE"; // open database in mix mode (first open by new thread, second open by new jvm-process)
|
||||
res += ";IGNORECASE=TRUE"; // ignore char case for text searching
|
||||
|
||||
// additional params
|
||||
// can be defined by connection string, by exec sql like "SET xxx = yyy", by settings from existing db-file
|
||||
|
||||
if (improveCaches) {
|
||||
// CACHE_SIZE
|
||||
// max query cache size in kb (default: 65 Mb per 1 GB of java's max memory)
|
||||
// warning, xmage require 150Mb cache for big queries in AI games like all card names (db can be broken on lower cache)
|
||||
//res += ";CACHE_SIZE=150000";
|
||||
res += ";CACHE_SIZE=" + Math.round(Math.max(150000, Runtime.getRuntime().maxMemory() * 0.1 / 1024));
|
||||
|
||||
|
||||
// QUERY_CACHE_SIZE
|
||||
// queries amount per session to cache (default: 8)
|
||||
res += ";QUERY_CACHE_SIZE=32";
|
||||
}
|
||||
|
||||
// add debug stats (see DebugUtil for usage instruction)
|
||||
if (DebugUtil.DATABASE_PROFILE_SQL_QUERIES_TO_FILE) {
|
||||
res += ";TRACE_LEVEL_FILE=2";
|
||||
res += ";QUERY_STATISTICS=TRUE";
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare JDBC connection string and setup additional params for SQLite databases
|
||||
*
|
||||
* @param dbName database name like "cards"
|
||||
*/
|
||||
public static String prepareSqliteConnection(String dbName) {
|
||||
// example: jdbc:sqlite:./db/table_record.db
|
||||
return String.format("jdbc:sqlite:./db/%s", dbName);
|
||||
}
|
||||
}
|
||||
|
|
@ -27,13 +27,13 @@ public enum ExpansionRepository {
|
|||
|
||||
private static final Logger logger = Logger.getLogger(ExpansionRepository.class);
|
||||
|
||||
private static final String JDBC_URL = "jdbc:h2:file:./db/cards.h2;AUTO_SERVER=TRUE;IGNORECASE=TRUE";
|
||||
// TODO: delete db version from cards and expansions due un-used (that's dbs re-created on each update)
|
||||
private static final String VERSION_ENTITY_NAME = "expansion";
|
||||
private static final long EXPANSION_DB_VERSION = 5;
|
||||
private static final long EXPANSION_CONTENT_VERSION = 18;
|
||||
|
||||
private Dao<ExpansionInfo, Object> expansionDao;
|
||||
private RepositoryEventSource eventSource = new RepositoryEventSource();
|
||||
private final RepositoryEventSource eventSource = new RepositoryEventSource();
|
||||
public boolean instanceInitialized = false;
|
||||
|
||||
ExpansionRepository() {
|
||||
|
|
@ -42,7 +42,7 @@ public enum ExpansionRepository {
|
|||
file.mkdirs();
|
||||
}
|
||||
try {
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL);
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_CARDS, true));
|
||||
|
||||
boolean isObsolete = RepositoryUtil.isDatabaseObsolete(connectionSource, VERSION_ENTITY_NAME, EXPANSION_DB_VERSION);
|
||||
boolean isNewBuild = RepositoryUtil.isNewBuildRun(connectionSource, VERSION_ENTITY_NAME, ExpansionRepository.class); // recreate db on new build
|
||||
|
|
@ -218,7 +218,7 @@ public enum ExpansionRepository {
|
|||
|
||||
public long getContentVersionFromDB() {
|
||||
try {
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL);
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_CARDS, false));
|
||||
return RepositoryUtil.getDatabaseVersion(connectionSource, VERSION_ENTITY_NAME + "Content");
|
||||
} catch (SQLException ex) {
|
||||
ex.printStackTrace();
|
||||
|
|
@ -228,7 +228,7 @@ public enum ExpansionRepository {
|
|||
|
||||
public void setContentVersion(long version) {
|
||||
try {
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL);
|
||||
ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_CARDS, false));
|
||||
RepositoryUtil.updateVersion(connectionSource, VERSION_ENTITY_NAME + "Content", version);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error setting content version - " + e, e);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import java.util.EventObject;
|
|||
*/
|
||||
public class RepositoryEvent extends EventObject implements ExternalEvent, Serializable {
|
||||
|
||||
// TODO: db changed on update only, events can be deleted
|
||||
public enum RepositoryEventType {
|
||||
DB_LOADED, DB_UPDATED
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.j256.ormlite.stmt.QueryBuilder;
|
|||
import com.j256.ormlite.stmt.SelectArg;
|
||||
import com.j256.ormlite.support.ConnectionSource;
|
||||
import com.j256.ormlite.table.TableUtils;
|
||||
import mage.util.DebugUtil;
|
||||
import mage.util.JarVersion;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
|
|
@ -39,22 +40,26 @@ public final class RepositoryUtil {
|
|||
logger.info(" - emblems: " + TokenRepository.instance.getByType(TokenType.EMBLEM).size());
|
||||
logger.info(" - planes: " + TokenRepository.instance.getByType(TokenType.PLANE).size());
|
||||
logger.info(" - dungeons: " + TokenRepository.instance.getByType(TokenType.DUNGEON).size());
|
||||
|
||||
if (DebugUtil.DATABASE_SHOW_CACHE_AND_MEMORY_STATS_ON_STARTUP) {
|
||||
CardRepository.instance.printDatabaseStats("on startup");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDatabaseObsolete(ConnectionSource connectionSource, String entityName, long version) throws SQLException {
|
||||
TableUtils.createTableIfNotExists(connectionSource, DatabaseVersion.class);
|
||||
Dao<DatabaseVersion, Object> dbVersionDao = DaoManager.createDao(connectionSource, DatabaseVersion.class);
|
||||
Dao<DatabaseVersion, Object> versionDao = DaoManager.createDao(connectionSource, DatabaseVersion.class);
|
||||
|
||||
QueryBuilder<DatabaseVersion, Object> queryBuilder = dbVersionDao.queryBuilder();
|
||||
QueryBuilder<DatabaseVersion, Object> queryBuilder = versionDao.queryBuilder();
|
||||
queryBuilder.where().eq("entity", new SelectArg(entityName))
|
||||
.and().eq("version", new SelectArg(version));
|
||||
List<DatabaseVersion> dbVersions = dbVersionDao.query(queryBuilder.prepare());
|
||||
List<DatabaseVersion> dbVersions = versionDao.query(queryBuilder.prepare());
|
||||
|
||||
if (dbVersions.isEmpty()) {
|
||||
DatabaseVersion dbVersion = new DatabaseVersion();
|
||||
dbVersion.setEntity(entityName);
|
||||
dbVersion.setVersion(version);
|
||||
dbVersionDao.create(dbVersion);
|
||||
versionDao.create(dbVersion);
|
||||
}
|
||||
return dbVersions.isEmpty();
|
||||
}
|
||||
|
|
@ -68,48 +73,48 @@ public final class RepositoryUtil {
|
|||
}
|
||||
|
||||
TableUtils.createTableIfNotExists(connectionSource, DatabaseBuild.class);
|
||||
Dao<DatabaseBuild, Object> dbBuildDao = DaoManager.createDao(connectionSource, DatabaseBuild.class);
|
||||
Dao<DatabaseBuild, Object> buildDao = DaoManager.createDao(connectionSource, DatabaseBuild.class);
|
||||
|
||||
QueryBuilder<DatabaseBuild, Object> queryBuilder = dbBuildDao.queryBuilder();
|
||||
QueryBuilder<DatabaseBuild, Object> queryBuilder = buildDao.queryBuilder();
|
||||
queryBuilder.where().eq("entity", new SelectArg(entityName))
|
||||
.and().eq("last_build", new SelectArg(currentBuild));
|
||||
List<DatabaseBuild> dbBuilds = dbBuildDao.query(queryBuilder.prepare());
|
||||
List<DatabaseBuild> dbBuilds = buildDao.query(queryBuilder.prepare());
|
||||
|
||||
if (dbBuilds.isEmpty()) {
|
||||
DatabaseBuild dbBuild = new DatabaseBuild();
|
||||
dbBuild.setEntity(entityName);
|
||||
dbBuild.setLastBuild(currentBuild);
|
||||
dbBuildDao.create(dbBuild);
|
||||
buildDao.create(dbBuild);
|
||||
}
|
||||
return dbBuilds.isEmpty();
|
||||
}
|
||||
|
||||
public static void updateVersion(ConnectionSource connectionSource, String entityName, long version) throws SQLException {
|
||||
TableUtils.createTableIfNotExists(connectionSource, DatabaseVersion.class);
|
||||
Dao<DatabaseVersion, Object> dbVersionDao = DaoManager.createDao(connectionSource, DatabaseVersion.class);
|
||||
Dao<DatabaseVersion, Object> versionDao = DaoManager.createDao(connectionSource, DatabaseVersion.class);
|
||||
|
||||
QueryBuilder<DatabaseVersion, Object> queryBuilder = dbVersionDao.queryBuilder();
|
||||
QueryBuilder<DatabaseVersion, Object> queryBuilder = versionDao.queryBuilder();
|
||||
queryBuilder.where().eq("entity", new SelectArg(entityName));
|
||||
List<DatabaseVersion> dbVersions = dbVersionDao.query(queryBuilder.prepare());
|
||||
List<DatabaseVersion> dbVersions = versionDao.query(queryBuilder.prepare());
|
||||
|
||||
if (!dbVersions.isEmpty()) {
|
||||
DeleteBuilder<DatabaseVersion, Object> deleteBuilder = dbVersionDao.deleteBuilder();
|
||||
DeleteBuilder<DatabaseVersion, Object> deleteBuilder = versionDao.deleteBuilder();
|
||||
deleteBuilder.where().eq("entity", new SelectArg(entityName));
|
||||
deleteBuilder.delete();
|
||||
}
|
||||
DatabaseVersion databaseVersion = new DatabaseVersion();
|
||||
databaseVersion.setEntity(entityName);
|
||||
databaseVersion.setVersion(version);
|
||||
dbVersionDao.create(databaseVersion);
|
||||
versionDao.create(databaseVersion);
|
||||
}
|
||||
|
||||
public static long getDatabaseVersion(ConnectionSource connectionSource, String entityName) throws SQLException {
|
||||
TableUtils.createTableIfNotExists(connectionSource, DatabaseVersion.class);
|
||||
Dao<DatabaseVersion, Object> dbVersionDao = DaoManager.createDao(connectionSource, DatabaseVersion.class);
|
||||
Dao<DatabaseVersion, Object> versionDao = DaoManager.createDao(connectionSource, DatabaseVersion.class);
|
||||
|
||||
QueryBuilder<DatabaseVersion, Object> queryBuilder = dbVersionDao.queryBuilder();
|
||||
QueryBuilder<DatabaseVersion, Object> queryBuilder = versionDao.queryBuilder();
|
||||
queryBuilder.where().eq("entity", new SelectArg(entityName));
|
||||
List<DatabaseVersion> dbVersions = dbVersionDao.query(queryBuilder.prepare());
|
||||
List<DatabaseVersion> dbVersions = versionDao.query(queryBuilder.prepare());
|
||||
if (dbVersions.isEmpty()) {
|
||||
return 0;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,18 @@ public class DebugUtil {
|
|||
// game dialogs
|
||||
public static boolean GUI_GAME_DIALOGS_DRAW_CARDS_AREA_BORDER = false;
|
||||
|
||||
// database - show additional info about cache and memory settings
|
||||
public static boolean DATABASE_SHOW_CACHE_AND_MEMORY_STATS_ON_STARTUP = true;
|
||||
|
||||
// database - collect sql queries and stats
|
||||
// how-to use:
|
||||
// - clean db folders or delete all *.trace.db files
|
||||
// - run tests or real server to collect some stats
|
||||
// - download h2 files for ver 1.4.197 from https://h2database.com/h2-2018-03-18.zip and open tools folder like xxx\H2\bin
|
||||
// - execute command: java -cp "h2-1.4.196.jar;%H2DRIVERS%;%CLASSPATH%" org.h2.tools.ConvertTraceFile -traceFile "xxx\Mage.Tests\db\cards.h2.trace.db" -script "xxx\Mage.Tests\db\cards.h2.trace.sql"
|
||||
// - open *.sql file for all sql-queries and exec stats
|
||||
public static boolean DATABASE_PROFILE_SQL_QUERIES_TO_FILE = false;
|
||||
|
||||
public static String getMethodNameWithSource(final int depth) {
|
||||
return TraceHelper.getMethodNameWithSource(depth);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue