blob: 2eb35d22ebe7c8167ab044f1f7f50e716ab1ad27 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.service.database;
import com.google.common.collect.ImmutableList;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.sql.JDBCType;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
public enum JdbcJavaType {
// Mysql, PostgreSql
BIT(JavaType.BOOLEAN, new DialectType(JDBCType.BIT), new DialectType(JDBCType.BIT, true)) { //
@Override
public Object toJdbcValueImpl(@NotNull DatabaseType dialect, Object value) {
return value == null ? null : (Boolean.TRUE.equals(value) ? 1 : 0);
}
},
BOOLEAN(JavaType.BOOLEAN, new DialectType(JDBCType.BIT), new DialectType(JDBCType.BOOLEAN, null, "BOOL")) { //
@Override
public Object toJdbcValueImpl(@NotNull DatabaseType dialect, Object value) {
return (value != null && dialect.isMySql()) ? (Boolean.TRUE.equals(value) ? 1 : 0) : super.toJdbcValueImpl(dialect, value);
}
},
SMALLINT(JavaType.SHORT, new DialectType(JDBCType.SMALLINT, true), new DialectType(JDBCType.SMALLINT, null, "INT2")), //
TINYINT(JavaType.SHORT, new DialectType(JDBCType.TINYINT, true), new DialectType(JDBCType.SMALLINT)) { //
@Override
public Object toJdbcValueImpl(@NotNull DatabaseType dialect, Object value) {
return dialect.isMySql() && value instanceof Boolean ? (Boolean.TRUE.equals(value) ? 1 : 0)
: super.toJdbcValueImpl(dialect, value);
}
},
INTEGER(JavaType.INT, new DialectType(JDBCType.INTEGER, null, true, "INT"), new DialectType(JDBCType.INTEGER, null, "INT", "INT4")), //
MEDIUMINT(JavaType.INT, new DialectType(JDBCType.INTEGER, "MEDIUMINT", true), new DialectType(JDBCType.INTEGER)), //
BIGINT(JavaType.LONG, new DialectType(JDBCType.BIGINT, true), new DialectType(JDBCType.BIGINT, null, "INT8")), //
REAL(JavaType.FLOAT, new DialectType(JDBCType.FLOAT, true, true), new DialectType(JDBCType.REAL)), //
FLOAT(JavaType.FLOAT, new DialectType(JDBCType.FLOAT, true, true), new DialectType(JDBCType.REAL, null, "FLOAT4")), //
DOUBLE(JavaType.DOUBLE, new DialectType(JDBCType.DOUBLE, null, true, true, "DOUBLE PRECISION", "REAL"),
new DialectType(JDBCType.DOUBLE, "DOUBLE PRECISION", "FLOAT8")), //
NUMERIC(JavaType.BIGDECIMAL, new DialectType(JDBCType.NUMERIC, true, true), new DialectType(JDBCType.NUMERIC, true, true)), //
DECIMAL(JavaType.BIGDECIMAL, new DialectType(JDBCType.DECIMAL, null, true, true, "DEC", "FIXED"),
new DialectType(JDBCType.DECIMAL, true, true)), //
SERIAL(JavaType.INT, new DialectType(JDBCType.INTEGER, "{type} AUTO_INCREMENT", true, false),
new DialectType(JDBCType.INTEGER, "SERIAL", "SERIAL4")), //
SMALLSERIAL(JavaType.SHORT, new DialectType(JDBCType.SMALLINT, "{type} AUTO_INCREMENT", true, false),
new DialectType(JDBCType.SMALLINT, "SMALLSERIAL")), //
BIGSERIAL(JavaType.LONG, new DialectType(JDBCType.BIGINT, "{type} AUTO_INCREMENT", true),
new DialectType(JDBCType.BIGINT, "BIGSERIAL", "SERIAL8")), //
MONEY(JavaType.BIGDECIMAL, new DialectType(JDBCType.DECIMAL, "DECIMAL(19,2)"), new DialectType(JDBCType.DECIMAL, "MONEY")), //
CHAR(JavaType.STRING, new DialectType(JDBCType.CHAR, true), new DialectType(JDBCType.CHAR, null, true, "CHARACTER", "BPCHAR")), //
VARCHAR(JavaType.STRING, new DialectType(JDBCType.VARCHAR, true), new DialectType(JDBCType.VARCHAR, null, true, "CHARACTER VARYING")), //
LONGVARCHAR(JavaType.STRING, new DialectType(JDBCType.VARCHAR, true), new DialectType(JDBCType.VARCHAR, true)), //
TEXT(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "TEXT"), new DialectType(JDBCType.VARCHAR, "TEXT")), //
TINYTEXT(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "TINYTEXT"), new DialectType(JDBCType.VARCHAR, "TEXT")), //
MEDIUMTEXT(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "MEDIUMTEXT"), new DialectType(JDBCType.VARCHAR, "TEXT")), //
LONGTEXT(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "LONGTEXT"), new DialectType(JDBCType.VARCHAR, "TEXT")), //
JSON(JavaType.STRING, new DialectType(JDBCType.VARCHAR, "JSON"), new DialectType(JDBCType.VARCHAR, "JSON")), //
DATE(JavaType.LOCAL_DATE, new DialectType(JDBCType.DATE), new DialectType(JDBCType.DATE)), //
// precision for TIME, TIMESTAMP (postgres) and INTERVAL specifies the number of fractional digits retained in the
// seconds field, but by default, there is no explicit bound on precision
TIME(JavaType.LOCAL_TIME, new DialectType(JDBCType.TIME), new DialectType(JDBCType.TIME, null, "TIME WITHOUT TIME ZONE")), //
TIME_WITH_TIMEZONE(JavaType.OFFSET_TIME, new DialectType(JDBCType.TIME_WITH_TIMEZONE, "TIME"),
new DialectType(JDBCType.TIME_WITH_TIMEZONE, "TIME WITH TIME ZONE")), //
TIMESTAMP(JavaType.LOCAL_DATETIME, new DialectType(JDBCType.TIMESTAMP),
new DialectType(JDBCType.TIMESTAMP, null, "TIMESTAMP WITHOUT TIME ZONE")), //
DATETIME(JavaType.LOCAL_DATETIME, new DialectType(JDBCType.TIMESTAMP, "DATETIME"), new DialectType(JDBCType.TIMESTAMP)), //
TIMESTAMP_WITH_TIMEZONE(JavaType.OFFSET_DATETIME, new DialectType(JDBCType.TIMESTAMP_WITH_TIMEZONE, "DATETIME"),
new DialectType(JDBCType.TIMESTAMP_WITH_TIMEZONE, "TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ")), //
INTERVAL(JavaType.TIME, new DialectType(JDBCType.TIME), new DialectType(JDBCType.TIME, "INTERVAL")), //
BINARY(JavaType.BINARY, new DialectType(JDBCType.BINARY, true), new DialectType(JDBCType.BINARY, "BYTEA")), //
VARBINARY(JavaType.BINARY, new DialectType(JDBCType.VARBINARY, true), new DialectType(JDBCType.VARBINARY, "BYTEA")), //
LONGVARBINARY(JavaType.BINARY, new DialectType(JDBCType.VARBINARY, true), new DialectType(JDBCType.VARBINARY, "BYTEA")), //
BYTEA(JavaType.BINARY, new DialectType(JDBCType.BLOB, "LONGBLOB"), new DialectType(JDBCType.BLOB, "BYTEA")), //
BLOB(JavaType.BINARY, new DialectType(JDBCType.BLOB, "BLOB"), new DialectType(JDBCType.BLOB, "BYTEA")), //
TINYBLOB(JavaType.BINARY, new DialectType(JDBCType.BLOB, "TINYBLOB"), new DialectType(JDBCType.BLOB, "BYTEA")), //
MEDIUMBLOB(JavaType.BINARY, new DialectType(JDBCType.BLOB, "MEDIUMBLOB"), new DialectType(JDBCType.BLOB, "BYTEA")), //
LONGBLOB(JavaType.BINARY, new DialectType(JDBCType.BLOB, "LONGBLOB"), new DialectType(JDBCType.BLOB, "BYTEA")), //
;
// /* Not used types */
// /* *** java.sql.JDBCType *** */
// NULL, OTHER, JAVA_OBJECT, DISTINCT, STRUCT, ARRAY, CLOB, REF, DATALINK
// /* JDBC 4.0 Types */
// ROWID, NCHAR(Types.NCHAR), NVARCHAR, NCLOB, SQLXML
// /* JDBC 4.2 Types */
// REF_CURSOR, TIME_WITH_TIMEZONE, TIMESTAMP_WITH_TIMEZONE
// /* *** POSTGRES *** */
// bit varying [(n)] / varbit, box, cidr, circle, inet, interval, line, lseg, macaddr, path, point, polygon, time
// [(p)] with time zone, timez,
// timestamp [(p)] with time zone, timestamptz, bit(n), bit varying(n), array[n]
// /* *** MYSQL *** */
// year, enum, set, spatial types, JSON
private static final String PLACEHOLDER_TYPE = "{type}";
@NotNull
private final JavaType javaType;
private final ImmutableList<DialectType> dialectTypes;
JdbcJavaType(@NotNull JavaType javaType, DialectType... dialectTypes) {
this.javaType = javaType;
this.dialectTypes = ImmutableList.copyOf(dialectTypes);
}
@NotNull
public JavaType getJavaType() {
return javaType;
}
public static JdbcJavaType getByTypeName(@NotNull DatabaseType dialect, String name, boolean check) {
if (name == null) {
return null;
}
name = name.toUpperCase();
for (JdbcJavaType type : values()) {
DialectType dialectType = type.getDialectType(dialect);
if (dialectType.getNameResolved().equals(name)) {
// NOTE: make MySQL systems happy aka TINYINT vs BOOLEAN issue!
if (type.canBooleanType(dialect)) {
return BOOLEAN;
}
return type;
}
if (dialectType.alterNames != null) {
for (String alterName : dialectType.alterNames) {
if (alterName.equals(name)) {
// NOTE: make MySQL systems happy aka TINYINT vs BOOLEAN issue!
if (type.canBooleanType(dialect)) {
return BOOLEAN;
}
return type;
}
}
}
}
if (check) {
throw new PlatformServiceUnavailableException("error.msg.database.type.not.supported",
"Data type '" + name + "' is not supported ");
}
return null;
}
@NotNull
private DialectType getDialectType(@NotNull DatabaseType dialect) {
DialectType dialectType = dialectTypes.get(dialect.ordinal());
if (dialectType == null) {
throw new PlatformServiceUnavailableException("error.msg.database.dialect.not.allowed",
"Dialect " + dialect + " is not supported for " + this);
}
return dialectType;
}
public boolean isBooleanType() {
return getJavaType().isBooleanType();
}
public boolean canBooleanType(@NotNull DatabaseType dialect) {
return isBooleanType() || (dialect.isMySql() && this == TINYINT);
}
public boolean isStringType() {
// ("char", "varchar", "blob", "text", "tinyblob", "tinytext", "mediumblob", "mediumtext", "longblob",
// "longtext")
return getJavaType().isStringType();
}
public boolean isVarcharType() {
// ("char", "varchar", "blob", "text", "tinyblob", "tinytext", "mediumblob", "mediumtext", "longblob",
// "longtext")
return this == VARCHAR || this == LONGVARCHAR;
}
public boolean isTextType() {
return this == TEXT || this == TINYTEXT || this == MEDIUMTEXT || this == LONGTEXT || this == JSON;
}
public boolean isJsonType() {
return this == JSON;
}
public boolean isSerialType() {
return this == SERIAL || this == SMALLSERIAL || this == BIGSERIAL;
}
public boolean isIntegerType() {
return getJavaType().isIntegerType();
}
public boolean isAnyIntegerType() {
return getJavaType().isAnyIntegerType();
}
public boolean isFloatType() {
return getJavaType().isFloatType();
}
public boolean isAnyFloatType() {
return getJavaType().isAnyFloatType();
}
public boolean isDecimalType() {
return getJavaType().isDecimalType();
}
public boolean isNumericType() {
return getJavaType().isNumericType();
}
public boolean isDateType() {
return getJavaType().isDateType();
}
public boolean isTimeType() {
return getJavaType().isTimeType();
}
public boolean isDateTimeType() {
return getJavaType().isDateTimeType();
}
public boolean isAnyDateType() {
return getJavaType().isAnyDateType();
}
public boolean isBlobType() {
return this == TINYBLOB || this == MEDIUMBLOB || this == BLOB || this == LONGBLOB;
}
public boolean isBinaryType() {
return getJavaType().isBinaryType();
}
public boolean hasPrecision(@NotNull DatabaseType dialect) {
return getDialectType(dialect).precision;
}
public boolean hasScale(@NotNull DatabaseType dialect) {
return getDialectType(dialect).scale;
}
public String getJdbcName(@NotNull DatabaseType dialect) {
DialectType dialectType = getDialectType(dialect);
return dialectType.getNameResolved();
}
public String formatSql(@NotNull DatabaseType dialect, Integer precision) {
return formatSql(dialect, precision, null);
}
public String formatSql(@NotNull DatabaseType dialect) {
return formatSql(dialect, null, null);
}
public String formatSql(@NotNull DatabaseType dialect, Integer precision, Integer scale) {
DialectType dialectType = getDialectType(dialect);
return dialectType.formatSql(precision, scale);
}
public Object toJdbcValue(@NotNull DatabaseType dialect, Object value, boolean check) {
if (value != null && check && !javaType.getObjectType().matchType(value.getClass(), false)) {
throw new PlatformServiceUnavailableException("error.msg.database.type.not.valid",
"Data type of parameter " + value + " does not match " + this);
}
return toJdbcValueImpl(dialect, value);
}
public Object toJdbcValueImpl(@NotNull DatabaseType dialect, Object value) {
return value;
}
@com.google.errorprone.annotations.Immutable
private static final class DialectType implements Serializable {
@NotNull
private final JDBCType jdbcType;
@NotNull
private final String name;
private final boolean precision;
private final boolean scale;
private final ImmutableList<String> alterNames;
private DialectType(@NotNull JDBCType jdbcType, String name, boolean precision, boolean scale, String... alterNames) {
this.jdbcType = jdbcType;
this.name = name == null ? jdbcType.getName() : name;
this.precision = precision;
this.scale = scale;
this.alterNames = ImmutableList.copyOf(alterNames);
}
private DialectType(@NotNull JDBCType jdbcType, String name, boolean precision, String... alterNames) {
this(jdbcType, name, precision, false, alterNames);
}
private DialectType(@NotNull JDBCType jdbcType, String name, boolean precision) {
this(jdbcType, name, precision, false);
}
private DialectType(@NotNull JDBCType jdbcType, String name, String... alterNames) {
this(jdbcType, name, false, false, alterNames);
}
private DialectType(@NotNull JDBCType jdbcType, boolean precision, boolean scale) {
this(jdbcType, null, precision, scale);
}
private DialectType(@NotNull JDBCType jdbcType, boolean precision) {
this(jdbcType, null, precision, false);
}
private DialectType(@NotNull JDBCType jdbcType) {
this(jdbcType, null, false, false);
}
private String getNameResolved() {
return name.replace(PLACEHOLDER_TYPE, jdbcType.getName());
}
private String formatSql(Integer precision, Integer scale) {
int idx = name.indexOf(PLACEHOLDER_TYPE);
if (idx >= 0) {
return name.substring(0, idx) + addSqlPrecisionScale(jdbcType.getName(), precision, scale)
+ name.substring(idx + PLACEHOLDER_TYPE.length());
} else {
return addSqlPrecisionScale(name, precision, scale);
}
}
private String addSqlPrecisionScale(@NotNull String name, Integer precision, Integer scale) {
if (!this.precision || precision == null) {
return name;
}
String sql = name + '(' + precision;
if (this.scale || scale != null) {
sql = sql + ',' + scale;
}
sql += ')';
return sql;
}
}
}