is.codion.dbms.h2.H2Database Maven / Gradle / Ivy
/*
* This file is part of Codion.
*
* Codion is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Codion is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Codion. If not, see .
*
* Copyright (c) 2009 - 2024, Björn Darri Sigurðsson.
*/
package is.codion.dbms.h2;
import is.codion.common.db.database.AbstractDatabase;
import is.codion.common.resource.MessageBundle;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import static is.codion.common.resource.MessageBundle.messageBundle;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static java.util.ResourceBundle.getBundle;
/**
* A Database implementation based on the H2 database.
*/
final class H2Database extends AbstractDatabase {
private static final MessageBundle MESSAGES =
messageBundle(H2Database.class, getBundle(H2Database.class.getName()));
/**
* The error code representing incorrect login credentials
*/
private static final int AUTHENTICATION_ERROR = 28000;
private static final int REFERENTIAL_INTEGRITY_ERROR_CHILD_EXISTS = 23503;
private static final int REFERENTIAL_INTEGRITY_ERROR_PARENT_MISSING = 23506;
private static final int UNIQUE_CONSTRAINT_ERROR = 23505;
private static final int TIMEOUT_ERROR = 57014;
private static final int NULL_NOT_ALLOWED = 23502;
private static final int CHECK_CONSTRAINT_INVALID = 23514;
private static final int WRONG_USER_OR_PASSWORD = 28000;
private static final Set INITIALIZED_DATABASES = new HashSet<>();
private static final String JDBC_URL_PREFIX = "jdbc:h2:";
private static final String JDBC_URL_PREFIX_MEM = "jdbc:h2:mem:";
private static final String JDBC_URL_PREFIX_FILE = "jdbc:h2:file:";
private static final String JDBC_URL_PREFIX_TCP = "jdbc:h2:tcp://";
private static final String JDBC_URL_PREFIX_SSL = "jdbc:h2:ssl:";
private static final String JDBC_URL_PREFIX_ZIP = "jdbc:h2:zip:";
private static final String FILE_SUFFIX_PAGESTORE = ".h2.db";
private static final String FILE_SUFFIX_MVSTORE = ".mv.db";
static final String AUTO_INCREMENT_QUERY = "CALL IDENTITY()";
static final String SEQUENCE_VALUE_QUERY = "select next value for ";
static final String SYSADMIN_USERNAME = "sa";
private static final Map ERROR_MESSAGES = new HashMap<>();
static {
ERROR_MESSAGES.put(UNIQUE_CONSTRAINT_ERROR, MESSAGES.getString("unique_key_error"));
ERROR_MESSAGES.put(REFERENTIAL_INTEGRITY_ERROR_CHILD_EXISTS, MESSAGES.getString("child_record_error"));
ERROR_MESSAGES.put(REFERENTIAL_INTEGRITY_ERROR_PARENT_MISSING, MESSAGES.getString("integrity_constraint_error"));
ERROR_MESSAGES.put(NULL_NOT_ALLOWED, MESSAGES.getString("value_missing"));
ERROR_MESSAGES.put(CHECK_CONSTRAINT_INVALID, MESSAGES.getString("check_constraint_invalid"));
ERROR_MESSAGES.put(WRONG_USER_OR_PASSWORD, MESSAGES.getString("wrong_user_or_password"));
}
private final boolean nowait;
H2Database(String url) {
this(url, emptyList());
}
H2Database(String url, List scriptPaths) {
this(url, scriptPaths, true);
}
H2Database(String url, List scriptPaths, boolean nowait) {
super(url);
this.nowait = nowait;
synchronized (INITIALIZED_DATABASES) {
if (!INITIALIZED_DATABASES.contains(url.toLowerCase())) {
initializeEmbeddedDatabase(scriptPaths);
}
}
}
@Override
public String name() {
return databaseName(url());
}
@Override
public String selectForUpdateClause() {
if (nowait) {
return FOR_UPDATE_NOWAIT;
}
return FOR_UPDATE;
}
@Override
public String limitOffsetClause(Integer limit, Integer offset) {
return createLimitOffsetClause(limit, offset);
}
@Override
public String autoIncrementQuery(String idSource) {
return AUTO_INCREMENT_QUERY;
}
@Override
public String sequenceQuery(String sequenceName) {
return SEQUENCE_VALUE_QUERY + requireNonNull(sequenceName);
}
@Override
public boolean isAuthenticationException(SQLException exception) {
return requireNonNull(exception).getErrorCode() == AUTHENTICATION_ERROR;
}
@Override
public boolean isReferentialIntegrityException(SQLException exception) {
return requireNonNull(exception).getErrorCode() == REFERENTIAL_INTEGRITY_ERROR_CHILD_EXISTS ||
exception.getErrorCode() == REFERENTIAL_INTEGRITY_ERROR_PARENT_MISSING;
}
@Override
public boolean isUniqueConstraintException(SQLException exception) {
return requireNonNull(exception).getErrorCode() == UNIQUE_CONSTRAINT_ERROR;
}
@Override
public boolean isTimeoutException(SQLException exception) {
return requireNonNull(exception).getErrorCode() == TIMEOUT_ERROR;
}
@Override
public String errorMessage(SQLException exception, Operation operation) {
if (exception.getErrorCode() == NULL_NOT_ALLOWED) {
// NULL not allowed for column "NAME;"
String exceptionMessage = exception.getMessage();
String columnName = exceptionMessage.substring(exceptionMessage.indexOf('"') + 1, exceptionMessage.lastIndexOf('"'));
return MESSAGES.getString("value_missing") + ": " + columnName;
}
if (ERROR_MESSAGES.containsKey(exception.getErrorCode())) {
return ERROR_MESSAGES.get(exception.getErrorCode());
}
return exception.getMessage();
}
static String databaseName(String url) {
String name = removeUrlPrefixOptionsAndParameters(url, JDBC_URL_PREFIX_TCP, JDBC_URL_PREFIX_FILE,
JDBC_URL_PREFIX_MEM, JDBC_URL_PREFIX_SSL, JDBC_URL_PREFIX_ZIP, JDBC_URL_PREFIX);
return name.isEmpty() ? "private" : name;
}
private void initializeEmbeddedDatabase(List scriptPaths) {
if ((isEmbeddedInMemory() || !databaseFileExists())) {
Properties properties = new Properties();
properties.put(USER_PROPERTY, SYSADMIN_USERNAME);
if (scriptPaths.isEmpty()) {
initialize(properties, ";DB_CLOSE_DELAY=-1");
}
else {
for (String scriptPath : scriptPaths) {
initialize(properties, ";DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM '" + scriptPath.replace("\\", "/") + "'");
}
}
}
INITIALIZED_DATABASES.add(url().toLowerCase());
}
private String databasePath() {
return removeUrlPrefixOptionsAndParameters(url(), JDBC_URL_PREFIX_FILE, JDBC_URL_PREFIX);
}
private boolean isEmbeddedInMemory() {
return url().startsWith(JDBC_URL_PREFIX_MEM);
}
private boolean databaseFileExists() {
return Files.exists(Paths.get(databasePath() + FILE_SUFFIX_PAGESTORE)) ||
Files.exists(Paths.get(databasePath() + FILE_SUFFIX_MVSTORE));
}
private void initialize(Properties properties, String appendToUrl) {
try {
DriverManager.getConnection(url() + appendToUrl, properties).close();
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy