org.jooq.impl.JSONEntryImpl Maven / Gradle / Ivy
/*
* 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
*
* http://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: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
// ...
import static org.jooq.SQLDialect.MARIADB;
import static org.jooq.SQLDialect.MYSQL;
// ...
import static org.jooq.impl.AbstractRowAsField.forceMultisetContent;
import static org.jooq.impl.DSL.NULL;
import static org.jooq.impl.DSL.case_;
import static org.jooq.impl.DSL.coalesce;
import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.function;
import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.inlined;
import static org.jooq.impl.DSL.not;
import static org.jooq.impl.DSL.nvl;
import static org.jooq.impl.DSL.toChar;
import static org.jooq.impl.DSL.when;
import static org.jooq.impl.Keywords.K_FORMAT;
import static org.jooq.impl.Keywords.K_JSON;
import static org.jooq.impl.Keywords.K_KEY;
import static org.jooq.impl.Keywords.K_VALUE;
import static org.jooq.impl.Names.N_HEX;
import static org.jooq.impl.Names.N_JSON;
import static org.jooq.impl.Names.N_JSON_ARRAY;
import static org.jooq.impl.Names.N_JSON_EXTRACT;
import static org.jooq.impl.Names.N_JSON_MERGE;
import static org.jooq.impl.Names.N_JSON_MERGE_PRESERVE;
import static org.jooq.impl.Names.N_JSON_QUERY;
import static org.jooq.impl.Names.N_RAWTOHEX;
import static org.jooq.impl.SQLDataType.BIT;
import static org.jooq.impl.SQLDataType.BOOLEAN;
import static org.jooq.impl.SQLDataType.JSON;
import static org.jooq.impl.SQLDataType.VARCHAR;
import static org.jooq.impl.Tools.combine;
import static org.jooq.impl.Tools.emulateMultiset;
import static org.jooq.impl.Tools.isScalarSubquery;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import org.jooq.Context;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.JSON;
import org.jooq.JSONEntry;
import org.jooq.JSONEntryValueStep;
import org.jooq.Param;
// ...
import org.jooq.QueryPart;
import org.jooq.Record1;
// ...
import org.jooq.SQLDialect;
import org.jooq.Scope;
import org.jooq.Select;
// ...
import org.jooq.conf.NestedCollectionEmulation;
/**
* The JSON object entry.
*
* @author Lukas Eder
*/
final class JSONEntryImpl extends AbstractQueryPart implements JSONEntry, JSONEntryValueStep {
static final Set SUPPORT_JSON_MERGE_PRESERVE = SQLDialect.supportedBy(MARIADB, MYSQL);
private final Field key;
private final Field value;
JSONEntryImpl(Field key) {
this(key, null);
}
JSONEntryImpl(Field key, Field value) {
this.key = key;
this.value = value;
}
@Override
public final Field key() {
return key;
}
@Override
public final Field value() {
return value;
}
@Override
public final JSONEntry value(X newValue) {
return value(Tools.field(newValue));
}
@Override
public final JSONEntry value(Field newValue) {
return new JSONEntryImpl<>(key, newValue);
}
@Override
public final JSONEntry value(Select extends Record1> newValue) {
return value(DSL.field(newValue));
}
@Override
public void accept(Context> ctx) {
switch (ctx.family()) {
case MARIADB:
case MYSQL:
case POSTGRES:
case SQLITE:
case YUGABYTEDB:
ctx.visit(key).sql(", ").visit(jsonCast(ctx, value));
break;
default:
ctx.visit(K_KEY).sql(' ').visit(key).sql(' ').visit(K_VALUE).sql(' ').visit(jsonCast(ctx, value));
break;
}
}
static final Function super Field>, ? extends Field>> jsonCastMapper(final Context> ctx) {
return field -> jsonCast(ctx, field);
}
@SuppressWarnings("unchecked")
static final Field> jsonCast(Context> ctx, Field> field) {
DataType> type = field.getDataType();
switch (ctx.family()) {
// [#10769] [#12141] Some dialects don't support auto conversions from X to JSON
case H2:
if (isType(type, UUID.class))
return field.cast(VARCHAR(36));
// This is fixed, but not in 2.1.210 yet:
// https://github.com/h2database/h2database/issues/3439
else if (type.isEnum())
return field.cast(VARCHAR);
else if (type.isTemporal())
return field.cast(VARCHAR);
// [#12134] No auto conversion available yet
else if (type.isBinary())
return function(N_RAWTOHEX, VARCHAR, field);
break;
// [#11025] These don't have boolean support outside of JSON
case MARIADB:
case MYSQL:
// [#10323] [#13089] An explicit CAST is needed for BIT(1) types,
// which jOOQ interprets as BOOLEAN, but which are
// serialised as binary by MySQL
if (type.getSQLDataType() == BIT)
return field.cast(BOOLEAN);
else if (isType(type, Boolean.class))
return booleanJsonExtract((Field) field);
break;
case SQLITE:
if (isType(type, Boolean.class))
return function(N_JSON, SQLDataType.JSON, booleanCase(field));
else if (type.isBinary())
return when(field.isNotNull(), function(N_HEX, VARCHAR, field));
break;
case POSTGRES:
case YUGABYTEDB:
if (field instanceof Param)
if (field.getType() != Object.class)
return field.cast(field.getDataType());
else
return field.cast(VARCHAR);
else
return field;
}
return field;
}
private static final Field> booleanJsonExtract(Field field) {
return Tools.isVal(field, v -> v.isInline())
? field
: case_(field)
.when(inline(true), booleanJsonExtract0(inline(true)))
.when(inline(false), booleanJsonExtract0(inline(false)));
}
private static final Field booleanJsonExtract0(Field> field) {
return function(N_JSON_EXTRACT, JSON, function(N_JSON_ARRAY, JSON, field), inline("$[0]"));
}
@SuppressWarnings("unchecked")
private static final Field booleanCase(Field> field) {
return case_((Field) field).when(inline(true), inline("true")).when(inline(false), inline("false"));
}
static final Field unescapeNestedJSON(Context> ctx, Field value) {
// [#12086] Avoid escaping nested JSON
// [#12168] Yet another MariaDB JSON un-escaping workaround https://jira.mariadb.org/browse/MDEV-26134
// [#12549] Using JSON_MERGE_PRESERVE doesn't work here, as we might not know the user content
if (isJSON(ctx, value.getDataType())) {
switch (ctx.family()) {
case MARIADB:
case MYSQL:
case SQLITE:
return function(
N_JSON_EXTRACT,
value.getDataType(),
value,
inline("$")
);
}
}
return value;
}
static final boolean isType(DataType> t, Class> type) {
return ConvertedDataType.delegate(t).getType() == type;
}
static final boolean isJSON(Context> ctx, DataType> type) {
DataType> t = ConvertedDataType.delegate(type);
return t.isJSON()
|| t.isEmbeddable() && forceMultisetContent(ctx, () -> t.getRow().size() > 1) && emulateMultisetWithJSON(ctx)
|| t.isRecord() && forceMultisetContent(ctx, () -> t.getRow().size() > 1) && emulateMultisetWithJSON(ctx)
|| t.isMultiset() && emulateMultisetWithJSON(ctx);
}
private static final boolean emulateMultisetWithJSON(Scope scope) {
return emulateMultiset(scope.configuration()) == NestedCollectionEmulation.JSON
|| emulateMultiset(scope.configuration()) == NestedCollectionEmulation.JSONB;
}
static final Field> jsonMerge(Scope scope, String empty, Field>... fields) {
return function(
SUPPORT_JSON_MERGE_PRESERVE.contains(scope.dialect()) ? N_JSON_MERGE_PRESERVE : N_JSON_MERGE,
fields[0].getDataType(),
combine(inline(empty), fields)
);
}
// -------------------------------------------------------------------------
// XXX: Query Object Model
// -------------------------------------------------------------------------
@Override
public final Field $key() {
return key;
}
@Override
public final Field> $value() {
return value;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy