FINERACT-1911: Assign Data Table to Transaction - general

This commit is contained in:
jmarta
2023-08-17 09:25:51 +02:00
committed by Adam Saghy
parent 2275f9c301
commit b616a96fc9
38 changed files with 2419 additions and 1360 deletions
@@ -69,16 +69,12 @@ public final class ApiParameterError {
public static ApiParameterError parameterError(final String globalisationMessageCode, final String defaultUserMessage,
final String parameterName, final Object... defaultUserMessageArgs) {
final ApiParameterError error = new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs,
parameterName, null);
return error;
return new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs, parameterName, null);
}
public static ApiParameterError parameterErrorWithValue(final String globalisationMessageCode, final String defaultUserMessage,
final String parameterName, final String value, final Object... defaultUserMessageArgs) {
final ApiParameterError error = new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs,
parameterName, value);
return error;
return new ApiParameterError(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs, parameterName, value);
}
private ApiParameterError(final String globalisationMessageCode, final String defaultUserMessage, final Object[] defaultUserMessageArgs,
@@ -25,18 +25,21 @@ import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Locale;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.jetbrains.annotations.NotNull;
public final class DateUtils {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATETIME_FORMAT = DEFAULT_DATE_FORMAT + " HH:mm:ss";
public static final DateTimeFormatter DEFAULT_DATETIME_FORMATTER = new DateTimeFormatterBuilder().appendPattern(DEFAULT_DATETIME_FORMAT)
.toFormatter();
public static final DateTimeFormatter DEFAULT_DATE_FORMATTER = new DateTimeFormatterBuilder().appendPattern(DEFAULT_DATE_FORMAT)
.toFormatter();
public static final DateTimeFormatter DEFAULT_DATE_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT);
public static final DateTimeFormatter DEFAULT_DATETIME_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_DATETIME_FORMAT);
private DateUtils() {
@@ -86,4 +89,106 @@ public final class DateUtils {
public static long getDifferenceInDays(final LocalDate localDateBefore, final LocalDate localDateAfter) {
return DAYS.between(localDateBefore, localDateAfter);
}
public static int compareToBusinessDate(LocalDate date) {
return compare(date, getBusinessLocalDate());
}
public static boolean isEqualBusinessDate(LocalDate date) {
return isEqual(date, getBusinessLocalDate());
}
public static boolean isBeforeBusinessDate(LocalDate date) {
return isBefore(date, getBusinessLocalDate());
}
public static boolean isAfterBusinessDate(LocalDate date) {
return isAfter(date, getBusinessLocalDate());
}
public static int compare(LocalDate first, LocalDate second) {
return first == null ? (second == null ? 0 : -1) : first.compareTo(second);
}
public static boolean isEqual(LocalDate first, LocalDate second) {
return first == null ? second == null : (second != null && first.isEqual(second));
}
public static boolean isBefore(LocalDate first, LocalDate second) {
return second != null && (first == null || first.isBefore(second));
}
public static boolean isAfter(LocalDate first, LocalDate second) {
return first != null && (second == null || first.isAfter(second));
}
public static LocalDate parseLocalDate(String stringDate) {
return parseLocalDate(stringDate, null);
}
public static LocalDate parseLocalDate(String stringDate, String format) {
return parseLocalDate(stringDate, format, null);
}
public static LocalDate parseLocalDate(String stringDate, String format, Locale locale) {
if (stringDate == null) {
return null;
}
DateTimeFormatter formatter = getDateFormatter(format, locale);
try {
return LocalDate.parse(stringDate, formatter);
} catch (final DateTimeParseException e) {
final List<ApiParameterError> errors = List.of(ApiParameterError.parameterError("validation.msg.invalid.date.pattern",
"The parameter date (" + stringDate + ") format is invalid", "date", stringDate));
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", errors, e);
}
}
public static String format(LocalDate date) {
return format(date, null);
}
public static String format(LocalDate date, String format) {
return format(date, format, null);
}
public static String format(LocalDate date, String format, Locale locale) {
return date == null ? null : date.format(getDateFormatter(format, locale));
}
public static String format(LocalDateTime dateTime) {
return format(dateTime, null);
}
public static String format(LocalDateTime dateTime, String format) {
return format(dateTime, format, null);
}
public static String format(LocalDateTime dateTime, String format, Locale locale) {
return dateTime == null ? null : dateTime.format(getDateTimeFormatter(format, locale));
}
@NotNull
private static DateTimeFormatter getDateFormatter(String format, Locale locale) {
DateTimeFormatter formatter = DEFAULT_DATE_FORMATTER;
if (format != null || locale != null) {
if (format == null) {
format = DEFAULT_DATE_FORMAT;
}
formatter = locale == null ? DateTimeFormatter.ofPattern(format) : DateTimeFormatter.ofPattern(format, locale);
}
return formatter;
}
@NotNull
private static DateTimeFormatter getDateTimeFormatter(String format, Locale locale) {
DateTimeFormatter formatter = DEFAULT_DATETIME_FORMATTER;
if (format != null || locale != null) {
if (format == null) {
format = DEFAULT_DATETIME_FORMAT;
}
formatter = locale == null ? DateTimeFormatter.ofPattern(format) : DateTimeFormatter.ofPattern(format, locale);
}
return formatter;
}
}
@@ -0,0 +1,445 @@
/**
* 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;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.math.MathContext;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
public final class MathUtil {
private MathUtil() {
}
public static <E extends Number> E nullToDefault(E value, E def) {
return value == null ? def : value;
}
public static Long nullToZero(Long value) {
return nullToDefault(value, 0L);
}
public static Long nullToDefault(Long value, Long def) {
return value == null ? def : value;
}
public static Long zeroToNull(Long value) {
return isEmpty(value) ? null : value;
}
/** @return parameter value or ZERO if it is negative */
public static Long negativeToZero(Long value) {
return isGreaterThanZero(value) ? value : 0L;
}
public static boolean isEmpty(Long value) {
return value == null || value.equals(0L);
}
public static boolean isGreaterThanZero(Long value) {
return value != null && value > 0L;
}
public static boolean isLessThanZero(Long value) {
return value != null && value < 0L;
}
public static boolean isZero(Long value) {
return value != null && value.equals(0L);
}
public static boolean isEqualTo(Long first, Long second) {
return nullToZero(first).equals(nullToZero(second));
}
public static boolean isGreaterThan(Long first, Long second) {
return nullToZero(first) > nullToZero(second);
}
public static boolean isLessThan(Long first, Long second) {
return nullToZero(first) < nullToZero(second);
}
public static boolean isGreaterThanOrEqualTo(Long first, Long second) {
return nullToZero(first) >= nullToZero(second);
}
public static boolean isLessThanOrEqualZero(Long value) {
return nullToZero(value) <= 0L;
}
/** @return parameter value or negated value to positive */
public static Long abs(Long value) {
return value == null ? 0L : Math.abs(value);
}
/**
* @return calculates minimum of the two values considering null values
* @param notNull
* if true then null parameter is omitted, otherwise returns null
*/
public static Long min(Long first, Long second, boolean notNull) {
if (first == null) {
return notNull ? second : null;
}
if (second == null) {
return notNull ? first : null;
}
return Math.min(first, second);
}
/**
* @return calculates minimum of the values considering null values
* @param notNull
* if true then null parameter is omitted, otherwise returns null
*/
public static Long min(Long first, Long second, Long third, boolean notNull) {
return min(min(first, second, notNull), third, notNull);
}
/** @return sum the two values considering null values */
public static Long add(Long first, Long second) {
return first == null ? second : second == null ? first : Math.addExact(first, second);
}
/** @return sum the values considering null values */
public static Long add(Long first, Long second, Long third) {
return add(add(first, second), third);
}
/** @return sum the values considering null values */
public static Long add(Long first, Long second, Long third, Long fourth) {
return add(add(add(first, second), third), fourth);
}
/** @return sum the values considering null values */
public static Long add(Long first, Long second, Long third, Long fourth, Long fifth) {
return add(add(add(add(first, second), third), fourth), fifth);
}
/** @return first minus second considering null values, maybe negative */
public static Long subtract(Long first, Long second) {
return first == null ? null : second == null ? first : Math.subtractExact(first, second);
}
/**
* @return first minus the others considering null values, maybe negative
*/
public static Long subtractToZero(Long first, Long second, Long third) {
return subtractToZero(subtract(first, second), third);
}
/**
* @return first minus the others considering null values, maybe negative
*/
public static Long subtractToZero(Long first, Long second, Long third, Long fourth) {
return subtractToZero(subtract(subtract(first, second), third), fourth);
}
/** @return NONE negative first minus second considering null values */
public static Long subtractToZero(Long first, Long second) {
return negativeToZero(subtract(first, second));
}
/** @return BigDecimal null safe negate */
public static Long negate(Long amount) {
return isEmpty(amount) ? amount : Math.negateExact(amount);
}
// ----------------- BigDecimal -----------------
public static BigDecimal nullToZero(BigDecimal value) {
return nullToDefault(value, BigDecimal.ZERO);
}
public static BigDecimal nullToDefault(BigDecimal value, BigDecimal def) {
return value == null ? def : value;
}
public static BigDecimal zeroToNull(BigDecimal value) {
return isEmpty(value) ? null : value;
}
/** @return parameter value or ZERO if it is negative */
public static BigDecimal negativeToZero(BigDecimal value) {
return isGreaterThanZero(value) ? value : BigDecimal.ZERO;
}
public static boolean isEmpty(BigDecimal value) {
return value == null || BigDecimal.ZERO.compareTo(value) == 0;
}
public static boolean isGreaterThanZero(BigDecimal value) {
return value != null && value.compareTo(BigDecimal.ZERO) > 0;
}
public static boolean isLessThanZero(BigDecimal value) {
return value != null && value.compareTo(BigDecimal.ZERO) < 0;
}
public static boolean isZero(BigDecimal value) {
return value != null && value.compareTo(BigDecimal.ZERO) == 0;
}
public static boolean isEqualTo(BigDecimal first, BigDecimal second) {
return nullToZero(first).compareTo(nullToZero(second)) == 0;
}
public static boolean isGreaterThan(BigDecimal first, BigDecimal second) {
return nullToZero(first).compareTo(nullToZero(second)) > 0;
}
public static boolean isLessThan(BigDecimal first, BigDecimal second) {
return nullToZero(first).compareTo(nullToZero(second)) < 0;
}
public static boolean isGreaterThanOrEqualTo(BigDecimal first, BigDecimal second) {
return nullToZero(first).compareTo(nullToZero(second)) >= 0;
}
public static boolean isLessThanOrEqualZero(BigDecimal value) {
return nullToZero(value).compareTo(BigDecimal.ZERO) <= 0;
}
/** @return parameter value or negated value to positive */
public static BigDecimal abs(BigDecimal value) {
return value == null ? BigDecimal.ZERO : value.abs();
}
/**
* @return calculates minimum of the two values considering null values
* @param notNull
* if true then null parameter is omitted, otherwise returns null
*/
public static BigDecimal min(BigDecimal first, BigDecimal second, boolean notNull) {
return notNull ? first == null ? second : second == null ? first : min(first, second, false)
: isLessThan(first, second) ? first : second;
}
/**
* @return calculates minimum of the values considering null values
* @param notNull
* if true then null parameter is omitted, otherwise returns null
*/
public static BigDecimal min(BigDecimal first, BigDecimal second, BigDecimal third, boolean notNull) {
return min(min(first, second, notNull), third, notNull);
}
/** @return sum the two values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second) {
return add(first, second, MoneyHelper.getMathContext());
}
/** @return sum the two values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, MathContext mc) {
return first == null ? second : second == null ? first : first.add(second, mc);
}
/** @return sum the values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third) {
return add(first, second, third, MoneyHelper.getMathContext());
}
/** @return sum the values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, MathContext mc) {
return add(add(first, second, mc), third, mc);
}
/** @return sum the values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth) {
return add(first, second, third, fourth, MoneyHelper.getMathContext());
}
/** @return sum the values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth, MathContext mc) {
return add(add(add(first, second, mc), third, mc), fourth, mc);
}
/** @return sum the values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth, BigDecimal fifth) {
return add(first, second, third, fourth, fifth, MoneyHelper.getMathContext());
}
/** @return sum the values considering null values */
public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth, BigDecimal fifth,
MathContext mc) {
return add(add(add(add(first, second, mc), third, mc), fourth, mc), fifth, mc);
}
/** @return first minus second considering null values, maybe negative */
public static BigDecimal subtract(BigDecimal first, BigDecimal second) {
return subtract(first, second, MoneyHelper.getMathContext());
}
/** @return first minus second considering null values, maybe negative */
public static BigDecimal subtract(BigDecimal first, BigDecimal second, MathContext mc) {
return first == null ? null : second == null ? first : first.subtract(second, mc);
}
/** @return NONE negative first minus second considering null values */
public static BigDecimal subtractToZero(BigDecimal first, BigDecimal second) {
return negativeToZero(subtract(first, second));
}
/**
* @return first minus the others considering null values, maybe negative
*/
public static BigDecimal subtractToZero(BigDecimal first, BigDecimal second, BigDecimal third) {
return subtractToZero(subtract(first, second), third);
}
/**
* @return first minus the others considering null values, maybe negative
*/
public static BigDecimal subtractToZero(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth) {
return subtractToZero(subtract(subtract(first, second), third), fourth);
}
/**
* @return BigDecimal with scale set to the 'digitsAfterDecimal' of the parameter currency
*/
public static BigDecimal normalizeAmount(BigDecimal amount, @NotNull MonetaryCurrency currency) {
return amount == null ? null : amount.setScale(currency.getDigitsAfterDecimal(), MoneyHelper.getRoundingMode());
}
/** @return BigDecimal null safe negate */
public static BigDecimal negate(BigDecimal amount) {
return negate(amount, MoneyHelper.getMathContext());
}
/** @return BigDecimal null safe negate */
public static BigDecimal negate(BigDecimal amount, MathContext mc) {
return isEmpty(amount) ? amount : amount.negate(mc);
}
public static String formatToSql(BigDecimal amount) {
return amount == null ? null : amount.toPlainString();
}
// ----------------- Money -----------------
public static Money nullToZero(Money value, @NotNull MonetaryCurrency currency) {
return nullToDefault(value, Money.zero(currency));
}
public static Money nullToDefault(Money value, Money def) {
return value == null ? def : value;
}
public static Money zeroToNull(Money value) {
return isEmpty(value) ? null : value;
}
/** @return parameter value or ZERO if it is negative */
public static Money negativeToZero(Money value) {
return value == null || isGreaterThanZero(value) ? value : Money.zero(value.getCurrency());
}
public static boolean isEmpty(Money value) {
return value == null || value.isZero();
}
public static boolean isGreaterThanZero(Money value) {
return value != null && value.isGreaterThanZero();
}
public static boolean isLessThanZero(Money value) {
return value != null && value.isLessThanZero();
}
public static boolean isEqualTo(Money first, Money second) {
return first == null ? second == null : (second != null && first.isEqualTo(second));
}
public static boolean isGreaterThan(Money first, Money second) {
return second == null || (first != null && first.isGreaterThan(second));
}
public static boolean isLessThan(Money first, Money second) {
return first == null || (second != null && first.isLessThan(second));
}
public static Money plus(Money first, Money second) {
return first == null ? second : second == null ? first : first.plus(second);
}
public static Money plus(Money first, Money second, Money third) {
return plus(plus(first, second), third);
}
public static Money plus(Money first, Money second, Money third, Money fourth) {
return plus(plus(plus(first, second), third), fourth);
}
public static Money minus(Money first, Money second) {
return first == null ? null : second == null ? first : first.minus(second);
}
/**
* @return first minus the others considering null values, maybe negative
*/
public static Money minusToZero(Money first, Money second, Money third) {
return minusToZero(minus(first, second), third);
}
/**
* @return first minus the others considering null values, maybe negative
*/
public static Money minusToZero(Money first, Money second, Money third, Money fourth) {
return minusToZero(minus(minus(first, second), third), fourth);
}
/** @return NONE negative first minus second considering null values */
public static Money minusToZero(Money first, Money second) {
return negativeToZero(minus(first, second));
}
/**
* @return calculates minimum of the two values considering null values
* @param notNull
* if true then null parameter is omitted, otherwise returns null
*/
public static Money min(Money first, Money second, boolean notNull) {
return notNull ? first == null ? second : second == null ? first : min(first, second, false)
: isLessThan(first, second) ? first : second;
}
/**
* @return calculates minimum of the values considering null values
* @param notNull
* if true then null parameter is omitted, otherwise returns null
*/
public static Money min(Money first, Money second, Money third, boolean notNull) {
return min(min(first, second, notNull), third, notNull);
}
public static BigDecimal percentageOf(final BigDecimal value, final BigDecimal percentage, final int precision) {
BigDecimal percentageOf = BigDecimal.ZERO;
if (isGreaterThanZero(value)) {
final MathContext mc = new MathContext(precision, MoneyHelper.getRoundingMode());
final BigDecimal multiplicand = percentage.divide(BigDecimal.valueOf(100L), mc);
percentageOf = value.multiply(multiplicand, mc);
}
return percentageOf;
}
}
@@ -20,8 +20,14 @@ package org.apache.fineract.infrastructure.core.service.database;
import static java.lang.String.format;
import jakarta.validation.constraints.NotNull;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
@Component
@@ -36,14 +42,19 @@ public class DatabaseSpecificSQLGenerator {
}
public String escape(String arg) {
if (databaseTypeResolver.isMySQL()) {
DatabaseType dialect = databaseTypeResolver.databaseType();
if (dialect.isMySql()) {
return format("`%s`", arg);
} else if (databaseTypeResolver.isPostgreSQL()) {
} else if (dialect.isPostgres()) {
return format("\"%s\"", arg);
}
return arg;
}
public String formatValue(JdbcJavaType columnType, String value) {
return (columnType.isStringType() || columnType.isAnyDateType()) ? format("'%s'", value) : value;
}
public String groupConcat(String arg) {
if (databaseTypeResolver.isMySQL()) {
return format("GROUP_CONCAT(%s)", arg);
@@ -189,4 +200,49 @@ public class DatabaseSpecificSQLGenerator {
throw new IllegalStateException("Database type is not supported for casting to json " + databaseTypeResolver.databaseType());
}
}
public String alias(@NotNull String field, String alias) {
return Strings.isEmpty(alias) ? field : (alias + '.') + field;
}
public String buildSelect(Collection<String> fields, String alias, boolean embedded) {
if (fields == null || fields.isEmpty()) {
return "";
}
String select = "";
if (!embedded) {
select = "SELECT ";
}
return select + fields.stream().map(e -> alias(escape(e), alias)).collect(Collectors.joining(", "));
}
public String buildFrom(String definition, String alias, boolean embedded) {
if (definition == null) {
return "";
}
String from = "";
if (!embedded) {
from = "FROM ";
}
return from + escape(definition) + (Strings.isEmpty(alias) ? "" : (" " + alias));
}
public String buildJoin(@NotNull String definition, String alias, @NotNull String fkCol, String refAlias, @NotNull String refCol,
String joinType) {
String join = Strings.isEmpty(joinType) ? "JOIN" : (joinType + " JOIN");
alias = Strings.isEmpty(alias) ? "" : (" " + alias);
return format("%s %s%s ON %s = %s", join, escape(definition), alias, alias(escape(fkCol), alias), alias(escape(refCol), refAlias));
}
public String buildOrderBy(List<Sort.Order> orders, String alias, boolean embedded) {
if (orders == null || orders.isEmpty()) {
return "";
}
String orderBy = "";
if (!embedded) {
orderBy = "ORDER BY ";
}
return orderBy + orders.stream().map(e -> String.join(" ", alias(escape(e.getProperty()), alias), e.getDirection().name()))
.collect(Collectors.joining(", "));
}
}
@@ -19,5 +19,16 @@
package org.apache.fineract.infrastructure.core.service.database;
public enum DatabaseType {
MYSQL, POSTGRESQL
MYSQL, //
POSTGRESQL, //
;
public boolean isMySql() {
return this == MYSQL;
}
public boolean isPostgres() {
return this == POSTGRESQL;
}
}
@@ -58,10 +58,10 @@ public class DatabaseTypeResolver implements InitializingBean {
}
public boolean isPostgreSQL() {
return DatabaseType.POSTGRESQL.equals(currentDatabaseType.get());
return DatabaseType.POSTGRESQL == currentDatabaseType.get();
}
public boolean isMySQL() {
return DatabaseType.MYSQL.equals(currentDatabaseType.get());
return DatabaseType.MYSQL == currentDatabaseType.get();
}
}
@@ -0,0 +1,371 @@
/**
* 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 jakarta.validation.constraints.NotNull;
import java.io.InputStream;
import java.io.Reader;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.springframework.data.domain.Persistable;
public enum JavaType {
BOOLEAN(boolean.class), //
BYTE(byte.class), //
SHORT(short.class), //
INT(int.class), //
LONG(long.class), //
CHAR(char.class), //
FLOAT(float.class), //
DOUBLE(double.class), //
// non-primitive types
BOOLEAN_OBJ(Boolean.class), //
BYTE_OBJ(Byte.class), //
SHORT_OBJ(Short.class), //
INT_OBJ(Integer.class), //
LONG_OBJ(Long.class), //
CHAR_OBJ(Character.class), //
FLOAT_OBJ(Float.class), //
DOUBLE_OBJ(Double.class), //
BIGDECIMAL(BigDecimal.class), //
BIGINTEGER(BigInteger.class), //
NUMBER(Number.class), //
STRING(String.class), //
DATETIME(Timestamp.class), //
TIME(Time.class), //
DATE(Date.class), //
// Java8 time API
LOCAL_DATE(LocalDate.class), //
LOCAL_TIME(LocalTime.class), //
LOCAL_DATETIME(LocalDateTime.class), //
OFFSET_TIME(OffsetTime.class), //
OFFSET_DATETIME(OffsetDateTime.class), //
BINARY(byte[].class), //
ARRAY(null), //
COLLECTION(Collection.class), //
MAP(Map.class), //
PC(null), //
LOCALE(Locale.class), //
PC_UNTYPED(Persistable.class), //
CALENDAR(Calendar.class), //
OID(null), //
INPUT_STREAM(InputStream.class), //
INPUT_READER(Reader.class), //
ENUM(Enum.class), //
OBJECT(Object.class), //
;
public static final JavaType[] VALUES = values();
private static final Map<Class<?>, JavaType> BY_TYPE = Arrays.stream(VALUES).filter(e -> e.type != null)
.collect(Collectors.toMap(JavaType::getTypeClass, v -> v));
private final Class<?> type;
JavaType(Class<?> type) {
this.type = type;
}
public Class<?> getTypeClass() {
return type;
}
public boolean isPrimitive() {
return ordinal() <= DOUBLE.ordinal();
}
public boolean isBooleanType() {
return this == BOOLEAN || this == BOOLEAN_OBJ;
}
public boolean isByteType() {
return this == BYTE || this == BYTE_OBJ;
}
public boolean isShortType() {
return this == SHORT || this == SHORT_OBJ;
}
public boolean isIntegerType() {
return this == INT || this == INT_OBJ;
}
public boolean isLongType() {
return this == LONG || this == LONG_OBJ;
}
public boolean isAnyIntegerType() {
return isByteType() || isShortType() || isIntegerType() || isLongType() || this == BIGINTEGER;
}
public boolean isFloatType() {
return this == FLOAT || this == FLOAT_OBJ;
}
public boolean isDoubleType() {
return this == DOUBLE || this == DOUBLE_OBJ;
}
public boolean isAnyFloatType() {
return isFloatType() || isDoubleType();
}
public boolean isDecimalType() {
return this == BIGDECIMAL;
}
public boolean isObjectNumericType() {
return Number.class.isAssignableFrom(getTypeClass());
}
public boolean isPrimitiveNumericType() {
return isPrimitive() && this != BOOLEAN && this != CHAR;
}
public boolean isNumericType() {
return isPrimitiveNumericType() || isObjectNumericType();
}
public boolean isStringType() {
return this == STRING;
}
public boolean isCharacterType() {
return this == CHAR || this == CHAR_OBJ;
}
public boolean isDateType() {
return this == DATE || this == LOCAL_DATE;
}
public boolean isTimeType() {
return this == TIME || this == LOCAL_TIME || this == OFFSET_TIME;
}
public boolean isDateTimeType() {
return this == DATETIME || this == LOCAL_DATETIME || this == OFFSET_DATETIME;
}
public boolean isAnyDateType() {
return isDateType() || isTimeType() || isDateTimeType();
}
public boolean isBinaryType() {
return this == BINARY;
}
public JavaType getPrimitiveType() {
if (isPrimitive()) {
return this;
}
return switch (this) {
case BOOLEAN_OBJ -> BOOLEAN;
case BYTE_OBJ -> BYTE;
case SHORT_OBJ -> SHORT;
case INT_OBJ -> INT;
case LONG_OBJ -> LONG;
case CHAR_OBJ -> CHAR;
case DOUBLE_OBJ -> DOUBLE;
case FLOAT_OBJ -> FLOAT;
default -> null;
};
}
@NotNull
public JavaType getObjectType() {
if (!isPrimitive()) {
return this;
}
return switch (this) {
case BOOLEAN -> BOOLEAN_OBJ;
case BYTE -> BYTE_OBJ;
case SHORT -> SHORT_OBJ;
case INT -> INT_OBJ;
case LONG -> LONG_OBJ;
case CHAR -> CHAR_OBJ;
case DOUBLE -> DOUBLE_OBJ;
case FLOAT -> FLOAT_OBJ;
default ->
throw new PlatformServiceUnavailableException("error.msg.database.type.not.allowed", "Unknown primitive type " + this);
};
}
/**
* @return the field metadata type for the given class. First class objects are not recognized in this method.
*/
@NotNull
public static JavaType forType(Class<?> type) {
if (type == null) {
return OBJECT;
}
JavaType javaType = BY_TYPE.get(type);
if (javaType != null) {
return javaType;
}
if (type.isArray()) {
return ARRAY;
}
// have to do this first to catch custom collection and map types;
// on resolve we figure out if these custom types are
// persistence-capable
for (Class<?> aType : BY_TYPE.keySet()) {
if (aType.isAssignableFrom(type)) {
return BY_TYPE.get(aType);
}
}
if (type.isInterface()) {
return Serializable.class.isAssignableFrom(type) ? OBJECT : PC_UNTYPED;
}
if (type.isAssignableFrom(Reader.class)) {
return INPUT_READER;
}
if (type.isAssignableFrom(InputStream.class)) {
return INPUT_STREAM;
}
return OBJECT;
}
/**
* @return the field metadata type for the given class. First class objects are not recognized in this method.
*/
public boolean matchType(Class<?> type) {
return matchType(type, true, false);
}
/**
* @return the field metadata type for the given class. First class objects are not recognized in this method.
*/
public boolean matchType(Class<?> type, boolean strict) {
return matchType(type, strict, false);
}
private boolean matchType(Class<?> type, boolean strict, boolean embedded) {
if (type == null) {
return this == OBJECT;
}
if (this.type == null) {
return false;
}
if (type.equals(this.type)) {
return true;
}
if (type.isArray()) {
return this.type.isArray();
}
if (this.type.isAssignableFrom(type)) {
return true;
}
if (type.isInterface()) {
return Serializable.class.isAssignableFrom(type) ? this == OBJECT : this == PC_UNTYPED;
}
if (type.isAssignableFrom(Reader.class)) {
return this == INPUT_READER;
}
if (type.isAssignableFrom(InputStream.class)) {
return this == INPUT_STREAM;
}
if (!strict && !embedded) {
JavaType thisType = this;
if (isPrimitive() && !type.isPrimitive()) {
thisType = getObjectType();
} else if (type.isPrimitive()) {
JavaType primitiveType = getPrimitiveType();
if (primitiveType != null) {
thisType = primitiveType;
}
}
if (this != thisType && thisType.matchType(type, false, true)) {
return true;
}
if (Number.class.isAssignableFrom(type) && isNumericType()) {
JavaType thatType = forType(type);
if (thisType.isAnyIntegerType() && thatType.isAnyIntegerType()) {
return thatType.ordinal() <= thisType.ordinal();
}
if (thisType.isAnyFloatType() && thatType.isAnyFloatType()) {
return thatType.ordinal() <= thisType.ordinal();
}
}
}
return this == OBJECT;
}
/**
* Determine whether the provided Object value is the default for this java type. For example: If o = Integer(0) and
* typeCode = JavaTypes.INT, this method will return true.
*/
public boolean isDefault(Object o) {
return switch (this) {
case BOOLEAN, BOOLEAN_OBJ -> Boolean.FALSE.equals(o);
case BYTE, BYTE_OBJ -> o != null && ((Byte) o) == 0;
case SHORT, SHORT_OBJ -> o != null && ((Short) o) == 0;
case INT, INT_OBJ -> o != null && ((Integer) o) == 0;
case LONG, LONG_OBJ -> o != null && ((Long) o) == 0L;
case CHAR, CHAR_OBJ -> o != null && ((Character) o) == '\u0000';
case FLOAT, FLOAT_OBJ -> o != null && ((Float) o) == 0.0F;
case DOUBLE, DOUBLE_OBJ -> o != null && ((Double) o) == 0.0d;
default -> false;
};
}
public Object parse(String s, String format, Locale locale) {
if (s == null) {
return null;
}
return switch (this) {
case BOOLEAN, BOOLEAN_OBJ -> Boolean.valueOf(s);
case BYTE, BYTE_OBJ -> Byte.parseByte(s);
case SHORT, SHORT_OBJ -> Short.parseShort(s);
case INT, INT_OBJ -> Integer.parseInt(s);
case LONG, LONG_OBJ -> Long.parseLong(s);
case CHAR, CHAR_OBJ -> StringUtils.isEmpty(s) ? null : s.charAt(0);
case FLOAT, FLOAT_OBJ -> Float.parseFloat(s);
case DOUBLE, DOUBLE_OBJ -> Double.parseDouble(s);
case BIGDECIMAL -> new BigDecimal(s, MoneyHelper.getMathContext());
case BIGINTEGER -> new BigInteger(s);
case STRING -> s;
case DATETIME -> Timestamp.valueOf(s);
case TIME -> Time.valueOf(s);
case DATE -> new Date(s);
default -> throw new PlatformServiceUnavailableException("error.msg.database.type.parse",
"Parse string representation is not supported " + this);
};
}
}
@@ -0,0 +1,342 @@
/**
* 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.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 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 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, "DECIMAL", 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")), //
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, null, "TIME WITH TIME ZONE")), //
DATETIME(JavaType.LOCAL_DATETIME, new DialectType(JDBCType.TIMESTAMP, "DATETIME", "TIMESTAMP"),
new DialectType(JDBCType.TIMESTAMP, null, "TIMESTAMP WITHOUT TIME ZONE")), //
TIMESTAMP_WITH_TIMEZONE(JavaType.OFFSET_DATETIME, new DialectType(JDBCType.TIMESTAMP_WITH_TIMEZONE, null, "DATETIME"),
new DialectType(JDBCType.TIMESTAMP_WITH_TIMEZONE, null, "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) {
if (name == null) {
return null;
}
name = name.toUpperCase();
for (JdbcJavaType type : values()) {
DialectType dialectType = type.getDialectType(dialect);
if (dialectType.getNameResolved().equals(name)) {
return type;
}
if (dialectType.alterNames != null) {
for (String alterName : dialectType.alterNames) {
if (alterName.equals(name)) {
return type;
}
}
}
}
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;
}
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.allowed",
"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 {
@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;
}
}
}
@@ -0,0 +1,202 @@
/**
* 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 static java.lang.String.format;
import jakarta.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
@AllArgsConstructor
@Getter
public enum SqlOperator {
EQ("="), //
NEQ("<>"), //
GTE(">="), //
LTE("<="), //
GT(">"), //
LT("<"), //
LIKE("LIKE") { //
@Override
public String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return format("%s %s %s", definition, getSymbol(), sqlGenerator.formatValue(columnType, "%" + values[0] + "%"));
}
@Override
public String formatPlaceholderImpl(String definition, String placeholder) {
return format("%s %s CONCAT('%%', %s, '%%')", definition, getSymbol(), placeholder);
}
},
NLIKE("NOT LIKE") { //
@Override
public String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return format("%s %s %s", definition, getSymbol(), sqlGenerator.formatValue(columnType, "%" + values[0] + "%"));
}
@Override
public String formatPlaceholderImpl(String definition, String placeholder) {
return format("%s %s CONCAT('%%', %s, '%%')", definition, getSymbol(), placeholder);
}
},
BTW("BETWEEN", 2) { //
@Override
public String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return format("%s %s %s AND %s", definition, getSymbol(), sqlGenerator.formatValue(columnType, values[0]),
sqlGenerator.formatValue(columnType, values[1]));
}
@Override
public String formatPlaceholderImpl(String definition, String placeholder) {
return format("%s %s %s AND %s", definition, getSymbol(), placeholder, placeholder);
}
},
NBTW("NOT BETWEEN", 2) { //
@Override
public String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return format("%s %s %s AND %s", definition, getSymbol(), sqlGenerator.formatValue(columnType, values[0]),
sqlGenerator.formatValue(columnType, values[1]));
}
@Override
public String formatPlaceholderImpl(String definition, String placeholder) {
return format("%s %s %s AND %s", definition, getSymbol(), placeholder, placeholder);
}
},
IN("IN", -1) { //
@Override
public String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return format("%s %s (%s)", definition, getSymbol(),
Arrays.stream(values).map(e -> sqlGenerator.formatValue(columnType, e)).collect(Collectors.joining(", ")));
}
@Override
public boolean isPlaceholderSupported() {
return false;
}
},
NIN("NOT IN", -1) { //
@Override
public String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return format("%s %s (%s)", definition, getSymbol(),
Arrays.stream(values).map(e -> sqlGenerator.formatValue(columnType, e)).collect(Collectors.joining(", ")));
}
@Override
public boolean isPlaceholderSupported() {
return false;
}
},
NULL("IS NULL", 0), //
NNULL("IS NOT NULL", 0), //
;
private final String symbol;
private final int paramCount;
SqlOperator(String symbol) {
this(symbol, 1);
}
public boolean isDefault() {
return this == getDefault();
}
public static SqlOperator getDefault() {
return EQ;
}
public boolean isListType() {
return paramCount < 0;
}
public String formatSql(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition, String alias,
List<String> values) {
return formatSql(sqlGenerator, columnType, definition, alias, values == null ? null : values.toArray(String[]::new));
}
public String formatSql(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition, String alias,
String... values) {
validateValues(values);
return formatImpl(sqlGenerator, columnType, sqlGenerator.alias(sqlGenerator.escape(definition), alias), values);
}
protected String formatImpl(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, JdbcJavaType columnType, String definition,
String... values) {
return paramCount == 0 ? format("%s %s", definition, symbol)
: format("%s %s %s", definition, symbol, sqlGenerator.formatValue(columnType, values[0]));
}
public boolean isPlaceholderSupported() {
return true;
}
public String formatPlaceholder(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, String definition, String alias) {
return formatPlaceholder(sqlGenerator, definition, alias, "?");
}
public String formatPlaceholder(@NotNull DatabaseSpecificSQLGenerator sqlGenerator, String definition, String alias,
String placeholder) {
return formatPlaceholderImpl(sqlGenerator.alias(sqlGenerator.escape(definition), alias), placeholder);
}
protected String formatPlaceholderImpl(String definition, String placeholder) {
if (!isPlaceholderSupported()) {
throw new UnsupportedOperationException("Placeholder is not supported for this operator");
}
return paramCount == 0 ? format("%s %s", definition, symbol) : format("%s %s %s", definition, symbol, placeholder);
}
public void validateValues(String... values) {
if (values == null ? paramCount != 0 : (paramCount < 0 ? values.length < -paramCount : values.length != paramCount)) {
throw new PlatformServiceUnavailableException("error.msg.database.operator.invalid",
"Number of parameters " + Arrays.toString(values) + " must be " + Math.abs(paramCount) + " on " + this);
}
}
public void validateValues(List<?> values) {
int size = values == null ? 0 : values.size();
if (paramCount < 0 ? size < -paramCount : size != paramCount) {
throw new PlatformServiceUnavailableException("error.msg.database.operator.invalid",
"Number of parameters " + size + " must be " + Math.abs(paramCount) + " on " + this);
}
}
@NotNull
public static SqlOperator forName(String name) {
return name == null ? getDefault() : SqlOperator.valueOf(name.toUpperCase());
}
}
@@ -18,10 +18,13 @@
*/
package org.apache.fineract.infrastructure.dataqueries.data;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.service.database.DatabaseType;
import org.apache.fineract.infrastructure.core.service.database.JdbcJavaType;
/**
* Immutable data object representing a resultset column.
@@ -29,9 +32,9 @@ import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityEx
public final class ResultsetColumnHeaderData implements Serializable {
private final String columnName;
private String columnType;
private JdbcJavaType columnType;
private final Long columnLength;
private final String columnDisplayType;
private final DisplayType columnDisplayType;
private final boolean isColumnNullable;
private final boolean isColumnPrimaryKey;
private final boolean isColumnUnique;
@@ -40,8 +43,7 @@ public final class ResultsetColumnHeaderData implements Serializable {
private final List<ResultsetColumnValueData> columnValues;
private final String columnCode;
public static ResultsetColumnHeaderData basic(final String columnName, final String columnType) {
public static ResultsetColumnHeaderData basic(final String columnName, final String columnType, DatabaseType dialect) {
final Long columnLength = null;
final boolean columnNullable = false;
final boolean columnIsPrimaryKey = false;
@@ -50,21 +52,20 @@ public final class ResultsetColumnHeaderData implements Serializable {
final boolean columnIsUnique = false;
final boolean columnIsIndexed = false;
return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues,
columnCode, columnIsUnique, columnIsIndexed);
columnCode, columnIsUnique, columnIsIndexed, dialect);
}
public static ResultsetColumnHeaderData detailed(final String columnName, final String columnType, final Long columnLength,
final boolean columnNullable, final boolean columnIsPrimaryKey, final List<ResultsetColumnValueData> columnValues,
final String columnCode, final boolean columnIsUnique, final boolean columnIsIndexed) {
final String columnCode, final boolean columnIsUnique, final boolean columnIsIndexed, DatabaseType dialect) {
return new ResultsetColumnHeaderData(columnName, columnType, columnLength, columnNullable, columnIsPrimaryKey, columnValues,
columnCode, columnIsUnique, columnIsIndexed);
columnCode, columnIsUnique, columnIsIndexed, dialect);
}
private ResultsetColumnHeaderData(final String columnName, final String columnType, final Long columnLength,
final boolean columnNullable, final boolean columnIsPrimaryKey, final List<ResultsetColumnValueData> columnValues,
final String columnCode, final boolean columnIsUnique, final boolean columnIsIndexed) {
private ResultsetColumnHeaderData(final String columnName, String columnType, final Long columnLength, final boolean columnNullable,
final boolean columnIsPrimaryKey, final List<ResultsetColumnValueData> columnValues, final String columnCode,
final boolean columnIsUnique, final boolean columnIsIndexed, DatabaseType dialect) {
this.columnName = columnName;
this.columnType = columnType;
this.columnLength = columnLength;
this.isColumnNullable = columnNullable;
this.isColumnPrimaryKey = columnIsPrimaryKey;
@@ -74,179 +75,20 @@ public final class ResultsetColumnHeaderData implements Serializable {
this.isColumnIndexed = columnIsIndexed;
// Refer org.drizzle.jdbc.internal.mysql.MySQLType.java
adjustColumnTypes();
this.columnType = JdbcJavaType.getByTypeName(dialect, adjustColumnType(columnType));
String displayType = null;
if (this.columnCode == null) {
if (isString()) {
displayType = "STRING";
} else if (isAnyInteger()) {
if (isInteger()) {
this.columnType = this.columnType.toUpperCase();
}
displayType = "INTEGER";
} else if (isDate()) {
displayType = "DATE";
} else if (isDateTime()) {
displayType = "DATETIME";
} else if (isDecimal()) {
displayType = "DECIMAL";
} else if (isAnyText()) {
displayType = "TEXT";
} else if (isBit() || isBoolean()) {
displayType = "BOOLEAN";
} else {
throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type",
"Invalid Lookup Type:" + this.columnType + " - Column Name: " + this.columnName);
}
} else {
if (isInt() || isInteger()) {
displayType = "CODELOOKUP";
} else if (isVarchar()) {
displayType = "CODEVALUE";
} else {
throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type",
"Invalid Lookup Type:" + this.columnType + " - Column Name: " + this.columnName);
}
}
this.columnDisplayType = displayType;
}
private void adjustColumnTypes() {
switch (this.columnType) {
case "NEWDECIMAL":
this.columnType = "DECIMAL";
break;
case "CLOB":
case "ENUM":
case "SET":
this.columnType = "varchar";
break;
case "LONGLONG":
this.columnType = "bigint";
break;
case "SHORT":
this.columnType = "smallint";
break;
case "TINY":
this.columnType = "tinyint";
break;
case "INT24":
this.columnType = "int";
break;
default:
break;
}
}
public boolean isNamed(final String columnName) {
return this.columnName.equalsIgnoreCase(columnName);
}
private boolean isAnyText() {
return isText() || isTinyText() || isMediumText() || isLongText();
}
private boolean isText() {
return "text".equalsIgnoreCase(this.columnType);
}
private boolean isTinyText() {
return "tinytext".equalsIgnoreCase(this.columnType);
}
private boolean isMediumText() {
return "mediumtext".equalsIgnoreCase(this.columnType);
}
private boolean isLongText() {
return "longtext".equalsIgnoreCase(this.columnType);
}
private boolean isDecimal() {
return "decimal".equalsIgnoreCase(this.columnType) || "NEWDECIMAL".equalsIgnoreCase(this.columnType)
|| "numeric".equalsIgnoreCase(this.columnType);
// Refer org.drizzle.jdbc.internal.mysql.MySQLType.java
}
private boolean isDate() {
return "date".equalsIgnoreCase(this.columnType);
}
private boolean isDateTime() {
return "datetime".equalsIgnoreCase(this.columnType) || "timestamp".equalsIgnoreCase(this.columnType)
|| "timestamp without time zone".equalsIgnoreCase(this.columnType) || "timestamptz".equalsIgnoreCase(this.columnType)
|| "timestamp with time zone".equalsIgnoreCase(this.columnType);
}
public boolean isString() {
return isVarchar() || isChar();
}
private boolean isChar() {
return "char".equalsIgnoreCase(this.columnType) || "CHARACTER VARYING".equalsIgnoreCase(this.columnType)
|| "bpchar".equalsIgnoreCase(this.columnType);
}
private boolean isVarchar() {
return "varchar".equalsIgnoreCase(this.columnType);
}
private boolean isAnyInteger() {
return isInt() || isInteger() || isSmallInt() || isTinyInt() || isMediumInt() || isBigInt() || isLong() || isSerial();
}
private boolean isSerial() {
return "SERIAL".equalsIgnoreCase(this.columnType) || "SERIAL4".equalsIgnoreCase(this.columnType)
|| "SERIAL8".equalsIgnoreCase(this.columnType) || "SMALLSERIAL".equalsIgnoreCase(this.columnType)
|| "BIGSERIAL".equalsIgnoreCase(this.columnType);
}
private boolean isInt() {
return "int".equalsIgnoreCase(this.columnType);
}
private boolean isInteger() {
return "integer".equalsIgnoreCase(this.columnType) || "int2".equalsIgnoreCase(this.columnType)
|| "int4".equalsIgnoreCase(this.columnType);
}
private boolean isSmallInt() {
return "smallint".equalsIgnoreCase(this.columnType);
}
private boolean isTinyInt() {
return "tinyint".equalsIgnoreCase(this.columnType);
}
private boolean isMediumInt() {
return "mediumint".equalsIgnoreCase(this.columnType);
}
private boolean isBigInt() {
return "bigint".equalsIgnoreCase(this.columnType) || "int8".equalsIgnoreCase(this.columnType);
}
private boolean isLong() {
return "LONG".equalsIgnoreCase(this.columnType) || "LONGLONG".equalsIgnoreCase(this.columnType);
// Refer org.drizzle.jdbc.internal.mysql.MySQLType.java
}
private boolean isBit() {
return "bit".equalsIgnoreCase(this.columnType);
}
private boolean isBoolean() {
return "boolean".equalsIgnoreCase(this.columnType) || "bool".equalsIgnoreCase(this.columnType);
this.columnDisplayType = calcDisplayType();
}
public String getColumnName() {
return this.columnName;
}
public String getColumnType() {
public boolean isNamed(final String columnName) {
return this.columnName.equalsIgnoreCase(columnName);
}
public JdbcJavaType getColumnType() {
return this.columnType;
}
@@ -270,78 +112,10 @@ public final class ResultsetColumnHeaderData implements Serializable {
return isColumnIndexed;
}
public String getColumnDisplayType() {
public DisplayType getColumnDisplayType() {
return this.columnDisplayType;
}
public boolean isDateDisplayType() {
return "DATE".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isDateTimeDisplayType() {
return "DATETIME".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isIntegerDisplayType() {
return "INTEGER".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isDecimalDisplayType() {
return "DECIMAL".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isBooleanDisplayType() {
return "BOOLEAN".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isCodeValueDisplayType() {
return "CODEVALUE".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isCodeLookupDisplayType() {
return "CODELOOKUP".equalsIgnoreCase(this.columnDisplayType);
}
public boolean isMandatory() {
return !isOptional();
}
public boolean isOptional() {
return this.isColumnNullable;
}
public boolean hasColumnValues() {
return !this.columnValues.isEmpty();
}
public boolean isColumnValueAllowed(final String match) {
boolean allowed = false;
for (final ResultsetColumnValueData allowedValue : this.columnValues) {
if (allowedValue.matches(match)) {
allowed = true;
}
}
return allowed;
}
public boolean isColumnValueNotAllowed(final String match) {
return !isColumnValueAllowed(match);
}
public boolean isColumnCodeNotAllowed(final Integer match) {
return !isColumnCodeAllowed(match);
}
public boolean isColumnCodeAllowed(final Integer match) {
boolean allowed = false;
for (final ResultsetColumnValueData allowedValue : this.columnValues) {
if (allowedValue.codeMatches(match)) {
allowed = true;
}
}
return allowed;
}
public String getColumnCode() {
return this.columnCode;
}
@@ -349,4 +123,156 @@ public final class ResultsetColumnHeaderData implements Serializable {
public List<ResultsetColumnValueData> getColumnValues() {
return this.columnValues;
}
public boolean isDateDisplayType() {
return columnDisplayType == DisplayType.DATE;
}
public boolean isDateTimeDisplayType() {
return columnDisplayType == DisplayType.DATETIME;
}
public boolean isTimeDisplayType() {
return columnDisplayType == DisplayType.TIME;
}
public boolean isIntegerDisplayType() {
return columnDisplayType == DisplayType.INTEGER;
}
public boolean isDecimalDisplayType() {
return columnDisplayType == DisplayType.DECIMAL;
}
public boolean isStringDisplayType() {
return columnDisplayType == DisplayType.STRING;
}
public boolean isTextDisplayType() {
return columnDisplayType == DisplayType.TEXT;
}
public boolean isBooleanDisplayType() {
return columnDisplayType == DisplayType.BOOLEAN;
}
public boolean isCodeValueDisplayType() {
return columnDisplayType == DisplayType.CODEVALUE;
}
public boolean isCodeLookupDisplayType() {
return columnDisplayType == DisplayType.CODELOOKUP;
}
public boolean isMandatory() {
return !this.isColumnNullable;
}
public boolean hasColumnValues() {
return columnValues != null && !columnValues.isEmpty();
}
public boolean isColumnValueAllowed(final String match) {
for (final ResultsetColumnValueData allowedValue : this.columnValues) {
if (allowedValue.matches(match)) {
return true;
}
}
return false;
}
public boolean isColumnCodeAllowed(final Integer match) {
for (final ResultsetColumnValueData allowedValue : this.columnValues) {
if (allowedValue.codeMatches(match)) {
return true;
}
}
return false;
}
public boolean hasPrecision(@NotNull DatabaseType dialect) {
return columnType.hasPrecision(dialect);
}
// --- Calculation ---
private String adjustColumnType(String type) {
type = type.toUpperCase();
switch (type) {
case "CLOB":
case "ENUM":
case "SET":
return "VARCHAR";
case "NEWDECIMAL":
return "DECIMAL";
case "LONGLONG":
return "BIGINT";
case "SHORT":
return "SMALLINT";
case "TINY":
return "TINYINT";
case "INT24":
return "INT";
default:
return type;
}
}
@NotNull
private DisplayType calcDisplayType() {
DisplayType displayType = null;
if (this.columnCode == null) {
displayType = calcColumnDisplayType(columnType);
} else {
if (columnType.isIntegerType()) {
displayType = DisplayType.CODELOOKUP;
} else if (columnType.isVarcharType()) {
displayType = DisplayType.CODEVALUE;
}
}
if (displayType == null) {
throw new PlatformDataIntegrityException("error.msg.invalid.lookup.type",
"Invalid Lookup Type:" + this.columnType + " - Column Name: " + this.columnName);
}
return displayType;
}
public static DisplayType calcColumnDisplayType(JdbcJavaType columnType) {
if (columnType.isTextType()) {
return DisplayType.TEXT;
}
if (columnType.isStringType()) {
return DisplayType.STRING;
}
if (columnType.isAnyIntegerType()) {
return DisplayType.INTEGER;
}
if (columnType.isAnyFloatType()) {
return DisplayType.FLOAT;
}
if (columnType.isDecimalType()) { // Refer org.drizzle.jdbc.internal.mysql.MySQLType.java
return DisplayType.DECIMAL;
}
if (columnType.isDateType()) {
return DisplayType.DATE;
}
if (columnType.isDateTimeType()) {
return DisplayType.DATETIME;
}
if (columnType.isTimeType()) {
return DisplayType.TIME;
}
if (columnType.isBooleanType()) {
return DisplayType.BOOLEAN;
}
if (columnType.isBinaryType()) {
return DisplayType.BINARY;
}
return null;
}
public enum DisplayType {
TEXT, STRING, INTEGER, FLOAT, DECIMAL, DATE, TIME, DATETIME, BOOLEAN, BINARY, CODELOOKUP, CODEVALUE,;
}
}