[#7229] Add <forceIntegerTypesOnZeroScaleDecimals/> configuration to allow for treating DECIMAL types as BigDecimal

This commit is contained in:
lukaseder 2018-03-23 15:25:08 +01:00
parent 2514d4e691
commit 86007b3540
9 changed files with 195 additions and 65 deletions

View File

@ -479,6 +479,7 @@ public class GenerationTool {
database.setIncludeTriggerRoutines(TRUE.equals(d.isIncludeTriggerRoutines()));
database.setIncludeUDTs(!FALSE.equals(d.isIncludeUDTs()));
database.setIncludeUniqueKeys(!FALSE.equals(d.isIncludeUniqueKeys()));
database.setForceIntegerTypesOnZeroScaleDecimals(!FALSE.equals(d.isForceIntegerTypesOnZeroScaleDecimals()));
database.setRecordVersionFields(new String[] { defaultString(d.getRecordVersionFields()) });
database.setRecordTimestampFields(new String[] { defaultString(d.getRecordTimestampFields()) });
database.setSyntheticPrimaryKeys(new String[] { defaultString(d.getSyntheticPrimaryKeys()) });

View File

@ -46,6 +46,7 @@ import static org.jooq.tools.StringUtils.defaultIfBlank;
import static org.jooq.tools.StringUtils.defaultString;
import static org.jooq.util.AbstractGenerator.Language.JAVA;
import static org.jooq.util.AbstractGenerator.Language.SCALA;
import static org.jooq.util.AbstractTypedElementDefinition.getDataType;
import static org.jooq.util.GenerationUtil.convertToIdentifier;
import java.io.File;
@ -5937,7 +5938,7 @@ public class JavaGenerator extends AbstractGenerator {
// Try finding a basic standard SQL type according to the current dialect
else {
try {
Class<?> clazz = mapJavaTimeTypes(DefaultDataType.getDataType(db.getDialect(), t, p, s)).getType();
Class<?> clazz = mapJavaTimeTypes(getDataType(db, t, p, s)).getType();
if (scala && clazz == byte[].class)
type = "scala.Array[scala.Byte]";
else
@ -5999,7 +6000,7 @@ public class JavaGenerator extends AbstractGenerator {
DataType<?> dataType = null;
try {
dataType = mapJavaTimeTypes(DefaultDataType.getDataType(db.getDialect(), t, p, s)).nullable(n).identity(i);
dataType = mapJavaTimeTypes(getDataType(db, t, p, s)).nullable(n).identity(i);
if (d != null)
dataType = dataType.defaultValue((Field) DSL.field(d, dataType));
@ -6102,7 +6103,7 @@ public class JavaGenerator extends AbstractGenerator {
sb.append(typeName);
if (!type1.equals(type2)) {
Class<?> clazz = mapJavaTimeTypes(DefaultDataType.getDataType(db.getDialect(), t, p, s)).getType();
Class<?> clazz = mapJavaTimeTypes(getDataType(db, t, p, s)).getType();
sb.append(".asNumberDataType(");
sb.append(classOf(clazz.getCanonicalName()));

View File

@ -16649,6 +16649,60 @@ public class CaseInsensitiveOrderProvider implements Comparator<Definition> {
</html></content>
</section>
<section id="codegen-generate-zero-scale-decimals">
<title>Zero Scale Decimal Types</title>
<content><html>
<p>
A zero-scale decimal, such as <code>DECIMAL(10)</code> or <code>NUMBER(10, 0)</code> is really an integer type with a decimal precision rather than a binary / bitwise precision. Some databases (e.g. Oracle) do not support actual integer types at all, only decimal types. Historically, jOOQ generates the most appropriate integer wrapper type instead of <code>BigDecimal</code> or <code>BigInteger</code>:
</p>
<ul>
<li><code>NUMBER(2, 0)</code> and less: <reference class="java.lang.Byte"/></li>
<li><code>NUMBER(4, 0)</code> and less: <reference class="java.lang.Short"/></li>
<li><code>NUMBER(9, 0)</code> and less: <reference class="java.lang.Integer"/></li>
<li><code>NUMBER(19, 0)</code> and less: <reference class="java.lang.Long"/></li>
</ul>
<p>
If this is not a desireable default, it can be deactivated either explicitly on a per-column basis using <reference id="codegen-database-forced-types" title="forced types"/>, or globally using the following flag:
</p>
<p>
<strong>XML configuration (standalone and Maven)</strong>
</p>
</html><xml><![CDATA[<configuration>
<generator>
<generate>
<forceIntegerTypesOnZeroScaleDecimals>true</forceIntegerTypesOnZeroScaleDecimals>
</generate>
</generator>
</configuration>]]></xml><html>
<p>
<strong>Programmatic configuration</strong>
</p>
</html><java><![CDATA[configuration
.withGenerator(new Generator(
.withGenerate(new Generate()
.withForceIntegerTypesOnZeroScaleDecimals(true))));]]></java><html>
<p>
<strong>Gradle configuration</strong>
</p>
</html><java><![CDATA[configuration {
generator {
generate {
forceIntegerTypesOnZeroScaleDecimals = true
}
}
}]]></java><html>
</html></content>
</section>
</sections>
</section>

View File

@ -105,21 +105,22 @@ public abstract class AbstractDatabase implements Database {
private List<RegexFlag> regexFlags;
private List<Filter> filters;
private String[] excludes;
private String[] includes = { ".*" };
private String[] includes = { ".*" };
private boolean includeExcludeColumns;
private boolean includeTables = true;
private boolean includeRoutines = true;
private boolean includeTriggerRoutines = false;
private boolean includePackages = true;
private boolean includePackageRoutines = true;
private boolean includePackageUDTs = true;
private boolean includePackageConstants = true;
private boolean includeUDTs = true;
private boolean includeSequences = true;
private boolean includeIndexes = true;
private boolean includePrimaryKeys = true;
private boolean includeUniqueKeys = true;
private boolean includeForeignKeys = true;
private boolean includeTables = true;
private boolean includeRoutines = true;
private boolean includeTriggerRoutines = false;
private boolean includePackages = true;
private boolean includePackageRoutines = true;
private boolean includePackageUDTs = true;
private boolean includePackageConstants = true;
private boolean includeUDTs = true;
private boolean includeSequences = true;
private boolean includeIndexes = true;
private boolean includePrimaryKeys = true;
private boolean includeUniqueKeys = true;
private boolean includeForeignKeys = true;
private boolean forceIntegerTypesOnZeroScaleDecimals = true;
private String[] recordVersionFields;
private String[] recordTimestampFields;
private String[] syntheticPrimaryKeys;
@ -128,8 +129,8 @@ public abstract class AbstractDatabase implements Database {
private boolean supportsUnsignedTypes;
private boolean ignoreProcedureReturnValues;
private boolean dateAsTimestamp;
private List<Catalog> configuredCatalogs = new ArrayList<Catalog>();
private List<Schema> configuredSchemata = new ArrayList<Schema>();
private List<Catalog> configuredCatalogs = new ArrayList<Catalog>();
private List<Schema> configuredSchemata = new ArrayList<Schema>();
private List<CustomType> configuredCustomTypes;
private List<EnumType> configuredEnumTypes;
private List<ForcedType> configuredForcedTypes;
@ -160,8 +161,8 @@ public abstract class AbstractDatabase implements Database {
private List<RoutineDefinition> routines;
private List<PackageDefinition> packages;
private Relations relations;
private boolean includeRelations = true;
private boolean tableValuedFunctions = true;
private boolean includeRelations = true;
private boolean tableValuedFunctions = true;
private transient Map<SchemaDefinition, List<SequenceDefinition>> sequencesBySchema;
private transient Map<SchemaDefinition, List<IdentityDefinition>> identitiesBySchema;
@ -1127,6 +1128,16 @@ public abstract class AbstractDatabase implements Database {
return includeRelations;
}
@Override
public void setForceIntegerTypesOnZeroScaleDecimals(boolean forceIntegerTypesOnZeroScaleDecimals) {
this.forceIntegerTypesOnZeroScaleDecimals = forceIntegerTypesOnZeroScaleDecimals;
}
@Override
public boolean getForceIntegerTypesOnZeroScaleDecimals() {
return forceIntegerTypesOnZeroScaleDecimals;
}
@Override
public final void setTableValuedFunctions(boolean tableValuedFunctions) {
this.tableValuedFunctions = tableValuedFunctions;

View File

@ -41,6 +41,8 @@ package org.jooq.util;
import static org.jooq.tools.Convert.convert;
import static org.jooq.tools.StringUtils.isEmpty;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
@ -80,7 +82,7 @@ abstract class AbstractTypedElementDefinition<T extends Definition>
this.definedType = definedType;
}
private static String protectName(Definition container, String name, int position) {
private static final String protectName(Definition container, String name, int position) {
if (name == null) {
// [#6654] Specific error messages per type
@ -139,8 +141,19 @@ abstract class AbstractTypedElementDefinition<T extends Definition>
return definedType;
}
static final DataType<?> getDataType(Database db, String t, int p, int s) {
if (db.getForceIntegerTypesOnZeroScaleDecimals())
return DefaultDataType.getDataType(db.getDialect(), t, p, s);
DataType<?> result = DefaultDataType.getDataType(db.getDialect(), t);
if (result.getType() == BigDecimal.class && s == 0)
DefaultDataType.getDataType(db.getDialect(), BigInteger.class);
return result;
}
@SuppressWarnings("deprecation")
static DataTypeDefinition mapDefinedType(Definition container, Definition child, DataTypeDefinition definedType, JavaTypeResolver resolver) {
static final DataTypeDefinition mapDefinedType(Definition container, Definition child, DataTypeDefinition definedType, JavaTypeResolver resolver) {
DataTypeDefinition result = definedType;
Database db = container.getDatabase();
@ -151,14 +164,14 @@ abstract class AbstractTypedElementDefinition<T extends Definition>
DataType<?> dataType = null;
try {
dataType = DefaultDataType.getDataType(db.getDialect(), result.getType(), 0, 0);
dataType = getDataType(db, result.getType(), 0, 0);
} catch (SQLDialectNotSupportedException ignore) {}
if (dataType != null) {
// [#5239] [#5762] [#6453] Don't rely on getSQLType()
if (SQLDataType.DATE.equals(dataType.getSQLDataType())) {
DataType<?> forcedDataType = DefaultDataType.getDataType(db.getDialect(), SQLDataType.TIMESTAMP.getTypeName(), 0, 0);
DataType<?> forcedDataType = getDataType(db, SQLDataType.TIMESTAMP.getTypeName(), 0, 0);
result = new DefaultDataTypeDefinition(db, child.getSchema(), forcedDataType.getTypeName(), 0, 0, 0, result.isNullable(), result.getDefaultValue(), (Name) null, null, DateAsTimestampBinding.class.getName());
}
}
@ -187,8 +200,7 @@ abstract class AbstractTypedElementDefinition<T extends Definition>
tType = resolver.resolve(definedType);
else
try {
tType = DefaultDataType
.getDataType(db.getDialect(), definedType.getType(), definedType.getPrecision(), definedType.getScale())
tType = getDataType(db, definedType.getType(), definedType.getPrecision(), definedType.getScale())
.getType()
.getName();
}
@ -229,7 +241,7 @@ abstract class AbstractTypedElementDefinition<T extends Definition>
}
try {
forcedDataType = DefaultDataType.getDataType(db.getDialect(), uType, p, s);
forcedDataType = getDataType(db, uType, p, s);
} catch (SQLDialectNotSupportedException ignore) {}
// [#677] SQLDataType matches are actual type-rewrites
@ -265,7 +277,7 @@ abstract class AbstractTypedElementDefinition<T extends Definition>
}
@SuppressWarnings("deprecation")
static CustomType customType(Database db, ForcedType forcedType) {
static final CustomType customType(Database db, ForcedType forcedType) {
String name = forcedType.getName();
// [#4598] Legacy use-case where a <forcedType/> referes to a <customType/>

View File

@ -506,6 +506,18 @@ public interface Database extends AutoCloseable {
*/
void setIncludeTables(boolean includeTables);
/**
* Whether zero-scale decimal types should be treated as their most
* appropriate, corresponding integer type.
*/
void setForceIntegerTypesOnZeroScaleDecimals(boolean forceIntegerTypesOnZeroScaleDecimals);
/**
* Whether zero-scale decimal types should be treated as their most
* appropriate, corresponding integer type.
*/
boolean getForceIntegerTypesOnZeroScaleDecimals();
/**
* Whether tables (and views) should be included.
*/

View File

@ -124,6 +124,8 @@ public class Database implements Serializable
@XmlElement(defaultValue = "")
@XmlJavaTypeAdapter(StringAdapter.class)
protected String orderProvider = "";
@XmlElement(defaultValue = "true")
protected Boolean forceIntegerTypesOnZeroScaleDecimals = true;
protected Boolean tableValuedFunctions;
@XmlElementWrapper(name = "properties")
@XmlElement(name = "property")
@ -1132,6 +1134,30 @@ public class Database implements Serializable
this.orderProvider = value;
}
/**
* Historically, zero-scale decimal types are generated as their most appropriate, corresponding integer type (e.g. NUMBER(2, 0) and less: Byte). This allows for turning off this feature. In case of conflict between this rule and actual {@link #getForcedTypes()}, the latter will win.
*
* @return
* possible object is
* {@link Boolean }
*
*/
public Boolean isForceIntegerTypesOnZeroScaleDecimals() {
return forceIntegerTypesOnZeroScaleDecimals;
}
/**
* Sets the value of the forceIntegerTypesOnZeroScaleDecimals property.
*
* @param value
* allowed object is
* {@link Boolean }
*
*/
public void setForceIntegerTypesOnZeroScaleDecimals(Boolean value) {
this.forceIntegerTypesOnZeroScaleDecimals = value;
}
/**
* Whether table valued functions should be reported as tables.
* <p>
@ -1416,6 +1442,11 @@ public class Database implements Serializable
return this;
}
public Database withForceIntegerTypesOnZeroScaleDecimals(Boolean value) {
setForceIntegerTypesOnZeroScaleDecimals(value);
return this;
}
public Database withTableValuedFunctions(Boolean value) {
setTableValuedFunctions(value);
return this;
@ -1725,6 +1756,11 @@ public class Database implements Serializable
sb.append(orderProvider);
sb.append("</orderProvider>");
}
if (forceIntegerTypesOnZeroScaleDecimals!= null) {
sb.append("<forceIntegerTypesOnZeroScaleDecimals>");
sb.append(forceIntegerTypesOnZeroScaleDecimals);
sb.append("</forceIntegerTypesOnZeroScaleDecimals>");
}
if (tableValuedFunctions!= null) {
sb.append("<tableValuedFunctions>");
sb.append(tableValuedFunctions);
@ -2090,6 +2126,15 @@ public class Database implements Serializable
return false;
}
}
if (forceIntegerTypesOnZeroScaleDecimals == null) {
if (other.forceIntegerTypesOnZeroScaleDecimals!= null) {
return false;
}
} else {
if (!forceIntegerTypesOnZeroScaleDecimals.equals(other.forceIntegerTypesOnZeroScaleDecimals)) {
return false;
}
}
if (tableValuedFunctions == null) {
if (other.tableValuedFunctions!= null) {
return false;
@ -2195,6 +2240,7 @@ public class Database implements Serializable
result = ((prime*result)+((schemaVersionProvider == null)? 0 :schemaVersionProvider.hashCode()));
result = ((prime*result)+((catalogVersionProvider == null)? 0 :catalogVersionProvider.hashCode()));
result = ((prime*result)+((orderProvider == null)? 0 :orderProvider.hashCode()));
result = ((prime*result)+((forceIntegerTypesOnZeroScaleDecimals == null)? 0 :forceIntegerTypesOnZeroScaleDecimals.hashCode()));
result = ((prime*result)+((tableValuedFunctions == null)? 0 :tableValuedFunctions.hashCode()));
result = ((prime*result)+((properties == null)? 0 :properties.hashCode()));
result = ((prime*result)+((catalogs == null)? 0 :catalogs.hashCode()));

View File

@ -672,6 +672,10 @@ This comparator can be used to influence the order of any object that is produce
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Configure type overrides for generated fields, attributes, sequences, parameters.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="forceIntegerTypesOnZeroScaleDecimals" type="boolean" minOccurs="0" maxOccurs="1" default="true">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Historically, zero-scale decimal types are generated as their most appropriate, corresponding integer type (e.g. NUMBER(2, 0) and less: Byte). This allows for turning off this feature. In case of conflict between this rule and actual {@link #getForcedTypes()}, the latter will win.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="tableValuedFunctions" type="boolean" minOccurs="0" maxOccurs="1">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Whether table valued functions should be reported as tables.
<p>

View File

@ -355,20 +355,15 @@ public class DefaultDataType<T> implements DataType<T> {
}
private static final int precision0(Class<?> type, int precision) {
if (precision == 0) {
if (type == Long.class || type == ULong.class) {
if (precision == 0)
if (type == Long.class || type == ULong.class)
precision = LONG_PRECISION;
}
else if (type == Integer.class || type == UInteger.class) {
else if (type == Integer.class || type == UInteger.class)
precision = INTEGER_PRECISION;
}
else if (type == Short.class || type == UShort.class) {
else if (type == Short.class || type == UShort.class)
precision = SHORT_PRECISION;
}
else if (type == Byte.class || type == UByte.class) {
else if (type == Byte.class || type == UByte.class)
precision = BYTE_PRECISION;
}
}
return precision;
}
@ -779,15 +774,15 @@ public class DefaultDataType<T> implements DataType<T> {
return Convert.convert(objects, uType);
}
public static DataType<Object> getDefaultDataType(String typeName) {
public static final DataType<Object> getDefaultDataType(String typeName) {
return new DefaultDataType<Object>(SQLDialect.DEFAULT, Object.class, typeName, typeName);
}
public static DataType<Object> getDefaultDataType(SQLDialect dialect, String typeName) {
public static final DataType<Object> getDefaultDataType(SQLDialect dialect, String typeName) {
return new DefaultDataType<Object>(dialect, Object.class, typeName, typeName);
}
public static DataType<?> getDataType(SQLDialect dialect, String typeName) {
public static final DataType<?> getDataType(SQLDialect dialect, String typeName) {
int ordinal = dialect.ordinal();
String upper = typeName.toUpperCase();
String normalised = typeName;
@ -819,11 +814,11 @@ public class DefaultDataType<T> implements DataType<T> {
return result;
}
public static <T> DataType<T> getDataType(SQLDialect dialect, Class<T> type) {
public static final <T> DataType<T> getDataType(SQLDialect dialect, Class<T> type) {
return getDataType(dialect, type, null);
}
public static <T> DataType<T> getDataType(SQLDialect dialect, Class<T> type, DataType<T> fallbackDataType) {
public static final <T> DataType<T> getDataType(SQLDialect dialect, Class<T> type, DataType<T> fallbackDataType) {
// Treat primitive types the same way as their respective wrapper types
type = (Class<T>) wrapper(type);
@ -1008,19 +1003,18 @@ public class DefaultDataType<T> implements DataType<T> {
/**
* @return The type name without all special characters and white spaces
*/
public static String normalise(String typeName) {
public static final String normalise(String typeName) {
return NORMALISE_PATTERN.matcher(typeName.toUpperCase()).replaceAll("");
}
/**
* Convert a type name (using precision and scale) into a Java class
*/
public static DataType<?> getDataType(SQLDialect dialect, String t, int p, int s) throws SQLDialectNotSupportedException {
public static final DataType<?> getDataType(SQLDialect dialect, String t, int p, int s) throws SQLDialectNotSupportedException {
DataType<?> result = DefaultDataType.getDataType(dialect, t);
if (result.getType() == BigDecimal.class) {
if (result.getType() == BigDecimal.class)
result = DefaultDataType.getDataType(dialect, getNumericClass(p, s));
}
return result;
}
@ -1028,45 +1022,40 @@ public class DefaultDataType<T> implements DataType<T> {
/**
* Convert a type name (using precision and scale) into a Java class
*/
public static Class<?> getType(SQLDialect dialect, String t, int p, int s) throws SQLDialectNotSupportedException {
public static final Class<?> getType(SQLDialect dialect, String t, int p, int s) throws SQLDialectNotSupportedException {
return getDataType(dialect, t, p, s).getType();
}
/**
* Get the most suitable Java class for a given precision and scale
*/
private static Class<?> getNumericClass(int precision, int scale) {
private static final Class<?> getNumericClass(int precision, int scale) {
// Integer numbers
if (scale == 0 && precision != 0) {
if (precision < BYTE_PRECISION) {
if (scale == 0 && precision != 0)
if (precision < BYTE_PRECISION)
return Byte.class;
}
if (precision < SHORT_PRECISION) {
else if (precision < SHORT_PRECISION)
return Short.class;
}
if (precision < INTEGER_PRECISION) {
else if (precision < INTEGER_PRECISION)
return Integer.class;
}
if (precision < LONG_PRECISION) {
else if (precision < LONG_PRECISION)
return Long.class;
}
// Default integer number
return BigInteger.class;
}
else
return BigInteger.class;
// Real numbers should not be represented as float or double
else {
else
return BigDecimal.class;
}
}
static Collection<Class<?>> types() {
static final Collection<Class<?>> types() {
return unmodifiableCollection(SQL_DATATYPES_BY_TYPE.keySet());
}
static Collection<DataType<?>> dataTypes() {
static final Collection<DataType<?>> dataTypes() {
return unmodifiableCollection(SQL_DATATYPES_BY_TYPE.values());
}
}