384 lines
9.0 KiB
Java
384 lines
9.0 KiB
Java
/*
|
|
* 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 java.util.stream.Collectors.joining;
|
|
// ...
|
|
import static org.jooq.conf.ParamType.INLINED;
|
|
import static org.jooq.conf.ParamType.NAMED;
|
|
import static org.jooq.conf.ParamType.NAMED_OR_INLINED;
|
|
import static org.jooq.impl.AbstractRowAsField.acceptMultisetContent;
|
|
import static org.jooq.impl.AbstractRowAsField.forceMultisetContent;
|
|
import static org.jooq.impl.DSL.sql;
|
|
import static org.jooq.impl.QueryPartListView.wrap;
|
|
import static org.jooq.impl.SQLDataType.OTHER;
|
|
import static org.jooq.impl.SQLDataType.VARCHAR;
|
|
import static org.jooq.impl.Tools.embeddedFields;
|
|
import static org.jooq.impl.Tools.map;
|
|
import static org.jooq.impl.Tools.row0;
|
|
import static org.jooq.impl.Tools.BooleanDataKey.DATA_LIST_ALREADY_INDENTED;
|
|
import static org.jooq.tools.StringUtils.defaultIfNull;
|
|
|
|
import java.sql.Date;
|
|
import java.sql.SQLException;
|
|
import java.sql.SQLWarning;
|
|
import java.sql.Time;
|
|
import java.sql.Timestamp;
|
|
import java.time.Instant;
|
|
import java.time.OffsetDateTime;
|
|
import java.time.OffsetTime;
|
|
import java.time.Year;
|
|
import java.util.Arrays;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
|
|
import org.jooq.Context;
|
|
import org.jooq.DataType;
|
|
import org.jooq.Field;
|
|
import org.jooq.JSONB;
|
|
import org.jooq.Param;
|
|
// ...
|
|
import org.jooq.RenderContext;
|
|
import org.jooq.conf.ParamType;
|
|
import org.jooq.exception.DataAccessException;
|
|
import org.jooq.impl.QOM.UEmpty;
|
|
import org.jooq.tools.JooqLogger;
|
|
import org.jooq.tools.StringUtils;
|
|
import org.jooq.types.DayToSecond;
|
|
import org.jooq.types.UByte;
|
|
import org.jooq.types.UInteger;
|
|
import org.jooq.types.ULong;
|
|
import org.jooq.types.UShort;
|
|
import org.jooq.types.YearToMonth;
|
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
/**
|
|
* @author Lukas Eder
|
|
*/
|
|
final class Val<T> extends AbstractParam<T> implements UEmpty {
|
|
|
|
private static final JooqLogger log = JooqLogger.getLogger(Val.class);
|
|
private static final ConcurrentHashMap<Class<?>, Object> legacyWarnings = new ConcurrentHashMap<>();
|
|
|
|
/**
|
|
* [#14694] Whether the data type was inferred as opposed to provided
|
|
* explicitly.
|
|
* <p>
|
|
* Numerous features depend on an inferred data type being overriden lazily
|
|
* once the information is available, e.g. when passing around a row(1, 2)
|
|
* to an <code>INSERT</code> statement, the initial type information should
|
|
* be overridden once the row is copied to the statement. It is different
|
|
* from when users provide type information explicitly, such as row(val(1,
|
|
* INTEGER), val(2, INTEGER)).
|
|
*/
|
|
final boolean inferredDataType;
|
|
|
|
Val(T value, DataType<T> type, boolean inferredDataType) {
|
|
super(value, type(value, type));
|
|
|
|
this.inferredDataType = inferredDataType;
|
|
}
|
|
|
|
Val(T value, DataType<T> type, boolean inferredDataType, String paramName) {
|
|
super(value, type(value, type), paramName);
|
|
|
|
this.inferredDataType = inferredDataType;
|
|
}
|
|
|
|
private static final <T> DataType<T> type(T value, DataType<T> type) {
|
|
return value == null ? type.null_() : type.notNull();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// XXX: Field API
|
|
// ------------------------------------------------------------------------
|
|
|
|
/**
|
|
* [#10438] Convert this bind value to a new type.
|
|
*/
|
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
|
final <U> Param<U> convertTo(DataType<U> type) {
|
|
|
|
// [#10438] A user defined data type could was not provided explicitly,
|
|
// when wrapping a bind value in DSL::val or DSL::inline
|
|
if (getDataType() instanceof DataTypeProxy<?> p) {
|
|
|
|
// [#9492] Maintain legacy static type registry behaviour for now
|
|
if (p.type() instanceof LegacyConvertedDataType && type == SQLDataType.OTHER) {
|
|
type = (DataType) p.type();
|
|
|
|
if (legacyWarnings.size() < 8 && legacyWarnings.put(type.getType(), "") == null)
|
|
log.warn("Deprecation", "User-defined, converted data type " + type.getType() + " was registered statically, which will be unsupported in the future, see https://github.com/jOOQ/jOOQ/issues/9492. Please use explicit data types in generated code, or e.g. with DSL.val(Object, DataType), or DSL.inline(Object, DataType).", new SQLWarning("Static type registry usage"));
|
|
}
|
|
|
|
return convertTo0(type);
|
|
}
|
|
|
|
// [#10438] A data type conversion between built in data types was made
|
|
else if (type instanceof ConvertedDataType)
|
|
return convertTo0(type);
|
|
|
|
// [#11061] Infer bind value data types if they could not be defined eagerly, mostly from the parser.
|
|
// Cannot use convertTo0() here as long as Param.setValue() is possible (mutable Params)
|
|
else if (OTHER.equals(getDataType()))
|
|
return new ConvertedVal<>(this, type);
|
|
else
|
|
return (Val) this;
|
|
}
|
|
|
|
final Val<T> copy(Object newValue) {
|
|
Val<T> w = new Val<>(getDataType().convert(newValue), getDataType(), inferredDataType, getParamName());
|
|
w.setInline0(isInline());
|
|
return w;
|
|
}
|
|
|
|
final <U> Val<U> convertTo0(DataType<U> type) {
|
|
Val<U> w = new Val<>(type.convert(getValue()), type, inferredDataType, getParamName());
|
|
w.setInline0(isInline());
|
|
return w;
|
|
}
|
|
|
|
@Override
|
|
public void accept(Context<?> ctx) {
|
|
if (getDataType().isEmbeddable()) {
|
|
|
|
// TODO [#12021] [#12706] ROW must consistently follow MULTISET emulation
|
|
// [#12237] If a RowField is nested somewhere in MULTISET, we must apply
|
|
// the MULTISET emulation as well, here
|
|
if (forceMultisetContent(ctx, () -> embeddedFields(this).length > 1))
|
|
acceptMultisetContent(ctx, row0(embeddedFields(this)), this, this::acceptDefaultEmbeddable);
|
|
else
|
|
acceptDefaultEmbeddable(ctx);
|
|
}
|
|
else if (ctx instanceof RenderContext r) {
|
|
ParamType paramType = ctx.paramType();
|
|
|
|
if (isInline(ctx))
|
|
ctx.paramType(INLINED);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
getBinding().sql(new DefaultBindingSQLContext<>(ctx.configuration(), ctx.data(), r, value, getBindVariable(ctx)));
|
|
}
|
|
catch (SQLException e) {
|
|
throw new DataAccessException("Error while generating SQL for Binding", e);
|
|
}
|
|
|
|
ctx.paramType(paramType);
|
|
}
|
|
|
|
else {
|
|
|
|
// [#1302] Bind value only if it was not explicitly forced to be inlined
|
|
if (!isInline(ctx))
|
|
ctx.bindValue(value, this);
|
|
}
|
|
}
|
|
|
|
private void acceptDefaultEmbeddable(Context<?> ctx) {
|
|
ctx.data(DATA_LIST_ALREADY_INDENTED, true, c -> c.visit(wrap(embeddedFields(this))));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Get a bind variable, depending on value of
|
|
* {@link RenderContext#namedParams()}
|
|
*/
|
|
@NotNull
|
|
final String getBindVariable(Context<?> ctx) {
|
|
if (ctx.paramType() == NAMED || ctx.paramType() == NAMED_OR_INLINED) {
|
|
int index = ctx.peekIndex();
|
|
String prefix = defaultIfNull(ctx.settings().getRenderNamedParamPrefix(), ":");
|
|
|
|
if (StringUtils.isBlank(getParamName()))
|
|
return prefix + index;
|
|
else
|
|
return prefix + getParamName();
|
|
}
|
|
else {
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// XXX: Query Object Model
|
|
// -------------------------------------------------------------------------
|
|
|
|
@Override
|
|
public final Param<T> $value(T newValue) {
|
|
return copy(newValue);
|
|
}
|
|
|
|
@Override
|
|
public final Param<T> $inline(boolean inline) {
|
|
Val<T> w = new Val<>(value, getDataType(), inferredDataType, getParamName());
|
|
w.setInline0(inline);
|
|
return w;
|
|
}
|
|
}
|