Skip to content

Commit

Permalink
Fix #474
Browse files Browse the repository at this point in the history
  • Loading branch information
babyfish-ct committed Mar 23, 2024
1 parent 00f2b60 commit 293faad
Show file tree
Hide file tree
Showing 28 changed files with 963 additions and 377 deletions.
2 changes: 1 addition & 1 deletion project/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
allprojects {
group = "org.babyfish.jimmer"
version = "0.8.112"
version = "0.8.113"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
public class JimmerVersion {

public static final JimmerVersion CURRENT =
new JimmerVersion(0, 8, 112);
new JimmerVersion(0, 8, 113);

private final int major;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.babyfish.jimmer.sql.kt.common

import org.babyfish.jimmer.sql.kt.ast.KExecutable
import org.babyfish.jimmer.sql.kt.ast.expression.value
import org.babyfish.jimmer.sql.kt.ast.mutation.KBatchSaveResult
import org.babyfish.jimmer.sql.kt.ast.mutation.KMutationResult
import org.babyfish.jimmer.sql.kt.ast.mutation.KSimpleSaveResult
Expand Down Expand Up @@ -83,6 +84,26 @@ abstract class AbstractMutationTest : AbstractTest() {
}
}

protected fun <T> connectAndExpect(
action: (Connection) -> T,
block: ExpectDSLWithValue<T>.() -> Unit
) {
jdbc(null, true) { con ->
clearExecutions()
var value: T?
var throwable: Throwable? = null
try {
value = action(con)
} catch (ex: Throwable) {
throwable = ex
value = null
}
val dsl = ExpectDSLWithValue(executions, throwable, value)
block(dsl)
dsl.close()
}
}

private fun assertRowCount(
throwable: Throwable?,
rowCount: Int,
Expand Down Expand Up @@ -234,6 +255,29 @@ abstract class AbstractMutationTest : AbstractTest() {
}
}

protected class ExpectDSLWithValue<T>(
executions: List<Execution>,
throwable: Throwable?,
private val value: T?
) : ExpectDSL(executions, throwable) {
private var entityCount = 0
fun value(value: String): ExpectDSLWithValue<T> {
assertContentEquals(
value,
this.value?.toString() ?: ""
)
return this
}

override fun close() {
super.close()
assertTrue(
value != null,
"value"
)
}
}

protected class StatementDSL internal constructor(
private val index: Int,
private val execution: Execution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.babyfish.jimmer.sql.kt.common
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import org.babyfish.jimmer.jackson.ImmutableModule
import org.babyfish.jimmer.sql.dialect.H2Dialect
import org.babyfish.jimmer.sql.kt.KSqlClient
import org.babyfish.jimmer.sql.kt.cfg.KSqlClientDsl
import org.babyfish.jimmer.sql.kt.model.ENTITY_MANAGER
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.babyfish.jimmer.sql.kt.mutation

import org.babyfish.jimmer.kt.new
import org.babyfish.jimmer.sql.ast.mutation.SaveMode
import org.babyfish.jimmer.sql.dialect.H2Dialect
import org.babyfish.jimmer.sql.kt.common.AbstractMutationTest
import org.babyfish.jimmer.sql.kt.model.embedded.Machine
import org.babyfish.jimmer.sql.kt.model.embedded.by
import org.junit.Test

class H2MutationTest : AbstractMutationTest() {

@Test
fun testInsertEmbeddedJson() {
connectAndExpect({
sqlClient { setDialect(H2Dialect()) }.entities.save(
new(Machine::class).by {
id = 10L
detail().apply {
factories = mapOf(
"F-A" to "Factory-A",
"F-B" to "Factory-B"
)
patents = mapOf(
"P-I" to "Patent-I",
"P-II" to "Patent-II"
)
}
},
con = it
) {
setMode(SaveMode.INSERT_ONLY)
}.totalAffectedRowCount to sqlClient.entities.forConnection(it).findById(
Machine::class,
10L
)
}) {
statement {
sql(
"""insert into MACHINE(ID, factory_map, patent_map)
|values(?, ? format json, ? format json)""".trimMargin()
)
}
statement {
sql(
"""select tb_1_.ID, tb_1_.factory_map, tb_1_.patent_map from MACHINE tb_1_ where tb_1_.ID = ?""".trimMargin()
)
}
value(
"""(
|--->1,
|--->{
|--->--->"id":10,
|--->--->"detail":{
|--->--->--->"factories":{"F-A":"Factory-A","F-B":"Factory-B"},
|--->--->--->"patents":{"P-I":"Patent-I","P-II":"Patent-II"}
|--->--->}
|--->}
|)""".trimMargin()
)
}
}

@Test
fun testUpdateEmbeddedJson() {
connectAndExpect({
sqlClient { setDialect(H2Dialect()) }.entities.save(
new(Machine::class).by {
id = 1L
detail().apply {
patents = mapOf(
"P-I" to "Patent-I",
"P-II" to "Patent-II"
)
}
},
con = it
) {
setMode(SaveMode.UPDATE_ONLY)
}.totalAffectedRowCount to sqlClient.entities.forConnection(it).findById(
Machine::class,
1L
)
}) {
statement {
sql(
"""update MACHINE
|set patent_map = ? format json
|where ID = ?""".trimMargin()
)
}
statement {
sql(
"""select tb_1_.ID, tb_1_.factory_map, tb_1_.patent_map
|from MACHINE tb_1_
|where tb_1_.ID = ?""".trimMargin()
)
}
value(
"""(
|--->1,
|--->{
|--->--->"id":1,
|--->--->"detail":{
|--->--->--->"factories":{"f-1":"factory-1","f-2":"factory-2"},
|--->--->--->"patents":{"P-I":"Patent-I","P-II":"Patent-II"}
|--->--->}
|--->}
|)""".trimMargin()
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.babyfish.jimmer.sql.kt.query

import org.babyfish.jimmer.kt.new
import org.babyfish.jimmer.sql.ast.mutation.SaveMode
import org.babyfish.jimmer.sql.kt.ast.expression.eq
import org.babyfish.jimmer.sql.kt.common.AbstractQueryTest
import org.babyfish.jimmer.sql.kt.common.assertContentEquals
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ class ScalarProviderManager {
public boolean isJsonScalar() {
return true;
}

@Override
public String toString() {
return "JacksonScalarProvider";
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.babyfish.jimmer.sql.ast.Predicate;
import org.babyfish.jimmer.sql.ast.Selection;
import org.babyfish.jimmer.sql.ast.impl.associated.VirtualPredicateMergedResult;
import org.babyfish.jimmer.sql.ast.impl.mutation.IdentityMap;
import org.babyfish.jimmer.sql.ast.impl.query.*;
import org.babyfish.jimmer.sql.ast.impl.table.StatementContext;
import org.babyfish.jimmer.sql.ast.impl.table.TableImplementor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,13 @@ private static void render(Object value, Class<?> type, Expression<?> matchedExp
} else if (value != null) {
ImmutableProp prop = null;
if (matchedExpr instanceof PropExpressionImplementor<?>) {
prop = ((PropExpressionImplementor<?>) matchedExpr).getProp();
prop = ((PropExpressionImplementor<?>) matchedExpr).getDeepestProp();
}
builder.variable(Variables.process(value, prop, builder.getAstContext().getSqlClient()));
builder.variable(
prop != null ?
Variables.process(value, prop, builder.getAstContext().getSqlClient()) :
null
);
} else {
builder.nullVariable(type);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,77 +1,86 @@
package org.babyfish.jimmer.sql.ast.impl;

import org.babyfish.jimmer.impl.util.Classes;
import org.babyfish.jimmer.meta.EmbeddedLevel;
import org.babyfish.jimmer.meta.ImmutableProp;
import org.babyfish.jimmer.meta.TargetLevel;
import org.babyfish.jimmer.runtime.ImmutableSpi;
import org.babyfish.jimmer.sql.collection.TypedList;
import org.babyfish.jimmer.sql.meta.SingleColumn;
import org.babyfish.jimmer.sql.meta.Storage;
import org.babyfish.jimmer.sql.runtime.DbLiteral;
import org.babyfish.jimmer.sql.runtime.ExecutionException;
import org.babyfish.jimmer.sql.runtime.JSqlClientImplementor;
import org.babyfish.jimmer.sql.runtime.ScalarProvider;
import org.babyfish.jimmer.sql.runtime.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Array;
import java.util.Collection;

public class Variables {

public static Object process(Object value, ImmutableProp prop, JSqlClientImplementor sqlClient) {
public static Object process(
@Nullable Object value,
@NotNull ImmutableProp prop,
@NotNull JSqlClientImplementor sqlClient
) {
return process(value, prop, true, sqlClient);
}

@SuppressWarnings("unchecked")
public static Object process(Object value, ImmutableProp prop, boolean applyScalarProvider, JSqlClientImplementor sqlClient) {
if (value != null && prop != null && prop.isReference(TargetLevel.ENTITY)) {
value = ((ImmutableSpi) value).__get(prop.getTargetType().getIdProp().getId());
public static Object process(
@Nullable Object value,
@NotNull ImmutableProp prop,
boolean applyScalarProvider,
@NotNull JSqlClientImplementor sqlClient
) {
if (value instanceof DbLiteral) {
return value;
}
ScalarProvider<Object, Object> scalarProvider = null;
if (applyScalarProvider) {
if (prop != null) {
scalarProvider = sqlClient.getScalarProvider(prop);
if (scalarProvider == null) {
scalarProvider = (ScalarProvider<Object, Object>) sqlClient.getScalarProvider(prop.getReturnClass());
}
}
if (scalarProvider == null && value != null) {
scalarProvider = (ScalarProvider<Object, Object>) sqlClient.getScalarProvider(value.getClass());
if (prop.isReference(TargetLevel.ENTITY)) {
if (value != null) {
value = ((ImmutableSpi) value).__get(prop.getTargetType().getIdProp().getId());
}
prop = prop.getTargetType().getIdProp();
}
if (scalarProvider != null) {
if (value == null) {
value = new DbLiteral.DbNull(scalarProvider.getSqlType());
} else {
if (prop.isEmbedded(EmbeddedLevel.SCALAR)) {
return new DbLiteral.DbValue(prop, value, false);
}
if (applyScalarProvider) {
ScalarProvider<Object, Object> scalarProvider = sqlClient.getScalarProvider(prop);
if (scalarProvider != null && value != null) {
try {
value = scalarProvider.toSql(value);
} catch (Exception ex) {
throw new ExecutionException(
"Cannot convert the value \"" +
"The value \"" +
value +
"\" by the scalar provider \"" +
scalarProvider.getClass().getName() +
"\"",
ex
"\" cannot be converted by the scalar provider \"" +
scalarProvider +
"\""
);
}
if (scalarProvider.isJsonScalar()) {
String suffix = sqlClient.getDialect().getJsonLiteralSuffix();
if (suffix != null) {
value = new DbLiteral.JsonWithSuffix(value, suffix);
}
}
}
}
if (value instanceof Collection<?>) {
if (prop != null) {
Object[] arr = (Object[]) Array.newInstance(Classes.boxTypeOf(prop.getElementClass()), ((Collection<?>)value).size());
((Collection<Object>)value).toArray(arr);
value = arr;
} else {
value = ((Collection<?>) value).toArray();
if (value == null) {
return new DbLiteral.DbNull(
scalarProvider != null ?
scalarProvider.getSqlType() :
prop.getReturnClass()
);
}
if (scalarProvider != null) {
return scalarProvider.isJsonScalar() ?
new DbLiteral.DbValue(prop, value, true) :
value;
}
}
if (value == null) {
return new DbLiteral.DbNull(prop.getReturnClass());
}
if (value instanceof Collection<?> && prop.isScalar(TargetLevel.ENTITY)) {
Object[] arr = (Object[]) Array.newInstance(Classes.boxTypeOf(prop.getElementClass()), ((Collection<?>) value).size());
((Collection<Object>) value).toArray(arr);
value = arr;
}
if (prop != null && value != null && value.getClass().isArray()) {
if (value.getClass().isArray()) {
Storage storage = prop.getStorage(sqlClient.getMetadataStrategy());
if (storage instanceof SingleColumn) {
SingleColumn singleColumn = (SingleColumn) storage;
Expand Down
Loading

0 comments on commit 293faad

Please sign in to comment.