diff --git a/jOOQ/src/main/java/org/jooq/Data.java b/jOOQ/src/main/java/org/jooq/Data.java new file mode 100644 index 0000000000..ab8a2e66f3 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/Data.java @@ -0,0 +1,79 @@ +/* + * Licensed 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 + * + * https://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. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * Apache-2.0 license and offer limited warranties, support, maintenance, and + * commercial database integrations. + * + * For more information, please visit: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq; + +import java.io.Serializable; +import java.sql.SQLXML; + +import org.jetbrains.annotations.NotNull; + +/** + * A type holding {@link #data()}, which is a {@link String} based + * representation of a SQL data type with no reasonable representation in the + * JDK or in JDBC. + *

+ * JDBC maps most types to JDK types, but some vendor specific types lack an + * equivalent in the JDK, or the relevant type is unsatisfactory (such as + * {@link SQLXML}, which is a resource). To work around such limitations, jOOQ + * establishes the set of {@link Data} types, with these properties: + *

+ *

+ * + * @author Lukas Eder + */ +public interface Data extends Serializable { + + /** + * Get the vendor specific {@link String} representation of this + * {@link Data} object. + */ + @NotNull + String data(); + +} diff --git a/jOOQ/src/main/java/org/jooq/Decfloat.java b/jOOQ/src/main/java/org/jooq/Decfloat.java new file mode 100644 index 0000000000..85d6022f0e --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/Decfloat.java @@ -0,0 +1,201 @@ +/* + * Licensed 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 + * + * https://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. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * Apache-2.0 license and offer limited warranties, support, maintenance, and + * commercial database integrations. + * + * For more information, please visit: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq; + +import java.math.BigDecimal; +import java.util.Objects; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A wrapper type for SQL:2016 standard DECFLOAT data types. + *

+ * The wrapper represents DECFLOAT {@link #data()} in serialised string form. A + * CAST(NULL AS DECFLOAT) value is represented by a + * null reference of type {@link Decfloat}, not as + * data() == null. This is consistent with jOOQ's general way of + * returning NULL from {@link Result} and {@link Record} methods. + */ +public class Decfloat implements Data { + + private final String data; + private transient BigDecimal coefficient; + private transient int exponent; + private transient Special special; + + private Decfloat(String data) { + this.data = String.valueOf(data); + } + + @Override + @NotNull + public final String data() { + return data; + } + + /** + * Create a new {@link Decfloat} instance from string data input. + */ + @NotNull + public static final Decfloat valueOf(String data) { + return new Decfloat(data); + } + + /** + * Create a new {@link Decfloat} instance from string data input. + *

+ * This is the same as {@link #valueOf(String)}, but it can be static + * imported. + */ + @NotNull + public static final Decfloat decfloat(String data) { + return new Decfloat(data); + } + + /** + * Create a new {@link Decfloat} instance from string data input, or + * null if the input is null. + */ + @Nullable + public static final Decfloat decfloatOrNull(String data) { + return data == null ? null : decfloat(data); + } + + @Override + public int hashCode() { + parse(); + + if (special != null) { + return special.hashCode(); + } + else if (coefficient != null) { + final int prime = 31; + int result = 1; + + result = prime * result + ((coefficient == null) ? 0 : coefficient.hashCode()); + result = prime * result + exponent; + + return result; + } + else + return data.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj instanceof Decfloat x) { + parse(); + x.parse(); + + if (special != null && x.special != null) + return special == x.special; + else if (coefficient != null && x.coefficient != null) + return Objects.equals(coefficient, x.coefficient) && exponent == x.exponent; + else + return Objects.equals(data, x.data); + } + return false; + + } + + @Override + public String toString() { + parse(); + + if (special != null) { + switch (special) { + case NAN: + return "NaN"; + case POSITIVE_INFINITY: + return "Infinity"; + case NEGATIVE_INFINITY: + return "-Infinity"; + } + } + else if (coefficient != null) + return coefficient + "E" + exponent; + + return String.valueOf(data); + } + + private void parse() { + if (coefficient != null || special != null) + return; + + switch (data) { + case "+NaN": + case "NaN": + special = Special.NAN; + break; + + case "+Infinity": + case "Infinity": + case "+Inf": + case "Inf": + special = Special.POSITIVE_INFINITY; + break; + + case "-Infinity": + case "-Inf": + special = Special.NEGATIVE_INFINITY; + break; + + default: { + int i = data.indexOf("E"); + if (i == -1) + i = data.indexOf("e"); + + try { + coefficient = new BigDecimal(data.substring(0, i)).stripTrailingZeros(); + exponent = Integer.parseInt(data.substring(i + 1)); + } + + // [#10880] If we cannot represent the value internally, then we'll just work with data + catch (NumberFormatException ignore) {} + break; + } + } + } + + private enum Special { + NAN, POSITIVE_INFINITY, NEGATIVE_INFINITY + } +} diff --git a/jOOQ/src/main/java/org/jooq/JSON.java b/jOOQ/src/main/java/org/jooq/JSON.java index 3ae4bca40c..621fd6adcd 100644 --- a/jOOQ/src/main/java/org/jooq/JSON.java +++ b/jOOQ/src/main/java/org/jooq/JSON.java @@ -37,8 +37,6 @@ */ package org.jooq; -import java.io.Serializable; - import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -68,7 +66,7 @@ import org.jetbrains.annotations.Nullable; *

  • {@link #toString()}
  • * */ -public final class JSON implements Serializable { +public final class JSON implements Data { private final String data; @@ -76,6 +74,7 @@ public final class JSON implements Serializable { this.data = String.valueOf(data); } + @Override @NotNull public final String data() { return data; diff --git a/jOOQ/src/main/java/org/jooq/JSONB.java b/jOOQ/src/main/java/org/jooq/JSONB.java index 8d67639f70..4c4b6d0b39 100644 --- a/jOOQ/src/main/java/org/jooq/JSONB.java +++ b/jOOQ/src/main/java/org/jooq/JSONB.java @@ -37,7 +37,6 @@ */ package org.jooq; -import java.io.Serializable; import java.util.Objects; import org.jooq.tools.json.JSONParser; @@ -75,7 +74,7 @@ import org.jetbrains.annotations.Nullable; *

    * The {@link #data()} content, however, is not normalised. */ -public final class JSONB implements Serializable { +public final class JSONB implements Data { private final String data; private transient Object parsed; @@ -84,6 +83,7 @@ public final class JSONB implements Serializable { this.data = String.valueOf(data); } + @Override @NotNull public final String data() { return data; diff --git a/jOOQ/src/main/java/org/jooq/Spatial.java b/jOOQ/src/main/java/org/jooq/Spatial.java index 62c4d45694..0936137972 100644 --- a/jOOQ/src/main/java/org/jooq/Spatial.java +++ b/jOOQ/src/main/java/org/jooq/Spatial.java @@ -37,10 +37,6 @@ */ package org.jooq; -import java.io.Serializable; - -import org.jetbrains.annotations.NotNull; - /** * A wrapper type for spatial data obtained from the database. *

    @@ -54,9 +50,7 @@ import org.jetbrains.annotations.NotNull; *

    * This data type is supported only by the commercial editions of jOOQ. */ -public interface Spatial extends Serializable { +public interface Spatial extends Data { - @NotNull - String data(); } diff --git a/jOOQ/src/main/java/org/jooq/XML.java b/jOOQ/src/main/java/org/jooq/XML.java index 8fae2e32ac..a550274caf 100644 --- a/jOOQ/src/main/java/org/jooq/XML.java +++ b/jOOQ/src/main/java/org/jooq/XML.java @@ -37,8 +37,6 @@ */ package org.jooq; -import java.io.Serializable; - import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -51,7 +49,7 @@ import org.jetbrains.annotations.Nullable; * consistent with jOOQ's general way of returning NULL from * {@link Result} and {@link Record} methods. */ -public final class XML implements Serializable { +public final class XML implements Data { private final String data; @@ -59,6 +57,7 @@ public final class XML implements Serializable { this.data = String.valueOf(data); } + @Override @NotNull public final String data() { return data; diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java index 05ce79f059..878f9f6eb9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java @@ -50,6 +50,8 @@ import static java.util.Arrays.asList; import static java.util.function.Function.identity; import static java.util.regex.Matcher.quoteReplacement; import static org.jooq.ContextConverter.scoped; +import static org.jooq.Decfloat.decfloat; +import static org.jooq.Decfloat.decfloatOrNull; import static org.jooq.Geography.geography; import static org.jooq.Geometry.geometry; // ... @@ -257,6 +259,7 @@ import org.jooq.Converter; import org.jooq.ConverterContext; import org.jooq.Converters; import org.jooq.DataType; +import org.jooq.Decfloat; import org.jooq.EnumType; import org.jooq.ExecuteScope; import org.jooq.Field; @@ -376,6 +379,8 @@ public class DefaultBinding implements Binding { return new DefaultDateBinding(dataType, converter); else if (type == DayToSecond.class) return new DefaultDayToSecondBinding(dataType, converter); + else if (type == Decfloat.class) + return new DefaultDecfloatBinding(dataType, converter); else if (type == Double.class || type == double.class) return new DefaultDoubleBinding(dataType, converter); else if (type == Float.class || type == float.class) @@ -875,6 +880,16 @@ public class DefaultBinding implements Binding { } } + if (dataType.getType() == Decfloat.class) { + switch (ctx.family()) { + + + case FIREBIRD: + case H2: + return true; + } + } + @@ -1969,6 +1984,43 @@ public class DefaultBinding implements Binding { } } + static final class DefaultDecfloatBinding extends InternalBinding { + + DefaultDecfloatBinding(DataType dataType, Converter converter) { + super(dataType, converter); + } + + @Override + final void set0(BindingSetStatementContext ctx, Decfloat value) throws SQLException { + ctx.statement().setString(ctx.index(), value.data()); + } + + @Override + final void set0(BindingSetSQLOutputContext ctx, Decfloat value) throws SQLException { + ctx.output().writeString(value.data()); + } + + @Override + final Decfloat get0(BindingGetResultSetContext ctx) throws SQLException { + return decfloatOrNull(ctx.resultSet().getString(ctx.index())); + } + + @Override + final Decfloat get0(BindingGetStatementContext ctx) throws SQLException { + return decfloatOrNull(ctx.statement().getString(ctx.index())); + } + + @Override + final Decfloat get0(BindingGetSQLInputContext ctx) throws SQLException { + return decfloatOrNull(ctx.input().readString()); + } + + @Override + final int sqltype(Statement statement, Configuration configuration) { + return Types.VARCHAR; + } + } + static final class DefaultBlobBinding extends InternalBinding { DefaultBlobBinding(DataType dataType, Converter converter) { diff --git a/jOOQ/src/main/java/org/jooq/impl/SQLDataType.java b/jOOQ/src/main/java/org/jooq/impl/SQLDataType.java index e0c01ca6e3..60c26aa478 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SQLDataType.java +++ b/jOOQ/src/main/java/org/jooq/impl/SQLDataType.java @@ -91,6 +91,7 @@ import java.util.UUID; import org.jooq.Configuration; import org.jooq.DataType; +import org.jooq.Decfloat; import org.jooq.Geography; import org.jooq.Geometry; import org.jooq.JSON; @@ -777,6 +778,32 @@ public final class SQLDataType { */ public static final DataType GEOMETRY = new BuiltInDataType<>(Geometry.class, "geometry"); + /** + * The {@link Decfloat} type. + *

    + * This is not a JDBC standard. This type handles DECFLOAT types where they + * are supported. + *

    + * If you want to opt out of code generation support for this type, you can + * specify /configuration/generator/generate/decfloatTypes to + * false. + */ + public static final DataType DECFLOAT = new BuiltInDataType<>(Decfloat.class, "decfloat(p)"); + + /** + * The {@link Decfloat} type. + *

    + * This is not a JDBC standard. This type handles DECFLOAT types where they + * are supported. + *

    + * If you want to opt out of code generation support for this type, you can + * specify /configuration/generator/generate/decfloatTypes to + * false. + */ + public static final DataType DECFLOAT(int precision) { + return DECFLOAT.precision(precision, 0); + } + // ------------------------------------------------------------------------- // Static initialisation of dialect-specific data types // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/util/firebird/FirebirdDataType.java b/jOOQ/src/main/java/org/jooq/util/firebird/FirebirdDataType.java index 2fdb289289..63f17d2e02 100644 --- a/jOOQ/src/main/java/org/jooq/util/firebird/FirebirdDataType.java +++ b/jOOQ/src/main/java/org/jooq/util/firebird/FirebirdDataType.java @@ -47,6 +47,7 @@ import java.time.Year; import java.util.UUID; import org.jooq.DataType; +import org.jooq.Decfloat; import org.jooq.JSON; import org.jooq.JSONB; import org.jooq.SQLDialect; @@ -103,6 +104,7 @@ public class FirebirdDataType { public static final DataType