From 86007b3540ed9650eebab671ae0a46d96810fe5a Mon Sep 17 00:00:00 2001 From: lukaseder Date: Fri, 23 Mar 2018 15:25:08 +0100 Subject: [PATCH] [#7229] Add configuration to allow for treating DECIMAL types as BigDecimal --- .../java/org/jooq/util/GenerationTool.java | 1 + .../java/org/jooq/util/JavaGenerator.java | 7 ++- .../resources/org/jooq/web/manual-3.11.xml | 54 ++++++++++++++++ .../java/org/jooq/util/AbstractDatabase.java | 47 ++++++++------ .../util/AbstractTypedElementDefinition.java | 28 ++++++--- .../src/main/java/org/jooq/util/Database.java | 12 ++++ .../java/org/jooq/util/jaxb/Database.java | 46 ++++++++++++++ .../resources/xsd/jooq-codegen-3.11.0.xsd | 4 ++ .../java/org/jooq/impl/DefaultDataType.java | 61 ++++++++----------- 9 files changed, 195 insertions(+), 65 deletions(-) diff --git a/jOOQ-codegen/src/main/java/org/jooq/util/GenerationTool.java b/jOOQ-codegen/src/main/java/org/jooq/util/GenerationTool.java index 90fd19a7e5..0b48c47aea 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/util/GenerationTool.java +++ b/jOOQ-codegen/src/main/java/org/jooq/util/GenerationTool.java @@ -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()) }); diff --git a/jOOQ-codegen/src/main/java/org/jooq/util/JavaGenerator.java b/jOOQ-codegen/src/main/java/org/jooq/util/JavaGenerator.java index 00d5ff8ad2..85f3ecc124 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/util/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/util/JavaGenerator.java @@ -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())); diff --git a/jOOQ-manual/src/main/resources/org/jooq/web/manual-3.11.xml b/jOOQ-manual/src/main/resources/org/jooq/web/manual-3.11.xml index c5d90a85d5..8b46f75b05 100644 --- a/jOOQ-manual/src/main/resources/org/jooq/web/manual-3.11.xml +++ b/jOOQ-manual/src/main/resources/org/jooq/web/manual-3.11.xml @@ -16649,6 +16649,60 @@ public class CaseInsensitiveOrderProvider implements Comparator { + +
+ Zero Scale Decimal Types + +

+ A zero-scale decimal, such as DECIMAL(10) or NUMBER(10, 0) 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 BigDecimal or BigInteger: +

+ +
    +
  • NUMBER(2, 0) and less:
  • +
  • NUMBER(4, 0) and less:
  • +
  • NUMBER(9, 0) and less:
  • +
  • NUMBER(19, 0) and less:
  • +
+ +

+ If this is not a desireable default, it can be deactivated either explicitly on a per-column basis using , or globally using the following flag: +

+ +

+ XML configuration (standalone and Maven) +

+ + + + + true + + +]]> + +

+ Programmatic configuration +

+ + + +

+ Gradle configuration +

+ + + +
+
diff --git a/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java b/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java index 2ec7d4c724..3dd541c5dc 100644 --- a/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java @@ -105,21 +105,22 @@ public abstract class AbstractDatabase implements Database { private List regexFlags; private List 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 configuredCatalogs = new ArrayList(); - private List configuredSchemata = new ArrayList(); + private List configuredCatalogs = new ArrayList(); + private List configuredSchemata = new ArrayList(); private List configuredCustomTypes; private List configuredEnumTypes; private List configuredForcedTypes; @@ -160,8 +161,8 @@ public abstract class AbstractDatabase implements Database { private List routines; private List packages; private Relations relations; - private boolean includeRelations = true; - private boolean tableValuedFunctions = true; + private boolean includeRelations = true; + private boolean tableValuedFunctions = true; private transient Map> sequencesBySchema; private transient Map> 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; diff --git a/jOOQ-meta/src/main/java/org/jooq/util/AbstractTypedElementDefinition.java b/jOOQ-meta/src/main/java/org/jooq/util/AbstractTypedElementDefinition.java index af79bb7684..7c1feffbea 100644 --- a/jOOQ-meta/src/main/java/org/jooq/util/AbstractTypedElementDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/util/AbstractTypedElementDefinition.java @@ -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 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 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 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 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 } 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 } @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 referes to a diff --git a/jOOQ-meta/src/main/java/org/jooq/util/Database.java b/jOOQ-meta/src/main/java/org/jooq/util/Database.java index ae1280bef2..ae57d71191 100644 --- a/jOOQ-meta/src/main/java/org/jooq/util/Database.java +++ b/jOOQ-meta/src/main/java/org/jooq/util/Database.java @@ -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. */ diff --git a/jOOQ-meta/src/main/java/org/jooq/util/jaxb/Database.java b/jOOQ-meta/src/main/java/org/jooq/util/jaxb/Database.java index 317a4ad99f..24abd53997 100644 --- a/jOOQ-meta/src/main/java/org/jooq/util/jaxb/Database.java +++ b/jOOQ-meta/src/main/java/org/jooq/util/jaxb/Database.java @@ -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. *

@@ -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(""); } + if (forceIntegerTypesOnZeroScaleDecimals!= null) { + sb.append(""); + sb.append(forceIntegerTypesOnZeroScaleDecimals); + sb.append(""); + } if (tableValuedFunctions!= null) { sb.append(""); 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())); diff --git a/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.11.0.xsd b/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.11.0.xsd index a0af5728d2..6f18e6ddb9 100644 --- a/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.11.0.xsd +++ b/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.11.0.xsd @@ -672,6 +672,10 @@ This comparator can be used to influence the order of any object that is produce + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultDataType.java b/jOOQ/src/main/java/org/jooq/impl/DefaultDataType.java index 2fd78e6b7e..9ed983f54d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultDataType.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultDataType.java @@ -355,20 +355,15 @@ public class DefaultDataType implements DataType { } 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 implements DataType { return Convert.convert(objects, uType); } - public static DataType getDefaultDataType(String typeName) { + public static final DataType getDefaultDataType(String typeName) { return new DefaultDataType(SQLDialect.DEFAULT, Object.class, typeName, typeName); } - public static DataType getDefaultDataType(SQLDialect dialect, String typeName) { + public static final DataType getDefaultDataType(SQLDialect dialect, String typeName) { return new DefaultDataType(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 implements DataType { return result; } - public static DataType getDataType(SQLDialect dialect, Class type) { + public static final DataType getDataType(SQLDialect dialect, Class type) { return getDataType(dialect, type, null); } - public static DataType getDataType(SQLDialect dialect, Class type, DataType fallbackDataType) { + public static final DataType getDataType(SQLDialect dialect, Class type, DataType fallbackDataType) { // Treat primitive types the same way as their respective wrapper types type = (Class) wrapper(type); @@ -1008,19 +1003,18 @@ public class DefaultDataType implements DataType { /** * @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 implements DataType { /** * 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> types() { + static final Collection> types() { return unmodifiableCollection(SQL_DATATYPES_BY_TYPE.keySet()); } - static Collection> dataTypes() { + static final Collection> dataTypes() { return unmodifiableCollection(SQL_DATATYPES_BY_TYPE.values()); } }