diff --git a/jOOQ/src/main/java/org/jooq/impl/Cast.java b/jOOQ/src/main/java/org/jooq/impl/Cast.java index c88902da85..06436b2f76 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Cast.java +++ b/jOOQ/src/main/java/org/jooq/impl/Cast.java @@ -40,12 +40,13 @@ package org.jooq.impl; // ... import static org.jooq.impl.DSL.inline; import static org.jooq.impl.Keywords.K_AS; -import static org.jooq.impl.Keywords.K_CAST; -import static org.jooq.impl.Keywords.K_TRIM; +import static org.jooq.impl.Keywords.*; import static org.jooq.impl.Names.N_CAST; +import static org.jooq.impl.Names.N_SAFE_CAST; import static org.jooq.impl.Names.N_TO_CLOB; import static org.jooq.impl.Names.N_TO_DATE; import static org.jooq.impl.Names.N_TO_TIMESTAMP; +import static org.jooq.impl.Names.N_TRY_CAST; import static org.jooq.impl.Names.N_XMLTYPE; import static org.jooq.impl.SQLDataType.BOOLEAN; import static org.jooq.impl.SQLDataType.CHAR; @@ -305,25 +306,35 @@ final class Cast extends AbstractField implements QOM.Cast { + + + static class CastNative extends AbstractQueryPart implements UTransient { - private final QueryPart expression; - private final DataType type; - private final Keyword typeAsKeyword; + final QueryPart expression; + final DataType type; + final Keyword typeAsKeyword; + final boolean tryCast; CastNative(QueryPart expression, DataType type) { + this(expression, type, false); + } + + CastNative(QueryPart expression, DataType type, boolean tryCast) { this.expression = expression; this.type = type; this.typeAsKeyword = null; + this.tryCast = tryCast; } CastNative(QueryPart expression, Keyword typeAsKeyword) { this.expression = expression; this.type = null; this.typeAsKeyword = typeAsKeyword; + this.tryCast = false; } @Override @@ -340,7 +351,8 @@ final class Cast extends AbstractField implements QOM.Cast { else c.sql(type.getCastTypeName(c.configuration())); - } + }, + tryCast ); } } @@ -350,20 +362,50 @@ final class Cast extends AbstractField implements QOM.Cast { ThrowingConsumer, E> expression, ThrowingConsumer, E> type ) throws E { + renderCast(ctx, expression, type, false); + } + + static void renderCast( + Context ctx, + ThrowingConsumer, E> expression, + ThrowingConsumer, E> type, + boolean tryCast + ) throws E { // Avoid casting bind values inside an explicit cast... CastMode castMode = ctx.castMode(); - // Default rendering, if no special case has applied yet - ctx.visit(K_CAST).sql('(') - .castMode(CastMode.NEVER); + if (tryCast) { + switch (ctx.family()) { + + + + + + + + + + default: + ctx.visit(N_TRY_CAST); + break; + } + } + else + ctx.visit(K_CAST); + + ctx.sql('(').castMode(CastMode.NEVER); expression.accept(ctx); - - ctx.castMode(castMode) - .sql(' ').visit(K_AS).sql(' '); + ctx.castMode(castMode).sql(' ').visit(K_AS).sql(' '); type.accept(ctx); + + + + + + ctx.sql(')'); } diff --git a/jOOQ/src/main/java/org/jooq/impl/DSL.java b/jOOQ/src/main/java/org/jooq/impl/DSL.java index b748d08371..6dd023470d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DSL.java +++ b/jOOQ/src/main/java/org/jooq/impl/DSL.java @@ -20934,6 +20934,30 @@ public class DSL { return new Nullif<>(value, other); } + /** + * The TRY_CAST function. + * + * @param value The value to be cast to a data type + * @param dataType The data type to try to cast the value to + */ + @NotNull + @Support({ DUCKDB, TRINO }) + public static Field tryCast(Object value, DataType dataType) { + return new TryCast<>(Tools.field(value), dataType); + } + + /** + * The TRY_CAST function. + * + * @param value The value to be cast to a data type + * @param dataType The data type to try to cast the value to + */ + @NotNull + @Support({ DUCKDB, TRINO }) + public static Field tryCast(Field value, DataType dataType) { + return new TryCast<>(value, dataType); + } + // ------------------------------------------------------------------------- // System functions // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/Keywords.java b/jOOQ/src/main/java/org/jooq/impl/Keywords.java index bd279d3e51..20f7203049 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Keywords.java +++ b/jOOQ/src/main/java/org/jooq/impl/Keywords.java @@ -105,6 +105,7 @@ final class Keywords { static final Keyword K_CONTAINS = keyword("contains"); static final Keyword K_CONTENT = keyword("content"); static final Keyword K_CONTINUE = keyword("continue"); + static final Keyword K_CONVERSION = keyword("conversion"); static final Keyword K_COUNT = keyword("count"); static final Keyword K_CREATE = keyword("create"); static final Keyword K_CUBE = keyword("cube"); diff --git a/jOOQ/src/main/java/org/jooq/impl/Names.java b/jOOQ/src/main/java/org/jooq/impl/Names.java index 035600100c..79f64147e7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Names.java +++ b/jOOQ/src/main/java/org/jooq/impl/Names.java @@ -233,6 +233,7 @@ final class Names { static final Name N_ROWID = systemName("rowid"); static final Name N_ROWSFROM = systemName("rowsfrom"); static final Name N_ROW_NUMBER = systemName("row_number"); + static final Name N_SAFE_CAST = systemName("safe_cast"); static final Name N_SCHEMA_NAME = systemName("schema_name"); static final Name N_SECONDS_BETWEEN = systemName("seconds_between"); static final Name N_SEQ4 = systemName("seq4"); @@ -573,6 +574,7 @@ final class Names { static final Name N_TRANSLATE = systemName("translate"); static final Name N_TRIM = systemName("trim"); static final Name N_TRUNC = systemName("trunc"); + static final Name N_TRY_CAST = systemName("try_cast"); static final Name N_UCASE = systemName("ucase"); static final Name N_UNIQUE = systemName("unique"); static final Name N_UPDATING = systemName("updating"); diff --git a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java index 06189e3711..8dde1912e7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java @@ -382,6 +382,7 @@ import static org.jooq.impl.DSL.translate; import static org.jooq.impl.DSL.trim; import static org.jooq.impl.DSL.trueCondition; import static org.jooq.impl.DSL.trunc; +import static org.jooq.impl.DSL.tryCast; import static org.jooq.impl.DSL.unique; import static org.jooq.impl.DSL.unnest; import static org.jooq.impl.DSL.user; @@ -9086,6 +9087,8 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { return parseFunctionArgs2(() -> toField(parseNumericOp()), (f1, f2) -> shr(f1, f2)); else if ((field = parseFieldSysConnectByPathIf()) != null) return field; + else if ((field = parseFieldCastIf()) != null) + return field; else if (!ignoreProEdition() && parseFunctionNameIf("ST_AREA") && requireProEdition()) { @@ -9291,6 +9294,8 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { return parseFunctionArgs2((f1, f2) -> DSL.timestampDiff(f1, f2)); else if ((field = parseFieldTruncIf()) != null) return field; + else if ((field = parseFieldCastIf()) != null) + return field; break; @@ -11557,15 +11562,24 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { private final Field parseFieldCastIf() { boolean cast = parseFunctionNameIf("CAST"); boolean coerce = !cast && parseFunctionNameIf("COERCE"); + boolean tryCast = !cast && !coerce && parseFunctionNameIf("TRY_CAST", "SAFE_CAST"); - if (cast || coerce) { + if (cast || coerce || tryCast) { parse('('); Field field = parseField(); parseKeyword("AS"); DataType type = parseCastDataType(); + + if (!tryCast) + tryCast = parseKeywordIf("DEFAULT NULL ON CONVERSION ERROR"); + parse(')'); - return cast ? cast(field, type) : coerce(field, type); + return tryCast + ? tryCast(field, type) + : cast + ? cast(field, type) + : coerce(field, type); } return null; diff --git a/jOOQ/src/main/java/org/jooq/impl/QOM.java b/jOOQ/src/main/java/org/jooq/impl/QOM.java index b6f09fcc8b..122ee2f7b7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/QOM.java +++ b/jOOQ/src/main/java/org/jooq/impl/QOM.java @@ -6010,6 +6010,40 @@ public final class QOM { @NotNull default Nullif $other(Field newOther) { return $arg2(newOther); } } + /** + * The TRY CAST function. + */ + public /*sealed*/ interface TryCast + extends + UOperator2, DataType, TryCast>, + org.jooq.Field + //permits + // TryCast + { + + /** + * The value to be cast to a data type + */ + @NotNull default Field $value() { return $arg1(); } + + /** + * The value to be cast to a data type + */ + @CheckReturnValue + @NotNull default TryCast $value(Field newValue) { return $arg1(newValue); } + + /** + * The data type to try to cast the value to + */ + @NotNull default DataType $dataType() { return $arg2(); } + + /** + * The data type to try to cast the value to + */ + @CheckReturnValue + @NotNull default TryCast $dataType(DataType newDataType) { return $arg2(newDataType); } + } + /** * The CURRENT CATALOG function. */ diff --git a/jOOQ/src/main/java/org/jooq/impl/TryCast.java b/jOOQ/src/main/java/org/jooq/impl/TryCast.java new file mode 100644 index 0000000000..b4ba70ac8d --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/TryCast.java @@ -0,0 +1,167 @@ +/* + * 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 + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import static org.jooq.impl.DSL.*; +import static org.jooq.impl.Internal.*; +import static org.jooq.impl.Keywords.*; +import static org.jooq.impl.Names.*; +import static org.jooq.impl.SQLDataType.*; +import static org.jooq.impl.Tools.*; +import static org.jooq.impl.Tools.BooleanDataKey.*; +import static org.jooq.impl.Tools.ExtendedDataKey.*; +import static org.jooq.impl.Tools.SimpleDataKey.*; +import static org.jooq.SQLDialect.*; + +import org.jooq.*; +import org.jooq.Function1; +import org.jooq.Record; +import org.jooq.conf.*; +import org.jooq.impl.*; +import org.jooq.impl.QOM.*; +import org.jooq.tools.*; + +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + + +/** + * The TRY CAST statement. + */ +@SuppressWarnings({ "rawtypes", "unchecked", "unused" }) +final class TryCast +extends + AbstractField +implements + QOM.TryCast +{ + + final Field value; + final DataType dataType; + + TryCast( + Field value, + DataType dataType + ) { + super( + N_TRY_CAST, + dataType + ); + + this.value = nullSafeNotNull(value, OTHER); + this.dataType = dataType; + } + + // ------------------------------------------------------------------------- + // XXX: QueryPart API + // ------------------------------------------------------------------------- + + @Override + public final void accept(Context ctx) { + switch (ctx.family()) { + + + + + + + default: + ctx.visit(new Cast.CastNative<>(value, dataType, true)); + break; + } + } + + + + + + + + + + + + + + + // ------------------------------------------------------------------------- + // XXX: Query Object Model + // ------------------------------------------------------------------------- + + @Override + public final Field $arg1() { + return value; + } + + @Override + public final DataType $arg2() { + return dataType; + } + + @Override + public final QOM.TryCast $arg1(Field newValue) { + return $constructor().apply(newValue, $arg2()); + } + + @Override + public final QOM.TryCast $arg2(DataType newValue) { + return $constructor().apply($arg1(), newValue); + } + + @Override + public final Function2, ? super DataType, ? extends QOM.TryCast> $constructor() { + return (a1, a2) -> new TryCast<>(a1, a2); + } + + // ------------------------------------------------------------------------- + // XXX: The Object API + // ------------------------------------------------------------------------- + + @Override + public boolean equals(Object that) { + if (that instanceof QOM.TryCast o) { + return + StringUtils.equals($value(), o.$value()) && + StringUtils.equals($dataType(), o.$dataType()) + ; + } + else + return super.equals(that); + } +}