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:
Oleg Agafonov 2024-07-18 21:22:10 +04:00
parent c448612c97
commit bf3f26ccc1
18 changed files with 376 additions and 394 deletions

View file

@ -1774,7 +1774,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
private void doClientShutdownAndExit() { private void doClientShutdownAndExit() {
tablesPane.cleanUp(); tablesPane.cleanUp();
CardRepository.instance.closeDB(); CardRepository.instance.closeDB(true);
Plugins.instance.shutdown(); Plugins.instance.shutdown();
dispose(); dispose();
System.exit(0); System.exit(0);

View file

@ -5,9 +5,8 @@ import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.jdbc.JdbcConnectionSource; import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.support.ConnectionSource; import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils; import com.j256.ormlite.table.TableUtils;
import mage.cards.repository.DatabaseUtils;
import mage.db.model.Feedback; import mage.db.model.Feedback;
import mage.db.model.Log;
import mage.utils.properties.PropertiesUtil;
import java.io.File; import java.io.File;
import java.sql.SQLException; import java.sql.SQLException;
@ -21,20 +20,15 @@ public enum EntityManager {
instance; instance;
private Dao<Log, Object> logDao;
private Dao<Feedback, Object> feedbackDao; private Dao<Feedback, Object> feedbackDao;
private EntityManager() { EntityManager() {
File file = new File("db"); File file = new File("db");
if (!file.exists()) { if (!file.exists()) {
file.mkdirs(); file.mkdirs();
} }
try { try {
ConnectionSource logConnectionSource = new JdbcConnectionSource(PropertiesUtil.getDBLogUrl()); ConnectionSource feedbackConnectionSource = new JdbcConnectionSource(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_FEEDBACK, false));
TableUtils.createTableIfNotExists(logConnectionSource, Log.class);
logDao = DaoManager.createDao(logConnectionSource, Log.class);
ConnectionSource feedbackConnectionSource = new JdbcConnectionSource(PropertiesUtil.getDBFeedbackUrl());
TableUtils.createTableIfNotExists(feedbackConnectionSource, Feedback.class); TableUtils.createTableIfNotExists(feedbackConnectionSource, Feedback.class);
feedbackDao = DaoManager.createDao(feedbackConnectionSource, Feedback.class); feedbackDao = DaoManager.createDao(feedbackConnectionSource, Feedback.class);
} catch (SQLException ex) { } catch (SQLException ex) {
@ -42,22 +36,6 @@ public enum EntityManager {
} }
} }
public void insertLog(String key, java.util.Date date, String... args) throws SQLException {
Log logEntity = new Log(key, date);
logEntity.setArguments(args);
logDao.create(logEntity);
}
public List<Log> getAllLogs() {
List<Log> logs = new ArrayList<>();
try {
logs = logDao.queryForAll();
} catch (SQLException ex) {
}
return logs;
}
public void insertFeedback(String username, String title, String type, String message, String email, String host, java.util.Date created) throws SQLException { public void insertFeedback(String username, String title, String type, String message, String email, String host, java.util.Date created) throws SQLException {
Feedback feedback = new Feedback(username, title, type, message, email, host, created, "new"); Feedback feedback = new Feedback(username, title, type, message, email, host, created, "new");
feedbackDao.create(feedback); feedbackDao.create(feedback);

View file

@ -12,26 +12,7 @@ import java.util.List;
*/ */
public final class EntityManagerTest { public final class EntityManagerTest {
private static DateFormat timeFormatter = SimpleDateFormat.getTimeInstance(SimpleDateFormat.FULL);
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
List<Log> logs = EntityManager.instance.getAllLogs();
System.out.println("logs found: " + logs.size());
for (Log log : logs) {
System.out.println(" key=" + log.getKey());
System.out.println(" date=" + timeFormatter.format(log.getCreatedDate()));
System.out.print(" arguments=[ ");
if (log.getArguments() != null) {
for (String argument : log.getArguments()) {
System.out.print("arg=" + argument + ' ');
}
}
System.out.println("]");
System.out.println(" --------------");
}
System.out.println("********************************");
List<Feedback> feedbackList = EntityManager.instance.getAllFeedbacks(); List<Feedback> feedbackList = EntityManager.instance.getAllFeedbacks();
System.out.println("feedbacks found: " + feedbackList.size()); System.out.println("feedbacks found: " + feedbackList.size());
int count = 1; int count = 1;

View file

@ -1,113 +0,0 @@
package mage.db;
import mage.db.model.Log;
import java.util.*;
/**
* @author noxx
*/
public final class Statistics {
public static void main(String[] args) throws Exception {
List<Log> logs = EntityManager.instance.getAllLogs();
System.out.println("logs found: " + logs.size());
Map<String, Integer> nicknames = displayCommonNumbers(logs);
List<Integer> games = displayTop3(nicknames);
displayPlayedOnlyOnce(games);
System.out.println("Done");
}
private static void displayPlayedOnlyOnce(List<Integer> games) {
Integer oneGame = 0;
for (Integer numberOfGames : games) {
if (numberOfGames == 1) {
oneGame++;
}
}
System.out.println("Number of players played only one game: " + oneGame);
}
private static List<Integer> displayTop3(Map<String, Integer> nicknames) {
Collection<Integer> values = nicknames.values();
List<Integer> games = new ArrayList<>();
games.addAll(values);
Collections.sort(games, new Comparator<Integer>() {
@Override
public int compare(Integer i1, Integer i2) {
return i2.compareTo(i1);
}
});
// Top-3
List<Integer> numbersToFind = new ArrayList<>();
for (Integer numberOfGames : games) {
numbersToFind.add(numberOfGames);
if (numbersToFind.size() == 3) {
break;
}
}
Map<Integer, String> players = new LinkedHashMap<>();
for (Map.Entry<String, Integer> entry : nicknames.entrySet()) {
if (check(numbersToFind, entry.getValue())) {
players.put(entry.getValue(), entry.getKey());
}
if (players.size() == 3) {
break;
}
}
System.out.println("Top-3");
for (Map.Entry<Integer, String> entry : players.entrySet()) {
System.out.println(" " + entry.getValue() + ": " + entry.getKey());
}
return games;
}
private static Map<String, Integer> displayCommonNumbers(List<Log> logs) {
int count = 0;
Map<String, Integer> nicknames = new HashMap<>();
for (Log log : logs) {
if (log.getKey().equals("gameStarted")) {
if (log.getArguments() != null) {
int index = 0;
for (String argument : log.getArguments()) {
if (index > 0) {
inc(nicknames, argument);
}
index++;
}
}
count++;
}
}
System.out.println("********************************");
System.out.println("Games played: " + count);
System.out.println("Number of players: " + nicknames.size());
return nicknames;
}
public static void inc(Map<String, Integer> map, String player) {
if (map.containsKey(player)) {
Integer count = map.get(player);
count++;
map.put(player, count);
} else {
map.put(player, 1);
}
}
public static boolean check(List<Integer> numbers, Integer value) {
for (Integer number : numbers) {
if (number.equals(value)) {
return true;
}
}
return false;
}
}

View file

@ -1,59 +0,0 @@
package mage.utils.properties;
import org.apache.log4j.Logger;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author noxx
*/
public final class PropertiesUtil {
private static final Logger logger = Logger.getLogger(PropertiesUtil.class);
private static final String LOG_JDBC_URL = "jdbc:h2:file:./db/mage.h2;AUTO_SERVER=TRUE";
private static final String FEEDBACK_JDBC_URL = "jdbc:h2:file:./db/feedback.h2;AUTO_SERVER=TRUE";
private static Properties properties = new Properties();
static {
try (InputStream in = PropertiesUtil.class.getResourceAsStream("/xmage.properties")) {
if(in != null) {
properties.load(in);
} else {
logger.warn("No xmage.properties were found");
}
} catch (FileNotFoundException fnfe) {
logger.warn("No xmage.properties were found on classpath");
} catch (IOException e) {
logger.error("Couldn't load properties");
e.printStackTrace();
}
}
/**
* Hide constructor
*/
private PropertiesUtil() {
}
public static String getDBLogUrl () {
String url = properties.getProperty(PropertyKeys.KEY_DB_LOG_URL, LOG_JDBC_URL);
if (url != null) {
return url.trim();
}
return null;
}
public static String getDBFeedbackUrl () {
String url = properties.getProperty(PropertyKeys.KEY_DB_FEEDBACK_URL, FEEDBACK_JDBC_URL);
if (url != null) {
return url.trim();
}
return null;
}
}

View file

@ -1,10 +0,0 @@
package mage.utils.properties;
/**
* @author noxx
*/
public final class PropertyKeys {
public static final String KEY_DB_LOG_URL = "db.log.url";
public static final String KEY_DB_FEEDBACK_URL = "db.feedback.url";
}

View file

@ -10,6 +10,7 @@ import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.support.DatabaseConnection; import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.table.TableUtils; import com.j256.ormlite.table.TableUtils;
import mage.cards.repository.CardRepository; import mage.cards.repository.CardRepository;
import mage.cards.repository.DatabaseUtils;
import mage.cards.repository.RepositoryUtil; import mage.cards.repository.RepositoryUtil;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.apache.shiro.crypto.RandomNumberGenerator; import org.apache.shiro.crypto.RandomNumberGenerator;
@ -24,7 +25,6 @@ import java.util.List;
public class AuthorizedUserRepository { public class AuthorizedUserRepository {
private static final String JDBC_URL = "jdbc:h2:file:./db/authorized_user.h2;AUTO_SERVER=TRUE";
private static final String VERSION_ENTITY_NAME = "authorized_user"; private static final String VERSION_ENTITY_NAME = "authorized_user";
// raise this if db structure was changed // raise this if db structure was changed
private static final long DB_VERSION = 2; private static final long DB_VERSION = 2;
@ -32,10 +32,10 @@ public class AuthorizedUserRepository {
private static final AuthorizedUserRepository instance; private static final AuthorizedUserRepository instance;
static { static {
instance = new AuthorizedUserRepository(JDBC_URL); instance = new AuthorizedUserRepository(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_USERS, false));
} }
private Dao<AuthorizedUser, Object> dao; private Dao<AuthorizedUser, Object> usersDao;
public AuthorizedUserRepository(String connectionString) { public AuthorizedUserRepository(String connectionString) {
File file = new File("db"); File file = new File("db");
@ -45,7 +45,7 @@ public class AuthorizedUserRepository {
try { try {
ConnectionSource connectionSource = new JdbcConnectionSource(connectionString); ConnectionSource connectionSource = new JdbcConnectionSource(connectionString);
TableUtils.createTableIfNotExists(connectionSource, AuthorizedUser.class); TableUtils.createTableIfNotExists(connectionSource, AuthorizedUser.class);
dao = DaoManager.createDao(connectionSource, AuthorizedUser.class); usersDao = DaoManager.createDao(connectionSource, AuthorizedUser.class);
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(AuthorizedUserRepository.class).error("Error creating / assigning authorized_user repository - ", ex); Logger.getLogger(AuthorizedUserRepository.class).error("Error creating / assigning authorized_user repository - ", ex);
} }
@ -59,7 +59,7 @@ public class AuthorizedUserRepository {
try { try {
Hash hash = new SimpleHash(Sha256Hash.ALGORITHM_NAME, password, rng.nextBytes(), 1024); Hash hash = new SimpleHash(Sha256Hash.ALGORITHM_NAME, password, rng.nextBytes(), 1024);
AuthorizedUser user = new AuthorizedUser(userName, hash, email); AuthorizedUser user = new AuthorizedUser(userName, hash, email);
dao.create(user); usersDao.create(user);
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(AuthorizedUserRepository.class).error("Error adding a user to DB - ", ex); Logger.getLogger(AuthorizedUserRepository.class).error("Error adding a user to DB - ", ex);
} }
@ -67,7 +67,7 @@ public class AuthorizedUserRepository {
public void remove(final String userName) { public void remove(final String userName) {
try { try {
DeleteBuilder<AuthorizedUser, Object> db = dao.deleteBuilder(); DeleteBuilder<AuthorizedUser, Object> db = usersDao.deleteBuilder();
db.where().eq("name", new SelectArg(userName)); db.where().eq("name", new SelectArg(userName));
db.delete(); db.delete();
} catch (SQLException ex) { } catch (SQLException ex) {
@ -77,9 +77,9 @@ public class AuthorizedUserRepository {
public AuthorizedUser getByName(String userName) { public AuthorizedUser getByName(String userName) {
try { try {
QueryBuilder<AuthorizedUser, Object> qb = dao.queryBuilder(); QueryBuilder<AuthorizedUser, Object> qb = usersDao.queryBuilder();
qb.where().eq("name", new SelectArg(userName)); qb.where().eq("name", new SelectArg(userName));
List<AuthorizedUser> results = dao.query(qb.prepare()); List<AuthorizedUser> results = usersDao.query(qb.prepare());
if (results.size() == 1) { if (results.size() == 1) {
return results.get(0); return results.get(0);
} }
@ -92,7 +92,7 @@ public class AuthorizedUserRepository {
public void update(AuthorizedUser authorizedUser) { public void update(AuthorizedUser authorizedUser) {
try { try {
dao.update(authorizedUser); usersDao.update(authorizedUser);
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(AuthorizedUserRepository.class).error("Error updating authorized_user", ex); Logger.getLogger(AuthorizedUserRepository.class).error("Error updating authorized_user", ex);
} }
@ -100,9 +100,9 @@ public class AuthorizedUserRepository {
public AuthorizedUser getByEmail(String userName) { public AuthorizedUser getByEmail(String userName) {
try { try {
QueryBuilder<AuthorizedUser, Object> qb = dao.queryBuilder(); QueryBuilder<AuthorizedUser, Object> qb = usersDao.queryBuilder();
qb.where().eq("email", new SelectArg(userName)); qb.where().eq("email", new SelectArg(userName));
List<AuthorizedUser> results = dao.query(qb.prepare()); List<AuthorizedUser> results = usersDao.query(qb.prepare());
if (results.size() == 1) { if (results.size() == 1) {
return results.get(0); return results.get(0);
} }
@ -115,9 +115,10 @@ public class AuthorizedUserRepository {
public void closeDB() { public void closeDB() {
try { try {
if (dao != null && dao.getConnectionSource() != null) { if (usersDao != null && usersDao.getConnectionSource() != null) {
DatabaseConnection conn = dao.getConnectionSource().getReadWriteConnection(dao.getTableName()); DatabaseConnection conn = usersDao.getConnectionSource().getReadWriteConnection(usersDao.getTableName());
conn.executeStatement("shutdown compact", 0); conn.executeStatement("SHUTDOWN IMMEDIATELY", 0);
usersDao.getConnectionSource().releaseConnection(conn);
} }
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(AuthorizedUserRepository.class).error("Error closing authorized_user repository - ", ex); Logger.getLogger(AuthorizedUserRepository.class).error("Error closing authorized_user repository - ", ex);
@ -126,7 +127,7 @@ public class AuthorizedUserRepository {
public long getDBVersionFromDB() { public long getDBVersionFromDB() {
try { try {
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL); ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_USERS, false));
return RepositoryUtil.getDatabaseVersion(connectionSource, VERSION_ENTITY_NAME); return RepositoryUtil.getDatabaseVersion(connectionSource, VERSION_ENTITY_NAME);
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(CardRepository.class).error("Error getting DB version from DB - ", ex); Logger.getLogger(CardRepository.class).error("Error getting DB version from DB - ", ex);
@ -145,11 +146,11 @@ public class AuthorizedUserRepository {
private boolean migrateFrom1To2() { private boolean migrateFrom1To2() {
try { try {
Logger.getLogger(AuthorizedUserRepository.class).info("Starting " + VERSION_ENTITY_NAME + " DB migration from version 1 to version 2"); Logger.getLogger(AuthorizedUserRepository.class).info("Starting " + VERSION_ENTITY_NAME + " DB migration from version 1 to version 2");
dao.executeRaw("ALTER TABLE authorized_user ADD COLUMN active BOOLEAN DEFAULT true;"); usersDao.executeRaw("ALTER TABLE authorized_user ADD COLUMN active BOOLEAN DEFAULT true;");
dao.executeRaw("ALTER TABLE authorized_user ADD COLUMN lockedUntil DATETIME;"); usersDao.executeRaw("ALTER TABLE authorized_user ADD COLUMN lockedUntil DATETIME;");
dao.executeRaw("ALTER TABLE authorized_user ADD COLUMN chatLockedUntil DATETIME;"); usersDao.executeRaw("ALTER TABLE authorized_user ADD COLUMN chatLockedUntil DATETIME;");
dao.executeRaw("ALTER TABLE authorized_user ADD COLUMN lastConnection DATETIME;"); usersDao.executeRaw("ALTER TABLE authorized_user ADD COLUMN lastConnection DATETIME;");
RepositoryUtil.updateVersion(dao.getConnectionSource(), VERSION_ENTITY_NAME, DB_VERSION); RepositoryUtil.updateVersion(usersDao.getConnectionSource(), VERSION_ENTITY_NAME, DB_VERSION);
Logger.getLogger(AuthorizedUserRepository.class).info("Migration finished."); Logger.getLogger(AuthorizedUserRepository.class).info("Migration finished.");
return true; return true;
} catch (SQLException ex) { } catch (SQLException ex) {

View file

@ -8,6 +8,7 @@ import com.j256.ormlite.stmt.SelectArg;
import com.j256.ormlite.support.ConnectionSource; import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.support.DatabaseConnection; import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.table.TableUtils; import com.j256.ormlite.table.TableUtils;
import mage.cards.repository.DatabaseUtils;
import mage.cards.repository.RepositoryUtil; import mage.cards.repository.RepositoryUtil;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -20,12 +21,11 @@ public enum TableRecordRepository {
instance; instance;
private static final String JDBC_URL = "jdbc:sqlite:./db/table_record.db";
private static final String VERSION_ENTITY_NAME = "table_record"; private static final String VERSION_ENTITY_NAME = "table_record";
// raise this if db structure was changed // raise this if db structure was changed
private static final long DB_VERSION = 0; private static final long DB_VERSION = 0;
private Dao<TableRecord, Object> dao; private Dao<TableRecord, Object> recordsDao;
TableRecordRepository() { TableRecordRepository() {
File file = new File("db"); File file = new File("db");
@ -33,7 +33,7 @@ public enum TableRecordRepository {
file.mkdirs(); file.mkdirs();
} }
try { try {
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL); ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareSqliteConnection(DatabaseUtils.DB_NAME_RECORDS));
boolean obsolete = RepositoryUtil.isDatabaseObsolete(connectionSource, VERSION_ENTITY_NAME, DB_VERSION); boolean obsolete = RepositoryUtil.isDatabaseObsolete(connectionSource, VERSION_ENTITY_NAME, DB_VERSION);
if (obsolete) { if (obsolete) {
@ -41,7 +41,7 @@ public enum TableRecordRepository {
} }
TableUtils.createTableIfNotExists(connectionSource, TableRecord.class); TableUtils.createTableIfNotExists(connectionSource, TableRecord.class);
dao = DaoManager.createDao(connectionSource, TableRecord.class); recordsDao = DaoManager.createDao(connectionSource, TableRecord.class);
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(TableRecordRepository.class).error("Error creating table_record repository - ", ex); Logger.getLogger(TableRecordRepository.class).error("Error creating table_record repository - ", ex);
} }
@ -49,7 +49,7 @@ public enum TableRecordRepository {
public void add(TableRecord tableHistory) { public void add(TableRecord tableHistory) {
try { try {
dao.create(tableHistory); recordsDao.create(tableHistory);
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(TableRecordRepository.class).error("Error adding a table_record to DB - ", ex); Logger.getLogger(TableRecordRepository.class).error("Error adding a table_record to DB - ", ex);
} }
@ -57,10 +57,10 @@ public enum TableRecordRepository {
public List<TableRecord> getAfter(long endTimeMs) { public List<TableRecord> getAfter(long endTimeMs) {
try { try {
QueryBuilder<TableRecord, Object> qb = dao.queryBuilder(); QueryBuilder<TableRecord, Object> qb = recordsDao.queryBuilder();
qb.where().gt("endTimeMs", new SelectArg(endTimeMs)); qb.where().gt("endTimeMs", new SelectArg(endTimeMs));
qb.orderBy("endTimeMs", true); qb.orderBy("endTimeMs", true);
return dao.query(qb.prepare()); return recordsDao.query(qb.prepare());
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(TableRecordRepository.class).error("Error getting table_records from DB - ", ex); Logger.getLogger(TableRecordRepository.class).error("Error getting table_records from DB - ", ex);
} }
@ -69,9 +69,10 @@ public enum TableRecordRepository {
public void closeDB() { public void closeDB() {
try { try {
if (dao != null && dao.getConnectionSource() != null) { if (recordsDao != null && recordsDao.getConnectionSource() != null) {
DatabaseConnection conn = dao.getConnectionSource().getReadWriteConnection(dao.getTableName()); DatabaseConnection conn = recordsDao.getConnectionSource().getReadWriteConnection(recordsDao.getTableName());
conn.executeStatement("shutdown compact", 0); conn.executeStatement("SHUTDOWN IMMEDIATELY", 0);
recordsDao.getConnectionSource().releaseConnection(conn);
} }
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(TableRecordRepository.class).error("Error closing table_record repository - ", ex); Logger.getLogger(TableRecordRepository.class).error("Error closing table_record repository - ", ex);

View file

@ -8,6 +8,7 @@ import com.j256.ormlite.stmt.SelectArg;
import com.j256.ormlite.support.ConnectionSource; import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.support.DatabaseConnection; import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.table.TableUtils; import com.j256.ormlite.table.TableUtils;
import mage.cards.repository.DatabaseUtils;
import mage.cards.repository.RepositoryUtil; import mage.cards.repository.RepositoryUtil;
import mage.game.result.ResultProtos; import mage.game.result.ResultProtos;
import mage.server.rating.GlickoRating; import mage.server.rating.GlickoRating;
@ -22,12 +23,11 @@ public enum UserStatsRepository {
instance; instance;
private static final String JDBC_URL = "jdbc:sqlite:./db/user_stats.db";
private static final String VERSION_ENTITY_NAME = "user_stats"; private static final String VERSION_ENTITY_NAME = "user_stats";
// raise this if db structure was changed // raise this if db structure was changed
private static final long DB_VERSION = 0; private static final long DB_VERSION = 0;
private Dao<UserStats, Object> dao; private Dao<UserStats, Object> statsDao;
UserStatsRepository() { UserStatsRepository() {
File file = new File("db"); File file = new File("db");
@ -35,7 +35,7 @@ public enum UserStatsRepository {
file.mkdirs(); file.mkdirs();
} }
try { try {
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL); ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareSqliteConnection(DatabaseUtils.DB_NAME_STATS));
boolean obsolete = RepositoryUtil.isDatabaseObsolete(connectionSource, VERSION_ENTITY_NAME, DB_VERSION); boolean obsolete = RepositoryUtil.isDatabaseObsolete(connectionSource, VERSION_ENTITY_NAME, DB_VERSION);
if (obsolete) { if (obsolete) {
@ -43,7 +43,7 @@ public enum UserStatsRepository {
} }
TableUtils.createTableIfNotExists(connectionSource, UserStats.class); TableUtils.createTableIfNotExists(connectionSource, UserStats.class);
dao = DaoManager.createDao(connectionSource, UserStats.class); statsDao = DaoManager.createDao(connectionSource, UserStats.class);
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(UserStatsRepository.class).error("Error creating user_stats repository - ", ex); Logger.getLogger(UserStatsRepository.class).error("Error creating user_stats repository - ", ex);
} }
@ -51,7 +51,7 @@ public enum UserStatsRepository {
public void add(UserStats userStats) { public void add(UserStats userStats) {
try { try {
dao.create(userStats); statsDao.create(userStats);
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(UserStatsRepository.class).error("Error adding a user_stats to DB - ", ex); Logger.getLogger(UserStatsRepository.class).error("Error adding a user_stats to DB - ", ex);
} }
@ -59,7 +59,7 @@ public enum UserStatsRepository {
public void update(UserStats userStats) { public void update(UserStats userStats) {
try { try {
dao.update(userStats); statsDao.update(userStats);
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(UserStatsRepository.class).error("Error updating a user_stats in DB - ", ex); Logger.getLogger(UserStatsRepository.class).error("Error updating a user_stats in DB - ", ex);
} }
@ -67,9 +67,9 @@ public enum UserStatsRepository {
public UserStats getUser(String userName) { public UserStats getUser(String userName) {
try { try {
QueryBuilder<UserStats, Object> qb = dao.queryBuilder(); QueryBuilder<UserStats, Object> qb = statsDao.queryBuilder();
qb.limit(1L).where().eq("userName", new SelectArg(userName)); qb.limit(1L).where().eq("userName", new SelectArg(userName));
List<UserStats> users = dao.query(qb.prepare()); List<UserStats> users = statsDao.query(qb.prepare());
if (!users.isEmpty()) { if (!users.isEmpty()) {
return users.get(0); return users.get(0);
} }
@ -81,8 +81,8 @@ public enum UserStatsRepository {
public List<UserStats> getAllUsers() { public List<UserStats> getAllUsers() {
try { try {
QueryBuilder<UserStats, Object> qb = dao.queryBuilder(); QueryBuilder<UserStats, Object> qb = statsDao.queryBuilder();
return dao.query(qb.prepare()); return statsDao.query(qb.prepare());
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(UserStatsRepository.class).error("Error getting all users from DB - ", ex); Logger.getLogger(UserStatsRepository.class).error("Error getting all users from DB - ", ex);
} }
@ -91,9 +91,9 @@ public enum UserStatsRepository {
public long getLatestEndTimeMs() { public long getLatestEndTimeMs() {
try { try {
QueryBuilder<UserStats, Object> qb = dao.queryBuilder(); QueryBuilder<UserStats, Object> qb = statsDao.queryBuilder();
qb.orderBy("endTimeMs", false).limit(1L); qb.orderBy("endTimeMs", false).limit(1L);
List<UserStats> users = dao.query(qb.prepare()); List<UserStats> users = statsDao.query(qb.prepare());
if (!users.isEmpty()) { if (!users.isEmpty()) {
return users.get(0).getEndTimeMs(); return users.get(0).getEndTimeMs();
} }
@ -366,9 +366,10 @@ public enum UserStatsRepository {
public void closeDB() { public void closeDB() {
try { try {
if (dao != null && dao.getConnectionSource() != null) { if (statsDao != null && statsDao.getConnectionSource() != null) {
DatabaseConnection conn = dao.getConnectionSource().getReadWriteConnection(dao.getTableName()); DatabaseConnection conn = statsDao.getConnectionSource().getReadWriteConnection(statsDao.getTableName());
conn.executeStatement("shutdown compact", 0); conn.executeStatement("SHUTDOWN IMMEDIATELY", 0);
statsDao.getConnectionSource().releaseConnection(conn);
} }
} catch (SQLException ex) { } catch (SQLException ex) {
Logger.getLogger(UserStatsRepository.class).error("Error closing user_stats repository - ", ex); Logger.getLogger(UserStatsRepository.class).error("Error closing user_stats repository - ", ex);

View file

@ -24,7 +24,7 @@ public final class BrainPry extends CardImpl {
public BrainPry(UUID ownerId, CardSetInfo setInfo) { public BrainPry(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}"); super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}");
//Name a nonland card. Target player reveals their hand. That player discards a card with that name. If they can't, you draw a card. // Name a nonland card. Target player reveals their hand. That player discards a card with that name. If they can't, you draw a card.
this.getSpellAbility().addEffect((new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.NON_LAND_NAME))); this.getSpellAbility().addEffect((new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.NON_LAND_NAME)));
this.getSpellAbility().addTarget(new TargetPlayer()); this.getSpellAbility().addTarget(new TargetPlayer());
this.getSpellAbility().addEffect(new BrainPryEffect()); this.getSpellAbility().addEffect(new BrainPryEffect());

View file

@ -0,0 +1,62 @@
package org.mage.test.serverside;
import mage.cards.repository.CardRepository;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps;
/**
* @author JayDi85
*/
public class DatabaseBigQueryPerformanceTest extends CardTestPlayerBaseWithAIHelps {
@Test
public void test_GetLands_SQL() {
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
Assert.assertTrue("must load getNames", CardRepository.instance.getNames().size() > 1000);
Assert.assertTrue("must load getNonLandNames", CardRepository.instance.getNonLandNames().size() > 1000);
Assert.assertTrue("must load getArtifactNames", CardRepository.instance.getArtifactNames().size() > 1000);
Assert.assertTrue("must load getCreatureNames", CardRepository.instance.getCreatureNames().size() > 1000);
Assert.assertTrue("must load getNonArtifactAndNonLandNames", CardRepository.instance.getNonArtifactAndNonLandNames().size() > 1000);
Assert.assertTrue("must load getNonLandAndNonCreatureNames", CardRepository.instance.getNonLandAndNonCreatureNames().size() > 1000);
}
@Test
public void test_GetLands_RealGame_Manual() {
// Name a nonland card. Target player reveals their hand. That player discards a card with that name.
// If they can't, you draw a card.
addCard(Zone.HAND, playerA, "Brain Pry", 1); // {1}{B}
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brain Pry", playerA);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
setChoice(playerA, "Balduvian Bears"); // name to choose
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
}
@Test
public void test_GetLands_RealGame_AI() {
// possible bug: game simulations can call big queries multiple times and overflow database cache to crash it
// how-to fix: increase CACHE_SIZE in DatabaseUtils (require 150 000 kb on 2024)
int cardsAmount = 5;
// Name a nonland card. Target player reveals their hand. That player discards a card with that name.
// If they can't, you draw a card.
addCard(Zone.HAND, playerA, "Brain Pry", cardsAmount); // {1}{B}
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2 * cardsAmount);
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
}
}

View file

@ -19,8 +19,6 @@ import java.nio.file.Paths;
*/ */
public class DatabaseCompatibleTest { public class DatabaseCompatibleTest {
private final String JDBC_URL = "jdbc:h2:file:%s;AUTO_SERVER=TRUE";
@Rule @Rule
public TemporaryFolder tempFolder = new TemporaryFolder(); public TemporaryFolder tempFolder = new TemporaryFolder();
@ -38,9 +36,8 @@ public class DatabaseCompatibleTest {
); );
Assert.assertTrue(Files.exists(Paths.get(dbFullFileName))); Assert.assertTrue(Files.exists(Paths.get(dbFullFileName)));
AuthorizedUserRepository dbUsers = new AuthorizedUserRepository( String connectionString = String.format("jdbc:h2:file:%s;AUTO_SERVER=TRUE", dbFullName);
String.format(JDBC_URL, dbFullName) AuthorizedUserRepository dbUsers = new AuthorizedUserRepository(connectionString);
);
// search // search
Assert.assertNotNull(dbUsers.getByName("user1")); Assert.assertNotNull(dbUsers.getByName("user1"));

View file

@ -2,6 +2,7 @@ package mage.cards.repository;
import com.j256.ormlite.dao.Dao; import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager; import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.dao.GenericRawResults;
import com.j256.ormlite.jdbc.JdbcConnectionSource; import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.stmt.QueryBuilder; import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.stmt.SelectArg; import com.j256.ormlite.stmt.SelectArg;
@ -19,6 +20,7 @@ import java.io.File;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/** /**
* @author North, JayDi85 * @author North, JayDi85
@ -31,16 +33,15 @@ public enum CardRepository {
// fixes limit for out of memory problems // fixes limit for out of memory problems
private static final AtomicInteger databaseFixes = new AtomicInteger(); 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"; 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 db structure was changed
private static final long CARD_DB_VERSION = 54; private static final long CARD_CONTENT_VERSION = 241; // raise this if new cards were added to the server
// raise this if new cards were added to the server
private static final long CARD_CONTENT_VERSION = 241; private Dao<CardInfo, Object> cardsDao;
private Dao<CardInfo, Object> cardDao;
private Set<String> classNames;
// sets with exclusively snow basics // sets with exclusively snow basics
public static final Set<String> snowLandSetCodes = new HashSet<>(Arrays.asList( public static final Set<String> snowLandSetCodes = new HashSet<>(Arrays.asList(
@ -55,7 +56,7 @@ public enum CardRepository {
file.mkdirs(); file.mkdirs();
} }
try { 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 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 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); TableUtils.createTableIfNotExists(connectionSource, CardInfo.class);
cardDao = DaoManager.createDao(connectionSource, CardInfo.class); cardsDao = DaoManager.createDao(connectionSource, CardInfo.class);
} catch (SQLException e) { } catch (SQLException e) {
Logger.getLogger(CardRepository.class).error("Error creating card repository - " + e, e); Logger.getLogger(CardRepository.class).error("Error creating card repository - " + e, e);
processMemoryErrors(e); processMemoryErrors(e);
@ -95,26 +96,22 @@ public enum CardRepository {
} }
public void saveCards(final List<CardInfo> newCards, long newContentVersion) { public void saveCards(final List<CardInfo> newCards, long newContentVersion) {
if (newCards == null || newCards.isEmpty()) {
return;
}
try { try {
cardDao.callBatchTasks(() -> { cardsDao.callBatchTasks(() -> {
// add // only add new cards (no updates)
if (newCards != null && !newCards.isEmpty()) { logger.info("DB: need to add " + newCards.size() + " new cards");
logger.info("DB: need to add " + newCards.size() + " new cards"); try {
try { for (CardInfo card : newCards) {
for (CardInfo card : newCards) { cardsDao.create(card);
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);
} }
} catch (SQLException e) {
Logger.getLogger(CardRepository.class).error("Error adding cards to DB - " + e, e);
processMemoryErrors(e);
} }
// no card updates
return null; return null;
}); });
@ -158,9 +155,9 @@ public enum CardRepository {
public Set<String> getNames() { public Set<String> getNames() {
Set<String> names = new TreeSet<>(); Set<String> names = new TreeSet<>();
try { try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName"); 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) { for (CardInfo card : results) {
addNewNames(card, names); addNewNames(card, names);
} }
@ -174,10 +171,10 @@ public enum CardRepository {
public Set<String> getNonLandNames() { public Set<String> getNonLandNames() {
Set<String> names = new TreeSet<>(); Set<String> names = new TreeSet<>();
try { try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName"); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
qb.where().not().like("types", new SelectArg('%' + CardType.LAND.name() + '%')); 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) { for (CardInfo card : results) {
addNewNames(card, names); addNewNames(card, names);
} }
@ -191,14 +188,14 @@ public enum CardRepository {
public Set<String> getNonbasicLandNames() { public Set<String> getNonbasicLandNames() {
Set<String> names = new TreeSet<>(); Set<String> names = new TreeSet<>();
try { try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName"); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
Where<CardInfo, Object> where = qb.where(); Where<CardInfo, Object> where = qb.where();
where.and( where.and(
where.not().like("supertypes", '%' + SuperType.BASIC.name() + '%'), where.not().like("supertypes", '%' + SuperType.BASIC.name() + '%'),
where.like("types", '%' + CardType.LAND.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) { for (CardInfo card : results) {
addNewNames(card, names); addNewNames(card, names);
} }
@ -212,10 +209,10 @@ public enum CardRepository {
public Set<String> getNotBasicLandNames() { public Set<String> getNotBasicLandNames() {
Set<String> names = new TreeSet<>(); Set<String> names = new TreeSet<>();
try { try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName"); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
qb.where().not().like("supertypes", new SelectArg('%' + SuperType.BASIC.name() + '%')); 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) { for (CardInfo card : results) {
addNewNames(card, names); addNewNames(card, names);
} }
@ -229,10 +226,10 @@ public enum CardRepository {
public Set<String> getCreatureNames() { public Set<String> getCreatureNames() {
Set<String> names = new TreeSet<>(); Set<String> names = new TreeSet<>();
try { try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName"); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
qb.where().like("types", new SelectArg('%' + CardType.CREATURE.name() + '%')); 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) { for (CardInfo card : results) {
addNewNames(card, names); addNewNames(card, names);
} }
@ -246,10 +243,10 @@ public enum CardRepository {
public Set<String> getArtifactNames() { public Set<String> getArtifactNames() {
Set<String> names = new TreeSet<>(); Set<String> names = new TreeSet<>();
try { try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName"); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
qb.where().like("types", new SelectArg('%' + CardType.ARTIFACT.name() + '%')); 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) { for (CardInfo card : results) {
addNewNames(card, names); addNewNames(card, names);
} }
@ -263,14 +260,14 @@ public enum CardRepository {
public Set<String> getNonLandAndNonCreatureNames() { public Set<String> getNonLandAndNonCreatureNames() {
Set<String> names = new TreeSet<>(); Set<String> names = new TreeSet<>();
try { try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName"); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
Where<CardInfo, Object> where = qb.where(); Where<CardInfo, Object> where = qb.where();
where.and( where.and(
where.not().like("types", '%' + CardType.CREATURE.name() + '%'), where.not().like("types", '%' + CardType.CREATURE.name() + '%'),
where.not().like("types", '%' + CardType.LAND.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) { for (CardInfo card : results) {
addNewNames(card, names); addNewNames(card, names);
} }
@ -284,14 +281,14 @@ public enum CardRepository {
public Set<String> getNonArtifactAndNonLandNames() { public Set<String> getNonArtifactAndNonLandNames() {
Set<String> names = new TreeSet<>(); Set<String> names = new TreeSet<>();
try { try {
QueryBuilder<CardInfo, Object> qb = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName"); qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName");
Where<CardInfo, Object> where = qb.where(); Where<CardInfo, Object> where = qb.where();
where.and( where.and(
where.not().like("types", '%' + CardType.ARTIFACT.name() + '%'), where.not().like("types", '%' + CardType.ARTIFACT.name() + '%'),
where.not().like("types", '%' + CardType.LAND.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) { for (CardInfo card : results) {
addNewNames(card, names); addNewNames(card, names);
} }
@ -308,7 +305,7 @@ public enum CardRepository {
public CardInfo findCard(String setCode, String cardNumber, boolean ignoreNightCards) { public CardInfo findCard(String setCode, String cardNumber, boolean ignoreNightCards) {
try { try {
QueryBuilder<CardInfo, Object> queryBuilder = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> queryBuilder = cardsDao.queryBuilder();
if (ignoreNightCards) { if (ignoreNightCards) {
queryBuilder.limit(1L).where() queryBuilder.limit(1L).where()
.eq("setCode", new SelectArg(setCode)) .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 // (example: vow - 65 - Jacob Hauken, Inspector), so make priority for main side first
queryBuilder.orderBy("nightCard", true); queryBuilder.orderBy("nightCard", true);
} }
List<CardInfo> result = cardDao.query(queryBuilder.prepare()); List<CardInfo> result = cardsDao.query(queryBuilder.prepare());
if (!result.isEmpty()) { if (!result.isEmpty()) {
return result.get(0); return result.get(0);
} }
@ -337,7 +334,7 @@ public enum CardRepository {
public List<String> getClassNames() { public List<String> getClassNames() {
List<String> names = new ArrayList<>(); List<String> names = new ArrayList<>();
try { try {
List<CardInfo> results = cardDao.queryForAll(); List<CardInfo> results = cardsDao.queryForAll();
for (CardInfo card : results) { for (CardInfo card : results) {
names.add(card.getClassName()); names.add(card.getClassName());
} }
@ -350,10 +347,10 @@ public enum CardRepository {
public List<CardInfo> getMissingCards(List<String> classNames) { public List<CardInfo> getMissingCards(List<String> classNames) {
try { try {
QueryBuilder<CardInfo, Object> queryBuilder = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> queryBuilder = cardsDao.queryBuilder();
queryBuilder.where().not().in("className", classNames); queryBuilder.where().not().in("className", classNames);
return cardDao.query(queryBuilder.prepare()); return cardsDao.query(queryBuilder.prepare());
} catch (SQLException e) { } catch (SQLException e) {
Logger.getLogger(CardRepository.class).error("Error getting missing cards from DB: " + e, e); Logger.getLogger(CardRepository.class).error("Error getting missing cards from DB: " + e, e);
processMemoryErrors(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 * Used for building cubes, packs, and for ensuring that dual faces and split cards have sides/halves from
* the same set and variant art. * the same set and variant art.
* *
* @param name name of the card, or side of the card, to find * @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 expansion the set name from which to find the card
* @param cardNumber the card number for variant arts in one set * @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. * @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 * 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. * the full card can be found by either name.
* @return * @return
*/ */
public CardInfo findCardWithPreferredSetAndNumber(String name, String expansion, String cardNumber, boolean returnSplitCardHalf) { 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. * 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" * 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". * To search for them using "A", "B", or "A // B".
* * <p>
* Note of how the function works: * Note of how the function works:
* Out of all card types (Split, MDFC, Adventure, Flip, Transform) * 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". * 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. * 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") * 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". * when querying by "name".
* *
* @param name the name of the card to search for * @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 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. * @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 * 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. * 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. * 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),
* @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.
* 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) { public List<CardInfo> findCards(String name, long limitByMaxAmount, boolean returnSplitCardHalf, boolean canCheckDatabaseHealth) {
List<CardInfo> results; List<CardInfo> results;
QueryBuilder<CardInfo, Object> queryBuilder = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> queryBuilder = cardsDao.queryBuilder();
if (limitByMaxAmount > 0) { if (limitByMaxAmount > 0) {
queryBuilder.limit(limitByMaxAmount); 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 // 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. // half of the name, but this is easier to understand.
queryBuilder.where().eq("name", new SelectArg(name)); 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.) // Result comes back empty, try to search using the first half (could be Adventure, MDFC, etc.)
if (results.isEmpty()) { if (results.isEmpty()) {
String mainCardName = name.split(" // ", 2)[0]; String mainCardName = name.split(" // ", 2)[0];
queryBuilder.where().eq("name", new SelectArg(mainCardName)); 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. } else { // Cannot tell if string represents the full name of a card or only part of it.
// Assume it is the full card name // Assume it is the full card name
queryBuilder.where().eq("name", new SelectArg(name)); queryBuilder.where().eq("name", new SelectArg(name));
results = cardDao.query(queryBuilder.prepare()); results = cardsDao.query(queryBuilder.prepare());
if (results.isEmpty()) { if (results.isEmpty()) {
// Nothing found when looking for main name, try looking under the other names // Nothing found when looking for main name, try looking under the other names
queryBuilder.where() queryBuilder.where()
.eq("flipCardName", new SelectArg(name)).or() .eq("flipCardName", new SelectArg(name)).or()
.eq("secondSideName", new SelectArg(name)).or() .eq("secondSideName", new SelectArg(name)).or()
.eq("adventureSpellName", new SelectArg(name)).or() .eq("adventureSpellName", new SelectArg(name)).or()
.eq("modalDoubleFacedSecondSideName", new SelectArg(name)); .eq("modalDoubleFacedSecondSideName", new SelectArg(name));
results = cardDao.query(queryBuilder.prepare()); results = cardsDao.query(queryBuilder.prepare());
} else { } else {
// Check that a full card was found and not a SplitCardHalf // Check that a full card was found and not a SplitCardHalf
// Can be caused by searching for "Fire" instead of "Fire // Ice" // Can be caused by searching for "Fire" instead of "Fire // Ice"
@ -522,7 +519,7 @@ public enum CardRepository {
queryBuilder.where() queryBuilder.where()
.eq("setCode", new SelectArg(firstCardInfo.setCode)).and() .eq("setCode", new SelectArg(firstCardInfo.setCode)).and()
.eq("cardNumber", new SelectArg(firstCardInfo.cardNumber)); .eq("cardNumber", new SelectArg(firstCardInfo.cardNumber));
List<CardInfo> tmpResults = cardDao.query(queryBuilder.prepare()); List<CardInfo> tmpResults = cardsDao.query(queryBuilder.prepare());
String fullSplitCardName = null; String fullSplitCardName = null;
for (CardInfo cardInfo : tmpResults) { for (CardInfo cardInfo : tmpResults) {
@ -536,7 +533,7 @@ public enum CardRepository {
} }
queryBuilder.where().eq("name", new SelectArg(fullSplitCardName)); 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) { public List<CardInfo> findCardsByClass(String canonicalClassName) {
try { try {
QueryBuilder<CardInfo, Object> queryBuilder = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> queryBuilder = cardsDao.queryBuilder();
queryBuilder.where().eq("className", new SelectArg(canonicalClassName)); queryBuilder.where().eq("className", new SelectArg(canonicalClassName));
return cardDao.query(queryBuilder.prepare()); return cardsDao.query(queryBuilder.prepare());
} catch (SQLException e) { } catch (SQLException e) {
Logger.getLogger(CardRepository.class).error("Error during execution of raw sql statement" + e, e); Logger.getLogger(CardRepository.class).error("Error during execution of raw sql statement" + e, e);
processMemoryErrors(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 * 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 * need that feature then check for simulation mode. See https://github.com/magefree/mage/issues/7014
* * <p>
* Ignoring night cards by default * Ignoring night cards by default
* *
* @param criteria * @param criteria
@ -578,10 +575,10 @@ public enum CardRepository {
*/ */
public List<CardInfo> findCards(CardCriteria criteria) { public List<CardInfo> findCards(CardCriteria criteria) {
try { try {
QueryBuilder<CardInfo, Object> queryBuilder = cardDao.queryBuilder(); QueryBuilder<CardInfo, Object> queryBuilder = cardsDao.queryBuilder();
criteria.buildQuery(queryBuilder); criteria.buildQuery(queryBuilder);
return cardDao.query(queryBuilder.prepare()); return cardsDao.query(queryBuilder.prepare());
} catch (SQLException e) { } catch (SQLException e) {
Logger.getLogger(CardRepository.class).error("Error during execution of card repository query statement: " + e, e); Logger.getLogger(CardRepository.class).error("Error during execution of card repository query statement: " + e, e);
processMemoryErrors(e); processMemoryErrors(e);
@ -622,7 +619,7 @@ public enum CardRepository {
public long getContentVersionFromDB() { public long getContentVersionFromDB() {
try { 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"); return RepositoryUtil.getDatabaseVersion(connectionSource, VERSION_ENTITY_NAME + "Content");
} catch (SQLException e) { } catch (SQLException e) {
Logger.getLogger(CardRepository.class).error("Error getting content version from DB - " + e, 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) { public void setContentVersion(long version) {
try { 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); RepositoryUtil.updateVersion(connectionSource, VERSION_ENTITY_NAME + "Content", version);
} catch (SQLException e) { } catch (SQLException e) {
Logger.getLogger(CardRepository.class).error("Error setting content version - " + e, e); Logger.getLogger(CardRepository.class).error("Error setting content version - " + e, e);
@ -645,25 +642,84 @@ public enum CardRepository {
return CARD_CONTENT_VERSION; return CARD_CONTENT_VERSION;
} }
public void closeDB() { public void closeDB(boolean writeCompact) {
try { try {
if (cardDao != null && cardDao.getConnectionSource() != null) { if (cardsDao != null && cardsDao.getConnectionSource() != null) {
DatabaseConnection conn = cardDao.getConnectionSource().getReadWriteConnection(cardDao.getTableName()); DatabaseConnection conn = cardsDao.getConnectionSource().getReadWriteConnection(cardsDao.getTableName());
conn.executeStatement("shutdown compact", 0); 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) { } catch (SQLException ignore) {
} }
} }
private void openDB() { public void openDB() {
try { try {
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL); ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_CARDS, true));
cardDao = DaoManager.createDao(connectionSource, CardInfo.class); cardsDao = DaoManager.createDao(connectionSource, CardInfo.class);
} catch (SQLException e) { } catch (SQLException e) {
Logger.getLogger(CardRepository.class).error("Error opening card repository - " + e, 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() { private static CardInfo safeFindKnownCard() {
// safe find of known card with memory/db fixes // safe find of known card with memory/db fixes
return instance.findCards("Silvercoat Lion", 1, false, false) 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) // 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(); instance.openDB();
cardInfo = safeFindKnownCard(); cardInfo = safeFindKnownCard();
if (cardInfo != null) { if (cardInfo != null) {

View 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);
}
}

View file

@ -27,13 +27,13 @@ public enum ExpansionRepository {
private static final Logger logger = Logger.getLogger(ExpansionRepository.class); 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 String VERSION_ENTITY_NAME = "expansion";
private static final long EXPANSION_DB_VERSION = 5; private static final long EXPANSION_DB_VERSION = 5;
private static final long EXPANSION_CONTENT_VERSION = 18; private static final long EXPANSION_CONTENT_VERSION = 18;
private Dao<ExpansionInfo, Object> expansionDao; private Dao<ExpansionInfo, Object> expansionDao;
private RepositoryEventSource eventSource = new RepositoryEventSource(); private final RepositoryEventSource eventSource = new RepositoryEventSource();
public boolean instanceInitialized = false; public boolean instanceInitialized = false;
ExpansionRepository() { ExpansionRepository() {
@ -42,7 +42,7 @@ public enum ExpansionRepository {
file.mkdirs(); file.mkdirs();
} }
try { 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 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 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() { public long getContentVersionFromDB() {
try { 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"); return RepositoryUtil.getDatabaseVersion(connectionSource, VERSION_ENTITY_NAME + "Content");
} catch (SQLException ex) { } catch (SQLException ex) {
ex.printStackTrace(); ex.printStackTrace();
@ -228,7 +228,7 @@ public enum ExpansionRepository {
public void setContentVersion(long version) { public void setContentVersion(long version) {
try { 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); RepositoryUtil.updateVersion(connectionSource, VERSION_ENTITY_NAME + "Content", version);
} catch (SQLException e) { } catch (SQLException e) {
logger.error("Error setting content version - " + e, e); logger.error("Error setting content version - " + e, e);

View file

@ -10,6 +10,7 @@ import java.util.EventObject;
*/ */
public class RepositoryEvent extends EventObject implements ExternalEvent, Serializable { public class RepositoryEvent extends EventObject implements ExternalEvent, Serializable {
// TODO: db changed on update only, events can be deleted
public enum RepositoryEventType { public enum RepositoryEventType {
DB_LOADED, DB_UPDATED DB_LOADED, DB_UPDATED
} }

View file

@ -7,6 +7,7 @@ import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.stmt.SelectArg; import com.j256.ormlite.stmt.SelectArg;
import com.j256.ormlite.support.ConnectionSource; import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils; import com.j256.ormlite.table.TableUtils;
import mage.util.DebugUtil;
import mage.util.JarVersion; import mage.util.JarVersion;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -39,22 +40,26 @@ public final class RepositoryUtil {
logger.info(" - emblems: " + TokenRepository.instance.getByType(TokenType.EMBLEM).size()); logger.info(" - emblems: " + TokenRepository.instance.getByType(TokenType.EMBLEM).size());
logger.info(" - planes: " + TokenRepository.instance.getByType(TokenType.PLANE).size()); logger.info(" - planes: " + TokenRepository.instance.getByType(TokenType.PLANE).size());
logger.info(" - dungeons: " + TokenRepository.instance.getByType(TokenType.DUNGEON).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 { public static boolean isDatabaseObsolete(ConnectionSource connectionSource, String entityName, long version) throws SQLException {
TableUtils.createTableIfNotExists(connectionSource, DatabaseVersion.class); 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)) queryBuilder.where().eq("entity", new SelectArg(entityName))
.and().eq("version", new SelectArg(version)); .and().eq("version", new SelectArg(version));
List<DatabaseVersion> dbVersions = dbVersionDao.query(queryBuilder.prepare()); List<DatabaseVersion> dbVersions = versionDao.query(queryBuilder.prepare());
if (dbVersions.isEmpty()) { if (dbVersions.isEmpty()) {
DatabaseVersion dbVersion = new DatabaseVersion(); DatabaseVersion dbVersion = new DatabaseVersion();
dbVersion.setEntity(entityName); dbVersion.setEntity(entityName);
dbVersion.setVersion(version); dbVersion.setVersion(version);
dbVersionDao.create(dbVersion); versionDao.create(dbVersion);
} }
return dbVersions.isEmpty(); return dbVersions.isEmpty();
} }
@ -68,48 +73,48 @@ public final class RepositoryUtil {
} }
TableUtils.createTableIfNotExists(connectionSource, DatabaseBuild.class); 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)) queryBuilder.where().eq("entity", new SelectArg(entityName))
.and().eq("last_build", new SelectArg(currentBuild)); .and().eq("last_build", new SelectArg(currentBuild));
List<DatabaseBuild> dbBuilds = dbBuildDao.query(queryBuilder.prepare()); List<DatabaseBuild> dbBuilds = buildDao.query(queryBuilder.prepare());
if (dbBuilds.isEmpty()) { if (dbBuilds.isEmpty()) {
DatabaseBuild dbBuild = new DatabaseBuild(); DatabaseBuild dbBuild = new DatabaseBuild();
dbBuild.setEntity(entityName); dbBuild.setEntity(entityName);
dbBuild.setLastBuild(currentBuild); dbBuild.setLastBuild(currentBuild);
dbBuildDao.create(dbBuild); buildDao.create(dbBuild);
} }
return dbBuilds.isEmpty(); return dbBuilds.isEmpty();
} }
public static void updateVersion(ConnectionSource connectionSource, String entityName, long version) throws SQLException { public static void updateVersion(ConnectionSource connectionSource, String entityName, long version) throws SQLException {
TableUtils.createTableIfNotExists(connectionSource, DatabaseVersion.class); 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)); queryBuilder.where().eq("entity", new SelectArg(entityName));
List<DatabaseVersion> dbVersions = dbVersionDao.query(queryBuilder.prepare()); List<DatabaseVersion> dbVersions = versionDao.query(queryBuilder.prepare());
if (!dbVersions.isEmpty()) { if (!dbVersions.isEmpty()) {
DeleteBuilder<DatabaseVersion, Object> deleteBuilder = dbVersionDao.deleteBuilder(); DeleteBuilder<DatabaseVersion, Object> deleteBuilder = versionDao.deleteBuilder();
deleteBuilder.where().eq("entity", new SelectArg(entityName)); deleteBuilder.where().eq("entity", new SelectArg(entityName));
deleteBuilder.delete(); deleteBuilder.delete();
} }
DatabaseVersion databaseVersion = new DatabaseVersion(); DatabaseVersion databaseVersion = new DatabaseVersion();
databaseVersion.setEntity(entityName); databaseVersion.setEntity(entityName);
databaseVersion.setVersion(version); databaseVersion.setVersion(version);
dbVersionDao.create(databaseVersion); versionDao.create(databaseVersion);
} }
public static long getDatabaseVersion(ConnectionSource connectionSource, String entityName) throws SQLException { public static long getDatabaseVersion(ConnectionSource connectionSource, String entityName) throws SQLException {
TableUtils.createTableIfNotExists(connectionSource, DatabaseVersion.class); 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)); queryBuilder.where().eq("entity", new SelectArg(entityName));
List<DatabaseVersion> dbVersions = dbVersionDao.query(queryBuilder.prepare()); List<DatabaseVersion> dbVersions = versionDao.query(queryBuilder.prepare());
if (dbVersions.isEmpty()) { if (dbVersions.isEmpty()) {
return 0; return 0;
} else { } else {

View file

@ -39,6 +39,18 @@ public class DebugUtil {
// game dialogs // game dialogs
public static boolean GUI_GAME_DIALOGS_DRAW_CARDS_AREA_BORDER = false; 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) { public static String getMethodNameWithSource(final int depth) {
return TraceHelper.getMethodNameWithSource(depth); return TraceHelper.getMethodNameWithSource(depth);
} }