From 293faade7b004bbe3c75c60124714b2bea56d162 Mon Sep 17 00:00:00 2001 From: babyfish-ct Date: Sat, 23 Mar 2024 21:45:47 +0800 Subject: [PATCH] Fix #474 --- project/build.gradle.kts | 2 +- .../org/babyfish/jimmer/JimmerVersion.java | 2 +- .../sql/kt/common/AbstractMutationTest.kt | 44 ++++ .../jimmer/sql/kt/common/AbstractTest.kt | 1 + .../jimmer/sql/kt/mutation/H2MutationTest.kt | 113 ++++++++ .../jimmer/sql/kt/query/EmbeddedTest.kt | 1 + .../jimmer/sql/ScalarProviderManager.java | 5 + .../impl/AbstractMutableStatementImpl.java | 1 + .../sql/ast/impl/ComparisonPredicates.java | 8 +- .../jimmer/sql/ast/impl/Variables.java | 93 ++++--- .../ast/impl/mutation/ChildTableOperator.java | 30 +-- .../ast/impl/mutation/EmbeddableObjects.java | 44 ++++ .../impl/{util => mutation}/IdentityMap.java | 2 +- .../sql/ast/impl/mutation/MutationItem.java | 248 ++++++++++++++++++ .../jimmer/sql/ast/impl/mutation/Saver.java | 57 ++-- .../sql/ast/impl/util/EmbeddableObjects.java | 108 -------- .../jimmer/sql/dialect/PostgresDialect.java | 8 - .../event/binlog/impl/BinLogDeserializer.java | 2 +- .../sql/event/binlog/impl/BinLogParser.java | 4 - .../jimmer/sql/runtime/DbLiteral.java | 129 ++++++--- .../jimmer/sql/runtime/DefaultExecutor.java | 71 +---- .../jimmer/sql/runtime/JdbcTypes.java | 62 +++++ .../jimmer/sql/runtime/ParameterIndex.java | 10 + .../jimmer/sql/runtime/ReaderManager.java | 23 +- .../jimmer/sql/runtime/SqlBuilder.java | 39 +-- .../sql/embedded/EmbeddedMutationTest.java | 6 +- .../babyfish/jimmer/sql/json/H2SaveTest.java | 225 +++++++++++++++- .../jimmer/sql/util/IdentityMapTest.java | 2 +- 28 files changed, 963 insertions(+), 377 deletions(-) create mode 100644 project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/mutation/H2MutationTest.kt create mode 100644 project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/EmbeddableObjects.java rename project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/{util => mutation}/IdentityMap.java (99%) create mode 100644 project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/MutationItem.java delete mode 100644 project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/util/EmbeddableObjects.java create mode 100644 project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/JdbcTypes.java create mode 100644 project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/ParameterIndex.java diff --git a/project/build.gradle.kts b/project/build.gradle.kts index 52772c8cae..40dfd3fb40 100644 --- a/project/build.gradle.kts +++ b/project/build.gradle.kts @@ -1,4 +1,4 @@ allprojects { group = "org.babyfish.jimmer" - version = "0.8.112" + version = "0.8.113" } diff --git a/project/jimmer-core/src/main/java/org/babyfish/jimmer/JimmerVersion.java b/project/jimmer-core/src/main/java/org/babyfish/jimmer/JimmerVersion.java index c3b20f757e..30c2c38579 100644 --- a/project/jimmer-core/src/main/java/org/babyfish/jimmer/JimmerVersion.java +++ b/project/jimmer-core/src/main/java/org/babyfish/jimmer/JimmerVersion.java @@ -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; diff --git a/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/common/AbstractMutationTest.kt b/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/common/AbstractMutationTest.kt index 9d7b9bdb25..1eadc67c83 100644 --- a/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/common/AbstractMutationTest.kt +++ b/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/common/AbstractMutationTest.kt @@ -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 @@ -83,6 +84,26 @@ abstract class AbstractMutationTest : AbstractTest() { } } + protected fun connectAndExpect( + action: (Connection) -> T, + block: ExpectDSLWithValue.() -> 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, @@ -234,6 +255,29 @@ abstract class AbstractMutationTest : AbstractTest() { } } + protected class ExpectDSLWithValue( + executions: List, + throwable: Throwable?, + private val value: T? + ) : ExpectDSL(executions, throwable) { + private var entityCount = 0 + fun value(value: String): ExpectDSLWithValue { + 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 diff --git a/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/common/AbstractTest.kt b/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/common/AbstractTest.kt index 53364f96bc..e57446ed39 100644 --- a/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/common/AbstractTest.kt +++ b/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/common/AbstractTest.kt @@ -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 diff --git a/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/mutation/H2MutationTest.kt b/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/mutation/H2MutationTest.kt new file mode 100644 index 0000000000..af755ea737 --- /dev/null +++ b/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/mutation/H2MutationTest.kt @@ -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() + ) + } + } +} \ No newline at end of file diff --git a/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/query/EmbeddedTest.kt b/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/query/EmbeddedTest.kt index d83eca1283..314a6cc99a 100644 --- a/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/query/EmbeddedTest.kt +++ b/project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/query/EmbeddedTest.kt @@ -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 diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ScalarProviderManager.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ScalarProviderManager.java index 94d4f97ba2..fa690b6c02 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ScalarProviderManager.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ScalarProviderManager.java @@ -227,6 +227,11 @@ class ScalarProviderManager { public boolean isJsonScalar() { return true; } + + @Override + public String toString() { + return "JacksonScalarProvider"; + } }; } diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/AbstractMutableStatementImpl.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/AbstractMutableStatementImpl.java index dd812d33c4..55eba0eb01 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/AbstractMutableStatementImpl.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/AbstractMutableStatementImpl.java @@ -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; diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/ComparisonPredicates.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/ComparisonPredicates.java index 24f3eae2b0..4e1877f368 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/ComparisonPredicates.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/ComparisonPredicates.java @@ -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); } diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/Variables.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/Variables.java index 690bb75f9e..78fcae6c37 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/Variables.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/Variables.java @@ -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 scalarProvider = null; - if (applyScalarProvider) { - if (prop != null) { - scalarProvider = sqlClient.getScalarProvider(prop); - if (scalarProvider == null) { - scalarProvider = (ScalarProvider) sqlClient.getScalarProvider(prop.getReturnClass()); - } - } - if (scalarProvider == null && value != null) { - scalarProvider = (ScalarProvider) 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 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)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) 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; diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/ChildTableOperator.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/ChildTableOperator.java index d9cac93eeb..c3c69d2f51 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/ChildTableOperator.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/ChildTableOperator.java @@ -9,11 +9,11 @@ import org.babyfish.jimmer.runtime.Internal; import org.babyfish.jimmer.sql.ast.Expression; import org.babyfish.jimmer.sql.ast.impl.AstContext; +import org.babyfish.jimmer.sql.ast.impl.Variables; import org.babyfish.jimmer.sql.ast.impl.query.FilterLevel; import org.babyfish.jimmer.sql.ast.impl.query.MutableRootQueryImpl; import org.babyfish.jimmer.sql.ast.impl.query.PaginationContextImpl; import org.babyfish.jimmer.sql.ast.impl.table.TableImplementor; -import org.babyfish.jimmer.sql.ast.impl.util.EmbeddableObjects; import org.babyfish.jimmer.sql.ast.table.Table; import org.babyfish.jimmer.sql.ast.tuple.Tuple3; import org.babyfish.jimmer.sql.meta.ColumnDefinition; @@ -194,28 +194,16 @@ private int setParentImpl(Object parentId, Collection childIds) { .sql("update ") .sql(parentProp.getDeclaringType().getTableName(strategy)) .enter(SqlBuilder.ScopeType.SET); - ColumnDefinition definition = parentProp.getStorage(strategy); - if (definition instanceof SingleColumn) { - builder.sql(((SingleColumn)definition).getName()).sql(" = "); - if (parentId == null) { + List items = MutationItem.create( + parentProp, + parentId + ); + for (MutationItem item : items) { + builder.separator().sql(item.columnName(strategy)).sql(" = "); + if (item.getValue() == null) { builder.sql("null"); } else { - builder.variable(parentId); - } - } else { - Object[] values = EmbeddableObjects.expand( - parentProp.getTargetType().getIdProp().getTargetType(), - parentId - ); - int size = definition.size(); - for (int i = 0; i < size; i++) { - builder.separator().sql(definition.name(i)).sql(" = "); - Object value = values[i]; - if (value == null) { - builder.sql("null"); - } else { - builder.variable(value); - } + builder.variable(Variables.process(item.getValue(), item.getProp(), sqlClient)); } } builder diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/EmbeddableObjects.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/EmbeddableObjects.java new file mode 100644 index 0000000000..acdada6645 --- /dev/null +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/EmbeddableObjects.java @@ -0,0 +1,44 @@ +package org.babyfish.jimmer.sql.ast.impl.mutation; + +import org.babyfish.jimmer.meta.EmbeddedLevel; +import org.babyfish.jimmer.meta.ImmutableProp; +import org.babyfish.jimmer.meta.ImmutableType; +import org.babyfish.jimmer.meta.PropId; +import org.babyfish.jimmer.runtime.ImmutableSpi; + +public class EmbeddableObjects { + + public static boolean isCompleted(Object embedded) { + if (!(embedded instanceof ImmutableSpi)) { + throw new IllegalArgumentException("The argument must be embeddable type"); + } + return isCompleted((ImmutableSpi) embedded); + } + + private static boolean isCompleted(ImmutableSpi spi) { + ImmutableType type = spi.__type(); + if (type.isEntity()) { + return isCompleted(spi, type.getIdProp()); + } + for (ImmutableProp prop : type.getProps().values()) { + if (!isCompleted(spi, prop)) { + return false; + } + } + return true; + } + + private static boolean isCompleted(ImmutableSpi spi, ImmutableProp prop) { + PropId propId = prop.getId(); + if (!spi.__isLoaded(propId)) { + return false; + } + if (prop.isEmbedded(EmbeddedLevel.SCALAR)) { + ImmutableSpi childSpi = (ImmutableSpi) spi.__get(propId); + if (childSpi != null && !isCompleted(childSpi)) { + return false; + } + } + return true; + } +} diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/util/IdentityMap.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/IdentityMap.java similarity index 99% rename from project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/util/IdentityMap.java rename to project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/IdentityMap.java index c861c690ec..fc01ebbac8 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/util/IdentityMap.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/IdentityMap.java @@ -1,4 +1,4 @@ -package org.babyfish.jimmer.sql.ast.impl.util; +package org.babyfish.jimmer.sql.ast.impl.mutation; import org.jetbrains.annotations.NotNull; diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/MutationItem.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/MutationItem.java new file mode 100644 index 0000000000..73b9e43f2a --- /dev/null +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/MutationItem.java @@ -0,0 +1,248 @@ +package org.babyfish.jimmer.sql.ast.impl.mutation; + +import org.babyfish.jimmer.meta.*; +import org.babyfish.jimmer.runtime.ImmutableSpi; +import org.babyfish.jimmer.sql.ast.PropExpression; +import org.babyfish.jimmer.sql.ast.table.Table; +import org.babyfish.jimmer.sql.meta.ColumnDefinition; +import org.babyfish.jimmer.sql.meta.MetadataStrategy; +import org.babyfish.jimmer.sql.meta.SingleColumn; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public interface MutationItem { + + ImmutableProp getProp(); + + List getProps(); + + Object getValue(); + + String columnName(MetadataStrategy strategy); + + PropExpression expression(Table table); + + /** + * When property is an entity reference property, `value` can be + * either associated object or associated id + */ + static List create(ImmutableProp prop, Object value) { + if (prop.isReference(TargetLevel.ENTITY)) { + ImmutableProp targetIdProp = prop.getTargetType().getIdProp(); + // Important value can be either associated object or associated id + if (value instanceof ImmutableSpi && ((ImmutableSpi)value).__type().isEntity()) { + ImmutableSpi spi = (ImmutableSpi) value; + PropId idPropId = targetIdProp.getId(); + if (spi.__isLoaded(idPropId)) { + value = spi.__get(idPropId); + } + } + if (targetIdProp.isEmbedded(EmbeddedLevel.SCALAR)) { + return ChainedMutationItemImpl.expand(prop, targetIdProp.getTargetType(), value); + } + return Collections.singletonList( + new ChainedMutationItemImpl( + Arrays.asList(prop, targetIdProp), + value, + 0 + ) { + @Override + public PropExpression expression(Table table) { + return table.getAssociatedId(prop); + } + } + ); + } + if (prop.isEmbedded(EmbeddedLevel.SCALAR)) { + return ChainedMutationItemImpl.expand(prop, prop.getTargetType(), value); + } + return Collections.singletonList(new SingleMutationImpl(prop, value)); + } +} + +class SingleMutationImpl implements MutationItem { + + private final ImmutableProp prop; + + private final Object value; + + SingleMutationImpl(ImmutableProp prop, Object value) { + this.prop = prop; + this.value = value; + } + + @Override + public ImmutableProp getProp() { + return prop; + } + + @Override + public List getProps() { + return Collections.singletonList(prop); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public String columnName(MetadataStrategy strategy) { + return prop.getStorage(strategy).getName(); + } + + @Override + public PropExpression expression(Table table) { + return table.get(prop); + } + + @Override + public String toString() { + return "SingleMutationImpl{" + + "prop=" + prop + + ", value=" + value + + '}'; + } +} + +class ChainedMutationItemImpl implements MutationItem { + + private final List props; + + private final Object value; + + private final int columnIndex; + + ChainedMutationItemImpl(List props, Object value, int columnIndex) { + if (value == Context.UNLOADED) { + throw new AssertionError("The value should not be UNLOADED flag"); + } + this.props = props; + this.value = value; + this.columnIndex = columnIndex; + } + + @Override + public ImmutableProp getProp() { + return props.get(props.size() - 1); + } + + @Override + public List getProps() { + return props; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public String columnName(MetadataStrategy strategy) { + return props.get(0).getStorage(strategy).name(columnIndex); + } + + @Override + public PropExpression expression(Table table) { + PropExpression expr = null; + for (ImmutableProp prop : props) { + if (expr != null) { + expr = ((PropExpression.Embedded) expr).get(prop); + } else { + expr = table.get(prop); + } + } + return expr; + } + + static List expand(ImmutableProp parentProp, ImmutableType type, Object obj) { + Context ctx = new Context(); + if (parentProp != null) { + ctx.push(parentProp); + } + collectItems(type, (ImmutableSpi) obj, ctx); + return ctx.toMutationItems(); + } + + private static void collectItems(ImmutableType type, Object obj, Context ctx) { + for (ImmutableProp prop : type.getProps().values()) { + if (prop.isFormula()) { + continue; + } + Object value; + if (obj != null && obj != Context.UNLOADED) { + ImmutableSpi spi = (ImmutableSpi) obj; + PropId propId = prop.getId(); + if (spi.__isLoaded(propId)) { + value = spi.__get(propId); + } else { + value = Context.UNLOADED; + } + } else { + value = obj; + } + if (prop.isEmbedded(EmbeddedLevel.BOTH)) { + ctx.push(prop); + try { + collectItems(prop.getTargetType(), (ImmutableSpi) value, ctx); + } finally { + ctx.pop(); + } + } else { + ctx.addResult(prop, value); + } + } + } + + @Override + public String toString() { + return "ChainedMutationItemImpl{" + + "props=" + props + + ", value=" + value + + ", columnIndex=" + columnIndex + + '}'; + } + + private static class Context { + + private static final Object UNLOADED = new Object(); + + private List stack = new ArrayList<>(); + + private int columnIndex; + + private List results = new ArrayList<>(); + + public void push(ImmutableProp prop) { + stack.add(prop); + } + + public void pop() { + stack.remove(stack.size() - 1); + } + + public void addResult(ImmutableProp prop, Object value) { + if (value == UNLOADED) { + columnIndex++; + return; + } + List props = new ArrayList<>(stack.size() + 1); + props.addAll(stack); + props.add(prop); + MutationItem item = new ChainedMutationItemImpl( + Collections.unmodifiableList(props), + value, + columnIndex++ + ); + results.add(item); + } + + public List toMutationItems() { + return Collections.unmodifiableList(results); + } + } +} + diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/Saver.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/Saver.java index ca6d21274a..c65d108962 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/Saver.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/Saver.java @@ -510,16 +510,13 @@ private void insert(DraftSpi draftSpi) { draftSpi.__set(type.getVersionProp().getId(), 0); } - List props = new ArrayList<>(); - List values = new ArrayList<>(); + List items = new ArrayList<>(); for (ImmutableProp prop : draftSpi.__type().getProps().values()) { if (prop.isColumnDefinition() && draftSpi.__isLoaded(prop.getId())) { - props.add(prop); - Object value = draftSpi.__get(prop.getId()); - values.add(Variables.process(value, prop, data.getSqlClient())); + items.addAll(MutationItem.create(prop, draftSpi.__get(prop.getId()))); } } - if (props.isEmpty()) { + if (items.isEmpty()) { throw new SaveException.NoNonIdProps( path, "Cannot insert \"" + @@ -533,8 +530,8 @@ private void insert(DraftSpi draftSpi) { .sql("insert into ") .sql(type.getTableName(strategy)) .enter(SqlBuilder.ScopeType.TUPLE); - for (ImmutableProp prop : props) { - builder.separator().definition(prop.getStorage(strategy)); + for (MutationItem item : items) { + builder.separator().sql(item.columnName(strategy)); } builder.leave(); if (id != null && idGenerator instanceof IdentityIdGenerator) { @@ -544,15 +541,9 @@ private void insert(DraftSpi draftSpi) { } } builder.enter(SqlBuilder.ScopeType.VALUES).enter(SqlBuilder.ScopeType.TUPLE); - int size = values.size(); - for (int i = 0; i < size; i++) { + for (MutationItem item : items) { builder.separator(); - Object value = values.get(i); - if (value != null) { - builder.variable(value); - } else { - builder.nullVariable(props.get(i)); - } + builder.variable(Variables.process(item.getValue(), item.getProp(), data.getSqlClient())); } builder.leave().leave(); @@ -639,8 +630,7 @@ private boolean update(DraftSpi draftSpi, ImmutableSpi original, boolean exclude callInterceptor(draftSpi, original); - List updatedProps = new ArrayList<>(); - List updatedValues = new ArrayList<>(); + List items = new ArrayList<>(); LockMode lockMode = data.getLockMode(); Integer version = null; BiFunction, Object, Predicate> lambda = @@ -653,9 +643,7 @@ private boolean update(DraftSpi draftSpi, ImmutableSpi original, boolean exclude if (prop.isVersion() && lockMode == LockMode.OPTIMISTIC) { version = (Integer) draftSpi.__get(prop.getId()); } else if (!prop.isId() && !excludeProps.contains(prop)) { - updatedProps.add(prop); - Object value = draftSpi.__get(prop.getId()); - updatedValues.add(Variables.process(value, prop, lambda == null, data.getSqlClient())); + items.addAll(MutationItem.create(prop, draftSpi.__get(prop.getId()))); } } } @@ -669,15 +657,15 @@ private boolean update(DraftSpi draftSpi, ImmutableSpi original, boolean exclude "\" is unloaded" ); } - if (updatedProps.isEmpty() && version == null) { + if (items.isEmpty() && version == null) { return false; } int rowCount; if (lambda != null) { - rowCount = executeUpdateWithLambda(draftSpi, updatedProps, updatedValues, version, lambda); + rowCount = executeUpdateWithLambda(draftSpi, items, version, lambda); } else { - rowCount = executeUpdateWithoutLambda(draftSpi, updatedProps, updatedValues, version); + rowCount = executeUpdateWithoutLambda(draftSpi, items, version); } if (rowCount != 0) { addOutput(AffectedTable.of(type), rowCount); @@ -701,8 +689,7 @@ private boolean update(DraftSpi draftSpi, ImmutableSpi original, boolean exclude @SuppressWarnings("unchecked") private int executeUpdateWithLambda( DraftSpi draftSpi, - List updatedProps, - List updatedValues, + List items, Integer version, BiFunction, Object, Predicate> lambda ) { @@ -718,9 +705,8 @@ private int executeUpdateWithLambda( } else { table = ((TableProxy)table).__disableJoin(GENERAL_OPTIMISTIC_DISABLED_JOIN_REASON); } - int updatedCount = updatedProps.size(); - for (int i = 0; i < updatedCount; i++) { - update.set(table.get(updatedProps.get(i)), updatedValues.get(i)); + for (MutationItem item : items) { + update.set(item.expression(table), item.getValue()); } update.where(table.get(idProp).eq(draftSpi.__get(idProp.getId()))); if (version != null) { @@ -734,8 +720,7 @@ private int executeUpdateWithLambda( private int executeUpdateWithoutLambda( DraftSpi draftSpi, - List updatedProps, - List updatedValues, + List items, Integer version ) { ImmutableType type = draftSpi.__type(); @@ -747,10 +732,14 @@ private int executeUpdateWithoutLambda( .sql(type.getTableName(strategy)) .enter(SqlBuilder.ScopeType.SET); - int updatedCount = updatedProps.size(); - for (int i = 0; i < updatedCount; i++) { - builder.separator().assignment(updatedProps.get(i), updatedValues.get(i)); + for (MutationItem item : items) { + builder + .separator() + .sql(item.columnName(strategy)) + .sql(" = ") + .variable(Variables.process(item.getValue(), item.getProp(), data.getSqlClient())); } + String versionColumName = null; if (version != null) { versionColumName = type.getVersionProp().getStorage(strategy).getName(); diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/util/EmbeddableObjects.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/util/EmbeddableObjects.java deleted file mode 100644 index d80c9b3644..0000000000 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/util/EmbeddableObjects.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.babyfish.jimmer.sql.ast.impl.util; - -import org.babyfish.jimmer.impl.util.TypeCache; -import org.babyfish.jimmer.meta.EmbeddedLevel; -import org.babyfish.jimmer.meta.ImmutableProp; -import org.babyfish.jimmer.meta.ImmutableType; -import org.babyfish.jimmer.meta.PropId; -import org.babyfish.jimmer.runtime.ImmutableSpi; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class EmbeddableObjects { - - private static final TypeCache>> FLAT_TYPES_CACHE = - new TypeCache<>(EmbeddableObjects::createFlatTypes, false); - - private EmbeddableObjects() {} - - public static List> expandTypes(ImmutableType type) { - return FLAT_TYPES_CACHE.get(type); - } - - public static Object[] expand(ImmutableType type, Object obj) { - if (obj != null && !type.getJavaClass().isAssignableFrom(obj.getClass())) { - throw new IllegalArgumentException( - "Illegal obj, it does not match the type \"" + - type + - "\"" - ); - } - Object[] values = new Object[FLAT_TYPES_CACHE.get(type).size()]; - if (obj != null) { - expandImpl((ImmutableSpi) obj, values, 0); - } - return values; - } - - private static int expandImpl(ImmutableSpi spi, Object[] values, int index) { - for (ImmutableProp prop : spi.__type().getProps().values()) { - Object value = spi.__get(prop.getId()); - if (prop.isEmbedded(EmbeddedLevel.SCALAR)) { - index = expandImpl((ImmutableSpi) value, values, index); - } else { - values[index++] = value; - } - } - return index; - } - - private static List> createFlatTypes(ImmutableType type) { - if (!type.isEmbeddable()) { - throw new IllegalArgumentException( - "Illegal obj, it does not match the type \"" + - type + - "\"" - ); - } - List> flatTypes = new ArrayList<>(); - collectFlatTypes(type, flatTypes); - return Collections.unmodifiableList(flatTypes); - } - - private static void collectFlatTypes(ImmutableType type, List> flatTypes) { - for (ImmutableProp prop : type.getProps().values()) { - if (prop.isEmbedded(EmbeddedLevel.SCALAR)) { - collectFlatTypes(prop.getTargetType(), flatTypes); - } else { - flatTypes.add(prop.getElementClass()); - } - } - } - - public static boolean isCompleted(Object embedded) { - if (!(embedded instanceof ImmutableSpi)) { - throw new IllegalArgumentException("The argument must be embeddable type"); - } - return isCompleted((ImmutableSpi) embedded); - } - - private static boolean isCompleted(ImmutableSpi spi) { - ImmutableType type = spi.__type(); - if (type.isEntity()) { - return isCompleted(spi, type.getIdProp()); - } - for (ImmutableProp prop : type.getProps().values()) { - if (!isCompleted(spi, prop)) { - return false; - } - } - return true; - } - - private static boolean isCompleted(ImmutableSpi spi, ImmutableProp prop) { - PropId propId = prop.getId(); - if (!spi.__isLoaded(propId)) { - return false; - } - if (prop.isEmbedded(EmbeddedLevel.SCALAR)) { - ImmutableSpi childSpi = (ImmutableSpi) spi.__get(propId); - if (childSpi != null && !isCompleted(childSpi)) { - return false; - } - } - return true; - } -} diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/dialect/PostgresDialect.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/dialect/PostgresDialect.java index 39faf1a051..d25170781c 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/dialect/PostgresDialect.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/dialect/PostgresDialect.java @@ -65,14 +65,6 @@ public T[] getArray(ResultSet rs, int col, Class arrayType) throws SQLE return (T[])rs.getArray(col).getArray(); } - @Override - public int resolveUnknownJdbcType(Class sqlType) { - if (sqlType.getName().equals("org.postgresql.util.PGobject")) { - return Types.NULL; - } - return Types.OTHER; - } - @Override public Reader unknownReader(Class sqlType) { if (sqlType == PGobject.class) { diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/event/binlog/impl/BinLogDeserializer.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/event/binlog/impl/BinLogDeserializer.java index 222b114014..b9a7782f37 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/event/binlog/impl/BinLogDeserializer.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/event/binlog/impl/BinLogDeserializer.java @@ -9,7 +9,7 @@ import org.babyfish.jimmer.meta.ImmutableType; import org.babyfish.jimmer.runtime.DraftSpi; import org.babyfish.jimmer.runtime.Internal; -import org.babyfish.jimmer.sql.ast.impl.util.EmbeddableObjects; +import org.babyfish.jimmer.sql.ast.impl.mutation.EmbeddableObjects; import org.babyfish.jimmer.sql.meta.MetadataStrategy; import java.io.IOException; diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/event/binlog/impl/BinLogParser.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/event/binlog/impl/BinLogParser.java index e661d58e1e..35848f9825 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/event/binlog/impl/BinLogParser.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/event/binlog/impl/BinLogParser.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import org.babyfish.jimmer.ImmutableObjects; import org.babyfish.jimmer.impl.util.PropCache; import org.babyfish.jimmer.lang.Lazy; import org.babyfish.jimmer.meta.*; @@ -12,7 +11,6 @@ import org.babyfish.jimmer.runtime.Internal; import org.babyfish.jimmer.sql.association.meta.AssociationProp; import org.babyfish.jimmer.sql.association.meta.AssociationType; -import org.babyfish.jimmer.sql.ast.impl.util.EmbeddableObjects; import org.babyfish.jimmer.sql.ast.tuple.Tuple2; import org.babyfish.jimmer.sql.event.binlog.BinLogPropReader; import org.babyfish.jimmer.sql.meta.JoinTableFilterInfo; @@ -22,9 +20,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.xml.crypto.Data; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/DbLiteral.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/DbLiteral.java index 77fb63aff1..15861dd083 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/DbLiteral.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/DbLiteral.java @@ -1,14 +1,18 @@ package org.babyfish.jimmer.sql.runtime; +import org.babyfish.jimmer.meta.EmbeddedLevel; +import org.babyfish.jimmer.meta.ImmutableProp; +import org.babyfish.jimmer.meta.ImmutableType; +import org.babyfish.jimmer.runtime.ImmutableSpi; + import java.sql.PreparedStatement; -import java.sql.SQLException; import java.util.Objects; public interface DbLiteral { Class getType(); - default void render(StringBuilder builder) { + default void render(StringBuilder builder, JSqlClientImplementor sqlClient) { builder.append('?'); } @@ -16,7 +20,7 @@ default void render(StringBuilder builder) { void renderToComment(StringBuilder builder); - void setParameter(PreparedStatement stmt, int index, int jdbcType) throws SQLException; + void setParameter(PreparedStatement stmt, ParameterIndex index, JSqlClientImplementor sqlClient) throws Exception; class DbNull implements DbLiteral { @@ -41,8 +45,8 @@ public void renderToComment(StringBuilder builder) { } @Override - public void setParameter(PreparedStatement stmt, int index, int jdbcType) throws SQLException { - stmt.setNull(index, jdbcType); + public void setParameter(PreparedStatement stmt, ParameterIndex index, JSqlClientImplementor sqlClient) throws Exception { + stmt.setNull(index.get(), JdbcTypes.toJdbcType(type, sqlClient.getDialect())); } @Override @@ -61,52 +65,97 @@ public boolean equals(Object o) { @Override public String toString() { return "DbNull{" + - "type=" + type + + "type=" + type.getName() + '}'; } } - class JsonWithSuffix implements DbLiteral { + class DbValue implements DbLiteral { + + private final ImmutableProp prop; private final Object value; - private final String suffix; + private final boolean converted; - public JsonWithSuffix(Object value, String suffix) { + public DbValue(ImmutableProp prop, Object value, boolean converted) { + if (value instanceof DbLiteral) { + throw new IllegalArgumentException("value cannot be DbLiteral"); + } + this.prop = prop; this.value = value; - this.suffix = suffix; + this.converted = converted; } @Override public Class getType() { - return value.getClass(); + return value != null ? value.getClass() : prop.getReturnClass(); } @Override - public void render(StringBuilder builder) { - builder.append("? ").append(suffix); + public void render(StringBuilder builder, JSqlClientImplementor sqlClient) { + builder.append('?'); + String suffix = sqlClient.getDialect().getJsonLiteralSuffix(); + if (value != null && suffix != null) { + ScalarProvider scalarProvider = sqlClient.getScalarProvider(prop); + if (scalarProvider != null && scalarProvider.isJsonScalar()) { + builder.append(' ').append(suffix); + } + } } @Override public void renderValue(StringBuilder builder) { - builder.append('"').append(value.toString().replace("'", "''")).append(' ').append(suffix); + if (value instanceof Number) { + builder.append("null"); + } else { + builder + .append('\'') + .append(value.toString().replace("'", "''")) + .append('\''); + } } @Override public void renderToComment(StringBuilder builder) { - builder.append(value).append(' ').append(suffix); - } - - @Override - public void setParameter(PreparedStatement stmt, int index, int jdbcType) throws SQLException { - stmt.setString(index, value.toString()); + builder.append(value.toString()); } @Override - public int hashCode() { - int result = value.hashCode(); - result = 31 * result + suffix.hashCode(); - return result; + public void setParameter(PreparedStatement stmt, ParameterIndex index, JSqlClientImplementor sqlClient) throws Exception { + Object value = this.value; + ScalarProvider scalarProvider = null; + if (value != null && !converted) { + scalarProvider = sqlClient.getScalarProvider(prop); + if (scalarProvider != null) { + try { + value = scalarProvider.toSql(value); + } catch (Exception ex) { + throw new ExecutionException( + "The value \"" + + value + + "\" cannot be converted by the scalar provider \"" + + scalarProvider + + "\"" + ); + } + } + } + if (value == null) { + stmt.setNull( + index.get(), + JdbcTypes.toJdbcType( + scalarProvider != null ? scalarProvider.getSqlType() : prop.getReturnClass(), + sqlClient.getDialect() + ) + ); + } else { + stmt.setObject( + index.get(), + value, + JdbcTypes.toJdbcType(value.getClass(), sqlClient.getDialect()) + ); + } } @Override @@ -114,20 +163,40 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - JsonWithSuffix that = (JsonWithSuffix) o; + DbValue dbValue = (DbValue) o; + + if (converted != dbValue.converted) return false; + if (!prop.equals(dbValue.prop)) return false; + return value.equals(dbValue.value); + } - if (!value.equals(that.value)) return false; - return suffix.equals(that.suffix); + @Override + public int hashCode() { + int result = prop.hashCode(); + result = 31 * result + value.hashCode(); + result = 31 * result + (converted ? 1 : 0); + return result; } @Override public String toString() { - return "JsonWithSuffix{" + - "value=" + value + - ", suffix='" + suffix + '\'' + + return "DbValue{" + + "prop=" + prop + + ", value=" + value + + ", converted=" + converted + '}'; } } + + static Object unwrap(Object value) { + if (value instanceof DbNull) { + return null; + } + if (value instanceof DbValue) { + return ((DbValue)value).value; + } + return value; + } } diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/DefaultExecutor.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/DefaultExecutor.java index 79a60f6008..3800189bb6 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/DefaultExecutor.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/DefaultExecutor.java @@ -14,41 +14,38 @@ public class DefaultExecutor implements Executor { public static final DefaultExecutor INSTANCE = new DefaultExecutor(); - private static final Map, Integer> SQL_TYPE_MAP; - DefaultExecutor() {} @Override public R execute(@NotNull Args args) { String sql = args.sql; List variables = args.variables; - Dialect dialect = args.sqlClient.getDialect(); + JSqlClientImplementor sqlClient = args.sqlClient; try (PreparedStatement stmt = args.statementFactory != null ? args.statementFactory.preparedStatement(args.con, sql) : args.con.prepareStatement(sql) ) { - int size = variables.size(); - for (int index = 0; index < size; index++) { - Object variable = variables.get(index); + ParameterIndex parameterIndex = new ParameterIndex(); + for (Object variable : variables) { if (variable instanceof DbLiteral) { DbLiteral literal = (DbLiteral) variable; literal.setParameter( stmt, - index + 1, - toJdbcType(literal.getType(), dialect) + parameterIndex, + sqlClient ); } else if (variable instanceof TypedList) { TypedList typedList = (TypedList) variable; stmt.setArray( - index + 1, + parameterIndex.get(), args.con.createArrayOf(typedList.getSqlElementType(), typedList.toArray()) ); } else { - stmt.setObject(index + 1, variable); + stmt.setObject(parameterIndex.get(), variable); } } return args.block.apply(stmt); - } catch (SQLException ex) { + } catch (Exception ex) { throw new ExecutionException( "Cannot execute SQL statement: " + sql + @@ -58,56 +55,4 @@ public R execute(@NotNull Args args) { ); } } - - private int toJdbcType(Class type, Dialect dialect) { - Integer sqlType = SQL_TYPE_MAP.get(type); - if (sqlType != null) { - return sqlType; - } - int jdbcType = dialect.resolveUnknownJdbcType(type); - if (jdbcType != Types.OTHER) { - return jdbcType; - } - throw new IllegalArgumentException( - "Cannot convert the sql type '" + - type + - "' to java.sql.Types by the current dialect \"" + - dialect.getClass().getName() + - "\"" - ); - } - - static { - Map, Integer> map = new HashMap<>(); - map.put(String.class, Types.VARCHAR); - map.put(boolean.class, Types.TINYINT); - map.put(Boolean.class, Types.TINYINT); - map.put(char.class, Types.CHAR); - map.put(Character.class, Types.CHAR); - map.put(byte.class, Types.TINYINT); - map.put(Byte.class, Types.TINYINT); - map.put(short.class, Types.SMALLINT); - map.put(Short.class, Types.SMALLINT); - map.put(int.class, Types.INTEGER); - map.put(Integer.class, Types.INTEGER); - map.put(long.class, Types.BIGINT); - map.put(Long.class, Types.BIGINT); - map.put(float.class, Types.FLOAT); - map.put(Float.class, Types.FLOAT); - map.put(double.class, Types.DOUBLE); - map.put(Double.class, Types.DOUBLE); - map.put(BigInteger.class, Types.DECIMAL); - map.put(BigDecimal.class, Types.DECIMAL); - map.put(UUID.class, Types.VARCHAR); - map.put(java.sql.Date.class, Types.DATE); - map.put(java.sql.Time.class, Types.TIME); - map.put(java.util.Date.class, Types.TIMESTAMP); - map.put(LocalDate.class, Types.DATE); - map.put(LocalTime.class, Types.TIME); - map.put(LocalDateTime.class, Types.TIMESTAMP); - map.put(OffsetDateTime.class, Types.TIMESTAMP_WITH_TIMEZONE); - map.put(ZonedDateTime.class, Types.TIMESTAMP_WITH_TIMEZONE); - map.put(byte[].class, Types.BINARY); - SQL_TYPE_MAP = map; - } } diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/JdbcTypes.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/JdbcTypes.java new file mode 100644 index 0000000000..7cb671bbf5 --- /dev/null +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/JdbcTypes.java @@ -0,0 +1,62 @@ +package org.babyfish.jimmer.sql.runtime; + +import org.babyfish.jimmer.sql.dialect.Dialect; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Types; +import java.time.*; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class JdbcTypes { + + private static final Map, Integer> SQL_TYPE_MAP; + + public static boolean isStandardType(Class type, Dialect dialect) { + return SQL_TYPE_MAP.containsKey(type) || dialect.resolveUnknownJdbcType(type) != Types.OTHER; + } + + public static int toJdbcType(Class type, Dialect dialect) { + Integer sqlType = SQL_TYPE_MAP.get(type); + if (sqlType != null) { + return sqlType; + } + return dialect.resolveUnknownJdbcType(type); + } + + static { + Map, Integer> map = new HashMap<>(); + map.put(String.class, Types.VARCHAR); + map.put(boolean.class, Types.TINYINT); + map.put(Boolean.class, Types.TINYINT); + map.put(char.class, Types.CHAR); + map.put(Character.class, Types.CHAR); + map.put(byte.class, Types.TINYINT); + map.put(Byte.class, Types.TINYINT); + map.put(short.class, Types.SMALLINT); + map.put(Short.class, Types.SMALLINT); + map.put(int.class, Types.INTEGER); + map.put(Integer.class, Types.INTEGER); + map.put(long.class, Types.BIGINT); + map.put(Long.class, Types.BIGINT); + map.put(float.class, Types.FLOAT); + map.put(Float.class, Types.FLOAT); + map.put(double.class, Types.DOUBLE); + map.put(Double.class, Types.DOUBLE); + map.put(BigInteger.class, Types.DECIMAL); + map.put(BigDecimal.class, Types.DECIMAL); + map.put(UUID.class, Types.VARCHAR); + map.put(java.sql.Date.class, Types.DATE); + map.put(java.sql.Time.class, Types.TIME); + map.put(java.util.Date.class, Types.TIMESTAMP); + map.put(LocalDate.class, Types.DATE); + map.put(LocalTime.class, Types.TIME); + map.put(LocalDateTime.class, Types.TIMESTAMP); + map.put(OffsetDateTime.class, Types.TIMESTAMP_WITH_TIMEZONE); + map.put(ZonedDateTime.class, Types.TIMESTAMP_WITH_TIMEZONE); + map.put(byte[].class, Types.BINARY); + SQL_TYPE_MAP = map; + } +} diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/ParameterIndex.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/ParameterIndex.java new file mode 100644 index 0000000000..9855b9154e --- /dev/null +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/ParameterIndex.java @@ -0,0 +1,10 @@ +package org.babyfish.jimmer.sql.runtime; + +public class ParameterIndex { + + private int index; + + public int get() { + return ++index; + } +} diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/ReaderManager.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/ReaderManager.java index 2be67a41d3..6362a2472b 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/ReaderManager.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/ReaderManager.java @@ -3,14 +3,13 @@ import org.apache.commons.lang3.ArrayUtils; import org.babyfish.jimmer.DraftConsumerUncheckedException; import org.babyfish.jimmer.sql.Serialized; -import org.babyfish.jimmer.sql.collection.TypedList; import org.babyfish.jimmer.impl.util.PropCache; import org.babyfish.jimmer.impl.util.TypeCache; import org.babyfish.jimmer.meta.*; import org.babyfish.jimmer.runtime.DraftSpi; import org.babyfish.jimmer.sql.association.Association; import org.babyfish.jimmer.sql.association.meta.AssociationType; -import org.babyfish.jimmer.sql.ast.impl.util.EmbeddableObjects; +import org.babyfish.jimmer.sql.ast.impl.mutation.EmbeddableObjects; import org.babyfish.jimmer.sql.dialect.Dialect; import org.babyfish.jimmer.sql.meta.ColumnDefinition; import org.babyfish.jimmer.sql.meta.FormulaTemplate; @@ -66,7 +65,7 @@ private Reader createPropReader(ImmutableProp prop) { Storage storage = prop.getStorage(sqlClient.getMetadataStrategy()); if (storage instanceof ColumnDefinition) { if (prop.isEmbedded(EmbeddedLevel.SCALAR)) { - return new EmbeddedReader(prop.getTargetType(), this); + return new FixedEmbeddedReader(prop.getTargetType(), this); } if (prop.isReference(TargetLevel.ENTITY)) { return new ReferenceReader(prop, this); @@ -84,7 +83,7 @@ private Reader createPropReader(ImmutableProp prop) { private Reader createTypeReader(ImmutableType immutableType) { if (immutableType.isEmbeddable()) { - return new EmbeddedReader(immutableType, this); + return new FixedEmbeddedReader(immutableType, this); } if (immutableType instanceof AssociationType) { return new AssociationReader((AssociationType) immutableType, this); @@ -108,7 +107,7 @@ private Reader createTypeReader(ImmutableType immutableType) { private Reader scalarReader(ImmutableProp prop) { ImmutableType immutableType = prop.getTargetType(); if (immutableType != null && immutableType.isEmbeddable()) { - return new EmbeddedReader(immutableType, this); + return new FixedEmbeddedReader(immutableType, this); } ScalarProvider scalarProvider = sqlClient.getScalarProvider(prop); if (scalarProvider != null) { @@ -158,7 +157,7 @@ private Reader scalarReader(Class type) { } ImmutableType immutableType = ImmutableType.tryGet(type); if (immutableType != null && immutableType.isEmbeddable()) { - return new EmbeddedReader(immutableType, this); + return new FixedEmbeddedReader(immutableType, this); } Reader reader = baseReader(type); if (reader == null) { @@ -620,6 +619,8 @@ public T read(ResultSet rs, Context ctx) throws SQLException { sqlValue + "\" to the jvm type \"" + scalarProvider.getScalarType() + + "\" by scalar provider \"" + + scalarProvider + "\"", ex ); @@ -673,7 +674,7 @@ private static class AssociationReader implements Reader> { } } - private static class EmbeddedReader implements Reader { + private static class FixedEmbeddedReader implements Reader { private static final ImmutableProp[] EMPTY_PROPS = new ImmutableProp[0]; @@ -685,12 +686,12 @@ private static class EmbeddedReader implements Reader { private Reader[] readers; - EmbeddedReader(ImmutableType targetType, ReaderManager readerManager) { + FixedEmbeddedReader(ImmutableType targetType, ReaderManager readerManager) { this.targetType = targetType; Map> map = new LinkedHashMap<>(); for (ImmutableProp childProp : targetType.getProps().values()) { if (childProp.isEmbedded(EmbeddedLevel.SCALAR)) { - map.put(childProp, new EmbeddedReader(childProp.getTargetType(), readerManager)); + map.put(childProp, new FixedEmbeddedReader(childProp.getTargetType(), readerManager)); } else if (!childProp.isFormula()) { map.put(childProp, readerManager.scalarReader(childProp)); } @@ -714,8 +715,8 @@ public Object read(ResultSet rs, Context ctx) throws SQLException { } catch (Throwable ex) { return DraftConsumerUncheckedException.rethrow(ex); } - Object embeddable = ctx.resolve(spi); - return EmbeddableObjects.isCompleted(embeddable) ? embeddable : null; + Object embedded = ctx.resolve(spi); + return EmbeddableObjects.isCompleted(embedded) ? embedded : null; } } diff --git a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/SqlBuilder.java b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/SqlBuilder.java index 5361b23235..00e14f0f50 100644 --- a/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/SqlBuilder.java +++ b/project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/runtime/SqlBuilder.java @@ -8,7 +8,6 @@ import org.babyfish.jimmer.sql.JoinType; import org.babyfish.jimmer.sql.ast.impl.AstContext; import org.babyfish.jimmer.sql.ast.impl.TupleImplementor; -import org.babyfish.jimmer.sql.ast.impl.util.EmbeddableObjects; import org.babyfish.jimmer.sql.ast.tuple.*; import org.babyfish.jimmer.sql.meta.ColumnDefinition; import org.babyfish.jimmer.sql.meta.SingleColumn; @@ -237,42 +236,6 @@ public SqlBuilder definition(ColumnDefinition definition) { return this; } - public SqlBuilder assignment(ImmutableProp prop, Object value) { - ColumnDefinition definition = prop.getStorage(getAstContext().getSqlClient().getMetadataStrategy()); - preAppend(); - if (definition instanceof SingleColumn) { - builder.append(((SingleColumn)definition).getName()).append(" = "); - if (value != null) { - variable(value); - } else { - nullVariable(prop.getReturnClass()); - } - } else { - ImmutableType type; - if (prop.isEmbedded(EmbeddedLevel.SCALAR)) { - type = prop.getTargetType(); - } else { - type = prop.getTargetType().getIdProp().getTargetType(); - } - List> subTypes = EmbeddableObjects.expandTypes(type); - Object[] subValues = EmbeddableObjects.expand(type, value); - int size = definition.size(); - for (int i = 0; i < size; i++) { - if (i != 0) { - builder.append(", "); - } - builder.append(definition.name(i)).append(" = "); - Object subValue = subValues[i]; - if (subValue != null) { - variable(subValue); - } else { - nullSingeVariable(subTypes.get(i)); - } - } - } - return this; - } - public SqlBuilder sql(String sql) { preAppend(); builder.append(sql); @@ -446,7 +409,7 @@ public SqlBuilder variable(Object value) { private SqlBuilder nonTupleVariable(Object value) { if (value instanceof DbLiteral) { preAppend(); - ((DbLiteral)value).render(builder); + ((DbLiteral)value).render(builder, getAstContext().getSqlClient()); variables.add(value); if (variablePositions != null) { variablePositions.add(builder.length()); diff --git a/project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/embedded/EmbeddedMutationTest.java b/project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/embedded/EmbeddedMutationTest.java index dca9bf4da1..db9a45fdcf 100644 --- a/project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/embedded/EmbeddedMutationTest.java +++ b/project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/embedded/EmbeddedMutationTest.java @@ -3,14 +3,18 @@ import org.babyfish.jimmer.sql.ast.mutation.SaveMode; import org.babyfish.jimmer.sql.common.AbstractMutationTest; import org.babyfish.jimmer.sql.model.Objects; +import org.babyfish.jimmer.sql.model.embedded.Machine; import org.babyfish.jimmer.sql.model.embedded.Transform; import org.babyfish.jimmer.sql.runtime.DbLiteral; import org.junit.jupiter.api.Test; +import java.util.LinkedHashMap; +import java.util.Map; + public class EmbeddedMutationTest extends AbstractMutationTest { @Test - public void test() { + public void testNestedEmbedded() { Transform transform = Objects.createTransform(draft -> { draft.setId(3L); draft.applySource(source -> { diff --git a/project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/json/H2SaveTest.java b/project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/json/H2SaveTest.java index c33da0bb83..3882dae697 100644 --- a/project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/json/H2SaveTest.java +++ b/project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/json/H2SaveTest.java @@ -4,37 +4,42 @@ import org.babyfish.jimmer.sql.ast.tuple.Tuple2; import org.babyfish.jimmer.sql.common.AbstractMutationTest; import org.babyfish.jimmer.sql.dialect.H2Dialect; +import org.babyfish.jimmer.sql.model.Objects; +import org.babyfish.jimmer.sql.model.embedded.Machine; import org.babyfish.jimmer.sql.model.json.Medicine; import org.babyfish.jimmer.sql.model.json.MedicineDraft; +import org.babyfish.jimmer.sql.model.json.MedicineProps; import org.babyfish.jimmer.sql.model.json.MedicineTable; import org.babyfish.jimmer.sql.runtime.DbLiteral; import org.junit.jupiter.api.Test; import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; public class H2SaveTest extends AbstractMutationTest { @Test public void testDML() { MedicineTable table = MedicineTable.$; + List tags = Arrays.asList( + new Medicine.Tag("Tag-1", "Description-1"), + new Medicine.Tag("Tag-2", "Description-2") + ); executeAndExpectRowCount( getSqlClient(it -> it.setDialect(new H2Dialect())) .createUpdate(table) - .set( - table.tags(), - Arrays.asList( - new Medicine.Tag("Tag-1", "Description-1"), - new Medicine.Tag("Tag-2", "Description-2") - ) - ) + .set(table.tags(), tags) .where(table.id().eq(1L)), ctx -> { ctx.statement(it -> { it.sql("update MEDICINE tb_1_ set TAGS = ? format json where tb_1_.ID = ?"); it.variables( - new DbLiteral.JsonWithSuffix( + new DbLiteral.DbValue( + MedicineProps.TAGS.unwrap(), "[{\"name\":\"Tag-1\",\"description\":\"Description-1\"},{\"name\":\"Tag-2\",\"description\":\"Description-2\"}]", - "format json" + true ), 1L ); @@ -45,7 +50,56 @@ public void testDML() { } @Test - public void testSaveCommand() { + public void testInsert() { + connectAndExpect(con -> { + int affectedCount = getSqlClient(it -> it.setDialect(new H2Dialect())) + .getEntities() + .saveCommand( + MedicineDraft.$.produce(draft -> { + draft.setId(10L); + draft.setTags( + Arrays.asList( + new Medicine.Tag("Tag-1", "Description-1"), + new Medicine.Tag("Tag-2", "Description-2") + ) + ); + }) + ).setMode(SaveMode.INSERT_ONLY) + .execute(con) + .getTotalAffectedRowCount(); + Medicine medicine = getSqlClient().getEntities().forConnection(con).findById( + Medicine.class, + 10L + ); + return new Tuple2<>(affectedCount, medicine); + }, ctx -> { + ctx.statement(it -> { + it.sql("insert into MEDICINE(ID, TAGS) values(?, ? format json)"); + }); + ctx.statement(it -> { + it.sql( + "select tb_1_.ID, tb_1_.TAGS " + + "from MEDICINE tb_1_ " + + "where tb_1_.ID = ?" + ); + }); + ctx.value( + "Tuple2(" + + "--->_1=1, " + + "--->_2={" + + "--->--->\"id\":10," + + "--->--->\"tags\":[" + + "--->--->--->{\"name\":\"Tag-1\",\"description\":\"Description-1\"}," + + "--->--->--->{\"name\":\"Tag-2\",\"description\":\"Description-2\"}" + + "--->--->]" + + "--->}" + + ")" + ); + }); + } + + @Test + public void testUpdate() { connectAndExpect(con -> { int affectedCount = getSqlClient(it -> it.setDialect(new H2Dialect())) .getEntities() @@ -92,4 +146,155 @@ public void testSaveCommand() { ); }); } + + @Test + public void testInsertEmbeddedJson() { + Map factoryMap = new LinkedHashMap<>(); + factoryMap.put("F-A", "Factory-A"); + factoryMap.put("F-B", "Factory-B"); + Map patentMap = new LinkedHashMap<>(); + patentMap.put("P-A", "PATENT-A"); + patentMap.put("P-B", "PATENT-B"); + Machine machine = Objects.createMachine(draft -> { + draft.setId(10L); + draft.setCpuFrequency(2); + draft.setMemorySize(16); + draft.setDiskSize(512); + draft.applyLocation(location -> { + location.setHost("localhost"); + location.setPort(9090); + }); + draft.applyDetail(detail -> { + detail.setFactories(factoryMap); + detail.setPatents(patentMap); + }); + }); + connectAndExpect( + con -> { + int affectRowCount = getSqlClient() + .getEntities() + .forConnection(con) + .saveCommand(machine) + .setMode(SaveMode.INSERT_ONLY) + .execute() + .getTotalAffectedRowCount(); + Machine medicine = getSqlClient().getEntities().forConnection(con).findById( + Machine.class, + 10L + ); + return new Tuple2<>(affectRowCount, medicine); + }, + ctx -> { + ctx.statement(it -> { + it.sql( + "insert into MACHINE(" + + "ID, " + + "HOST, PORT, " + + "CPU_FREQUENCY, MEMORY_SIZE, DISK_SIZE, " + + "factory_map, patent_map" + + ") values(?, ?, ?, ?, ?, ?, ? format json, ? format json)" + ); + }); + ctx.statement(it -> { + it.sql( + "select tb_1_.ID, " + + "tb_1_.HOST, tb_1_.PORT, tb_1_.SECONDARY_HOST, tb_1_.SECONDARY_PORT, " + + "tb_1_.CPU_FREQUENCY, tb_1_.MEMORY_SIZE, tb_1_.DISK_SIZE, " + + "tb_1_.factory_map, tb_1_.patent_map " + + "from MACHINE tb_1_ where tb_1_.ID = ?" + ); + }); + ctx.value( + "Tuple2(" + + "--->_1=1, " + + "--->_2={" + + "--->--->\"id\":10," + + "--->--->\"location\":{\"host\":\"localhost\",\"port\":9090}," + + "--->--->\"secondaryLocation\":null," + + "--->--->\"cpuFrequency\":2," + + "--->--->\"memorySize\":16," + + "--->--->\"diskSize\":512," + + "--->--->\"detail\":{" + + "--->--->--->\"factories\":{\"F-A\":\"Factory-A\",\"F-B\":\"Factory-B\"}," + + "--->--->--->\"patents\":{\"P-A\":\"PATENT-A\",\"P-B\":\"PATENT-B\"}" + + "--->--->}" + + "--->}" + + ")" + ); + } + ); + } + + @Test + public void testUpdateEmbeddedJson() { + Map patentMap = new LinkedHashMap<>(); + patentMap.put("P-A", "Patent-A"); + patentMap.put("P-B", "Patent-B"); + Machine machine = Objects.createMachine(draft -> { + draft.setId(1L); + draft.setCpuFrequency(4); + draft.setMemorySize(16); + draft.setDiskSize(512); + draft.applyLocation(location -> { + location.setHost("localhost"); + location.setPort(9090); + }); + draft.applyDetail(detail -> { + detail.setPatents(patentMap); + }); + }); + connectAndExpect( + con -> { + int affectRowCount = getSqlClient() + .getEntities() + .forConnection(con) + .saveCommand(machine) + .setMode(SaveMode.UPDATE_ONLY) + .execute() + .getTotalAffectedRowCount(); + Machine medicine = getSqlClient().getEntities().forConnection(con).findById( + Machine.class, + 1L + ); + return new Tuple2<>(affectRowCount, medicine); + }, + ctx -> { + ctx.statement(it -> { + it.sql( + "update MACHINE set " + + "HOST = ?, PORT = ?, " + + "CPU_FREQUENCY = ?, MEMORY_SIZE = ?, DISK_SIZE = ?, " + + "patent_map = ? format json " + + "where ID = ?" + ); + }); + ctx.statement(it -> { + it.sql( + "select tb_1_.ID, " + + "tb_1_.HOST, tb_1_.PORT, tb_1_.SECONDARY_HOST, tb_1_.SECONDARY_PORT, " + + "tb_1_.CPU_FREQUENCY, tb_1_.MEMORY_SIZE, tb_1_.DISK_SIZE, " + + "tb_1_.factory_map, tb_1_.patent_map " + + "from MACHINE tb_1_ where tb_1_.ID = ?" + ); + }); + ctx.value( + "Tuple2(" + + "--->_1=1, " + + "--->_2={" + + "--->--->\"id\":1," + + "--->--->\"location\":{\"host\":\"localhost\",\"port\":9090}," + + "--->--->\"secondaryLocation\":null," + + "--->--->\"cpuFrequency\":4," + + "--->--->\"memorySize\":16," + + "--->--->\"diskSize\":512," + + "--->--->\"detail\":{" + + "--->--->--->\"factories\":{\"f-1\":\"factory-1\",\"f-2\":\"factory-2\"}," + + "--->--->--->\"patents\":{\"P-A\":\"Patent-A\",\"P-B\":\"Patent-B\"}" + + "--->--->}" + + "--->}" + + ")" + ); + } + ); + } } diff --git a/project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/util/IdentityMapTest.java b/project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/util/IdentityMapTest.java index ac52da2c9a..3e7337043b 100644 --- a/project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/util/IdentityMapTest.java +++ b/project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/util/IdentityMapTest.java @@ -1,6 +1,6 @@ package org.babyfish.jimmer.sql.util; -import org.babyfish.jimmer.sql.ast.impl.util.IdentityMap; +import org.babyfish.jimmer.sql.ast.impl.mutation.IdentityMap; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test;