[jOOQ/jOOQ#14799] Add support for TRY_CAST()

This commit is contained in:
Lukas Eder 2023-09-04 13:30:09 +02:00
parent f5ca5ae269
commit cb9ff43fcd
7 changed files with 298 additions and 14 deletions

View File

@ -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<T> extends AbstractField<T> implements QOM.Cast<T> {
static class CastNative<T> extends AbstractQueryPart implements UTransient {
private final QueryPart expression;
private final DataType<T> type;
private final Keyword typeAsKeyword;
final QueryPart expression;
final DataType<T> type;
final Keyword typeAsKeyword;
final boolean tryCast;
CastNative(QueryPart expression, DataType<T> type) {
this(expression, type, false);
}
CastNative(QueryPart expression, DataType<T> 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<T> extends AbstractField<T> implements QOM.Cast<T> {
else
c.sql(type.getCastTypeName(c.configuration()));
}
},
tryCast
);
}
}
@ -350,20 +362,50 @@ final class Cast<T> extends AbstractField<T> implements QOM.Cast<T> {
ThrowingConsumer<? super Context<?>, E> expression,
ThrowingConsumer<? super Context<?>, E> type
) throws E {
renderCast(ctx, expression, type, false);
}
static <E extends Throwable> void renderCast(
Context<?> ctx,
ThrowingConsumer<? super Context<?>, E> expression,
ThrowingConsumer<? super Context<?>, 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(')');
}

View File

@ -20934,6 +20934,30 @@ public class DSL {
return new Nullif<>(value, other);
}
/**
* The <code>TRY_CAST</code> 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 <T> Field<T> tryCast(Object value, DataType<T> dataType) {
return new TryCast<>(Tools.field(value), dataType);
}
/**
* The <code>TRY_CAST</code> 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 <T> Field<T> tryCast(Field<?> value, DataType<T> dataType) {
return new TryCast<>(value, dataType);
}
// -------------------------------------------------------------------------
// System functions
// -------------------------------------------------------------------------

View File

@ -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");

View File

@ -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");

View File

@ -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;

View File

@ -6010,6 +6010,40 @@ public final class QOM {
@NotNull default Nullif<T> $other(Field<T> newOther) { return $arg2(newOther); }
}
/**
* The <code>TRY CAST</code> function.
*/
public /*sealed*/ interface TryCast<T>
extends
UOperator2<Field<?>, DataType<T>, TryCast<T>>,
org.jooq.Field<T>
//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<T> $value(Field<?> newValue) { return $arg1(newValue); }
/**
* The data type to try to cast the value to
*/
@NotNull default DataType<T> $dataType() { return $arg2(); }
/**
* The data type to try to cast the value to
*/
@CheckReturnValue
@NotNull default TryCast<T> $dataType(DataType<T> newDataType) { return $arg2(newDataType); }
}
/**
* The <code>CURRENT CATALOG</code> function.
*/

View File

@ -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 <code>TRY CAST</code> statement.
*/
@SuppressWarnings({ "rawtypes", "unchecked", "unused" })
final class TryCast<T>
extends
AbstractField<T>
implements
QOM.TryCast<T>
{
final Field<?> value;
final DataType<T> dataType;
TryCast(
Field<?> value,
DataType<T> 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<T> $arg2() {
return dataType;
}
@Override
public final QOM.TryCast<T> $arg1(Field<?> newValue) {
return $constructor().apply(newValue, $arg2());
}
@Override
public final QOM.TryCast<T> $arg2(DataType<T> newValue) {
return $constructor().apply($arg1(), newValue);
}
@Override
public final Function2<? super Field<?>, ? super DataType<T>, ? extends QOM.TryCast<T>> $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);
}
}