Skip to content

Commit 7d18bea

Browse files
committed
1 parent ee1915f commit 7d18bea

File tree

5 files changed

+109
-65
lines changed

5 files changed

+109
-65
lines changed

project/jimmer-sql-kotlin/src/test/kotlin/org/babyfish/jimmer/sql/kt/mutation/ModifiedFetcherTest.kt

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -92,23 +92,17 @@ class ModifiedFetcherTest : AbstractMutationTest() {
9292
"""merge into BOOK tb_1_
9393
|using(values(?, ?, ?, ?)) tb_2_(NAME, EDITION, PRICE, STORE_ID)
9494
|--->on tb_1_.NAME = tb_2_.NAME and tb_1_.EDITION = tb_2_.EDITION
95-
|when matched then
96-
|--->update set /* fake update to return all ids */ EDITION = tb_2_.EDITION
9795
|when not matched then
9896
|--->insert(NAME, EDITION, PRICE, STORE_ID)
9997
|--->values(tb_2_.NAME, tb_2_.EDITION, tb_2_.PRICE, tb_2_.STORE_ID)""".trimMargin()
10098
)
10199
}
102-
// 1. `INSERT_IF_ABSENT` will be translated to
103-
// `UPSERT + UpsertMask` when fetcher is specified
104-
// 2, `UpsertMask` or `SaveRule` may cause
105-
// jimmer cannot do non-query optimization
106-
// even if the shape of modified object is enough
107100
statement {
101+
queryReason(QueryReason.FETCHER)
108102
sql(
109103
"""select tb_1_.ID, tb_1_.NAME, tb_1_.EDITION, tb_1_.PRICE
110104
|from BOOK tb_1_
111-
|where tb_1_.ID = ?""".trimMargin()
105+
|where (tb_1_.NAME, tb_1_.EDITION) = (?, ?)""".trimMargin()
112106
)
113107
}
114108
value(
@@ -149,29 +143,22 @@ class ModifiedFetcherTest : AbstractMutationTest() {
149143
"""merge into BOOK tb_1_
150144
|using(values(?, ?, ?, ?)) tb_2_(NAME, EDITION, PRICE, STORE_ID)
151145
|--->on tb_1_.NAME = tb_2_.NAME and tb_1_.EDITION = tb_2_.EDITION
152-
|when matched then
153-
|--->update set /* fake update to return all ids */ EDITION = tb_2_.EDITION
154146
|when not matched then
155147
|--->insert(NAME, EDITION, PRICE, STORE_ID)
156148
|--->values(tb_2_.NAME, tb_2_.EDITION, tb_2_.PRICE, tb_2_.STORE_ID)""".trimMargin()
157149
)
158150
}
159-
// 1. `INSERT_IF_ABSENT` will be translated to
160-
// `UPSERT + UpsertMask` when fetcher is specified
161-
// 2, `UpsertMask` or `SaveRule` may cause
162-
// jimmer cannot do non-query optimization
163-
// even if the shape of modified object is enough
164151
statement {
165152
sql(
166153
"""select tb_1_.ID, tb_1_.NAME, tb_1_.EDITION, tb_1_.PRICE
167154
|from BOOK tb_1_
168-
|where tb_1_.ID = any(?)""".trimMargin()
155+
|where (tb_1_.NAME, tb_1_.EDITION) = (?, ?)""".trimMargin()
169156
)
170157
}
171158
value(
172159
"""[
173160
|{"id":12,"name":"GraphQL in Action","edition":3,"price":80.00},
174-
|{"id":100,"name":"GraphQL in Action","edition":4,"price":78.90}
161+
|{"id":100,"name":"GraphQL in Action","edition":4,"price":78.9}
175162
|]""".trimMargin()
176163
)
177164
}

project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/EntitiesImpl.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.babyfish.jimmer.sql.ast.impl.table.FetcherSelectionImpl;
2222
import org.babyfish.jimmer.sql.ast.mutation.BatchEntitySaveCommand;
2323
import org.babyfish.jimmer.sql.ast.mutation.DeleteCommand;
24+
import org.babyfish.jimmer.sql.ast.mutation.QueryReason;
2425
import org.babyfish.jimmer.sql.ast.mutation.SimpleEntitySaveCommand;
2526
import org.babyfish.jimmer.sql.ast.query.ConfigurableRootQuery;
2627
import org.babyfish.jimmer.sql.ast.query.Example;
@@ -119,6 +120,14 @@ public Entities forExporter() {
119120
return new EntitiesImpl(sqlClient, forUpdate, con, ExecutionPurpose.EXPORT);
120121
}
121122

123+
public Entities forSaveCommandFetch(QueryReason reason) {
124+
if (purpose instanceof ExecutionPurpose.Command &&
125+
((ExecutionPurpose.Command)purpose).getQueryReason() == reason) {
126+
return this;
127+
}
128+
return new EntitiesImpl(sqlClient, forUpdate, con, ExecutionPurpose.command(reason));
129+
}
130+
122131
@Override
123132
public <E> E findById(Class<E> type, Object id) {
124133
return sqlClient.getConnectionManager().execute(con, con -> findById(type, id, con));

project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/impl/mutation/Saver.java

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.babyfish.jimmer.runtime.Internal;
88
import org.babyfish.jimmer.sql.JSqlClient;
99
import org.babyfish.jimmer.sql.ast.impl.AbstractMutableStatementImpl;
10+
import org.babyfish.jimmer.sql.ast.impl.EntitiesImpl;
1011
import org.babyfish.jimmer.sql.ast.impl.table.FetcherSelectionImpl;
1112
import org.babyfish.jimmer.sql.ast.impl.table.StatementContext;
1213
import org.babyfish.jimmer.sql.ast.impl.table.TableImplementor;
@@ -41,9 +42,10 @@ public Saver(
4142
// `INSERT_IF_ABSENT` must be changed to
4243
// `UPSERT` when fetcher is specified to
4344
// get the ids so that fetch can work
44-
fetcher != null ?
45-
options.withQueryable(type) :
46-
options,
45+
// fetcher != null ?
46+
// options.withQueryable(type) :
47+
// options,
48+
options,
4749
con,
4850
type,
4951
fetcher
@@ -250,45 +252,83 @@ private void fetch(List<DraftSpi> drafts) {
250252
DraftSpi[] arr = new DraftSpi[drafts.size()];
251253
int index = 0;
252254
List<Object> unmatchedIds = new ArrayList<>();
255+
List<DraftSpi> nonIdObjects = new ArrayList<DraftSpi>();
253256
PropId idPropId = fetcher.getImmutableType().getIdProp().getId();
254257
ShapeMatchContext shapeMatchContext = new ShapeMatchContext();
255258
for (DraftSpi draft : drafts) {
256-
if (!isShapeMatched(draft, fetcher, shapeMatchContext)) {
259+
if (!draft.__isLoaded(idPropId)) {
260+
nonIdObjects.add(draft);
261+
} else if (!isShapeMatched(draft, fetcher, shapeMatchContext)) {
257262
arr[index] = draft;
258263
unmatchedIds.add(draft.__get(idPropId));
259264
}
260265
++index;
261266
}
262-
if (unmatchedIds.isEmpty()) {
263-
return;
267+
if (!unmatchedIds.isEmpty()) {
268+
JSqlClient sqlClient = ctx.options.getSqlClient().caches(CacheDisableConfig::disableAll);
269+
Map<Object, Object> map = ((EntitiesImpl) sqlClient.getEntities())
270+
.forSaveCommandFetch(QueryReason.FETCHER)
271+
.forConnection(ctx.con)
272+
.findMapByIds((Fetcher<Object>) fetcher, unmatchedIds);
273+
index = 0;
274+
ListIterator<DraftSpi> itr = drafts.listIterator();
275+
while (itr.hasNext()) {
276+
DraftSpi draft = itr.next();
277+
if (arr[index] != null) {
278+
Object fetched = map.get(draft.__get(idPropId));
279+
DraftSpi replacedDraft = replaceDraft(draft, fetched);
280+
if (replacedDraft != null) {
281+
itr.set(replacedDraft);
282+
}
283+
}
284+
++index;
285+
}
264286
}
265-
JSqlClient sqlClient = ctx.options.getSqlClient().caches(CacheDisableConfig::disableAll);
266-
Map<Object, Object> map = sqlClient.getEntities().forConnection(ctx.con).findMapByIds((Fetcher<Object>) fetcher, unmatchedIds);
267-
index = 0;
268-
ListIterator<DraftSpi> itr = drafts.listIterator();
269-
while (itr.hasNext()) {
270-
DraftSpi draft = itr.next();
271-
if (arr[index] != null) {
272-
Object fetched = map.get(draft.__get(idPropId));
273-
if (fetched instanceof DraftSpi) {
274-
itr.set((DraftSpi) fetched);
275-
} else if (fetched instanceof ImmutableSpi) {
276-
ImmutableSpi spi = (ImmutableSpi) fetched;
277-
for (ImmutableProp prop : draft.__type().getProps().values()) {
278-
PropId propId = prop.getId();
279-
if (spi.__isLoaded(propId)) {
280-
draft.__set(propId, spi.__get(propId));
281-
if (!spi.__isVisible(propId)) {
282-
draft.__show(propId, false);
283-
}
284-
} else {
285-
draft.__unload(propId);
286-
}
287+
if (!nonIdObjects.isEmpty()) {
288+
KeyMatcher keyMatcher = ctx.options.getKeyMatcher(fetcher.getImmutableType());
289+
Map<KeyMatcher.Group, Map<Object, ImmutableSpi>> map = Rows.findMapByKeys(
290+
ctx,
291+
QueryReason.FETCHER,
292+
(Fetcher<ImmutableSpi>) fetcher,
293+
nonIdObjects
294+
);
295+
ListIterator<DraftSpi> itr = drafts.listIterator();
296+
while (itr.hasNext()) {
297+
DraftSpi draft = itr.next();
298+
if (draft.__isLoaded(idPropId)) {
299+
continue;
300+
}
301+
KeyMatcher.Group group = keyMatcher.match(draft);
302+
Map<Object, ImmutableSpi> subMap = map.get(group);
303+
Object key = Keys.keyOf(draft, group.getProps());
304+
ImmutableSpi fetched = subMap.get(key);
305+
DraftSpi replacedDraft = replaceDraft(draft, fetched);
306+
if (replacedDraft != null) {
307+
itr.set(replacedDraft);
308+
}
309+
}
310+
}
311+
}
312+
313+
private static DraftSpi replaceDraft(DraftSpi draft, Object fetched) {
314+
if (fetched instanceof DraftSpi) {
315+
return (DraftSpi) fetched;
316+
}
317+
if (fetched instanceof ImmutableSpi) {
318+
ImmutableSpi spi = (ImmutableSpi) fetched;
319+
for (ImmutableProp prop : draft.__type().getProps().values()) {
320+
PropId propId = prop.getId();
321+
if (spi.__isLoaded(propId)) {
322+
if (!prop.isView()) {
323+
draft.__set(propId, spi.__get(propId));
287324
}
325+
draft.__show(propId, spi.__isVisible(propId));
326+
} else {
327+
draft.__unload(propId);
288328
}
289329
}
290-
++index;
291330
}
331+
return null;
292332
}
293333

294334
private boolean isVisitable(ImmutableProp prop) {

project/jimmer-sql/src/main/java/org/babyfish/jimmer/sql/ast/mutation/QueryReason.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,5 +376,10 @@ public enum QueryReason {
376376
*
377377
* @see org.babyfish.jimmer.sql.runtime.ExceptionTranslator
378378
*/
379-
INVESTIGATE_CONSTRAINT_VIOLATION_ERROR
379+
INVESTIGATE_CONSTRAINT_VIOLATION_ERROR,
380+
381+
/**
382+
* The fetcher or viewType of save command is specified
383+
*/
384+
FETCHER
380385
}

project/jimmer-sql/src/test/java/org/babyfish/jimmer/sql/mutation/ModifiedFetcherTest.java

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.babyfish.jimmer.sql.mutation;
22

33
import org.babyfish.jimmer.sql.ast.mutation.BatchSaveResult;
4+
import org.babyfish.jimmer.sql.ast.mutation.QueryReason;
45
import org.babyfish.jimmer.sql.ast.mutation.SaveMode;
56
import org.babyfish.jimmer.sql.common.AbstractMutationTest;
67
import org.babyfish.jimmer.sql.common.Constants;
@@ -274,13 +275,12 @@ public void testInsertIgnore() {
274275
"merge into DEPARTMENT tb_1_ " +
275276
"using(values(?, ?)) tb_2_(NAME, DELETED_MILLIS) " +
276277
"--->on tb_1_.NAME = tb_2_.NAME and tb_1_.DELETED_MILLIS = tb_2_.DELETED_MILLIS " +
277-
"when matched then update set " +
278-
"--->/* fake update to return all ids */ DELETED_MILLIS = tb_2_.DELETED_MILLIS " +
279278
"when not matched then " +
280279
"--->insert(NAME, DELETED_MILLIS) values(tb_2_.NAME, tb_2_.DELETED_MILLIS)"
281280
);
282281
});
283282
ctx.statement(it -> {
283+
it.queryReason(QueryReason.FETCHER);
284284
it.sql(
285285
"select tb_1_.ID, tb_1_.NAME " +
286286
"from DEPARTMENT tb_1_ " +
@@ -294,6 +294,21 @@ public void testInsertIgnore() {
294294
"where tb_1_.DEPARTMENT_ID = any(?) and tb_1_.DELETED_MILLIS = ?"
295295
);
296296
});
297+
ctx.statement(it -> {
298+
it.queryReason(QueryReason.FETCHER);
299+
it.sql(
300+
"select tb_1_.ID, tb_1_.NAME " +
301+
"from DEPARTMENT tb_1_ " +
302+
"where tb_1_.NAME = ? and tb_1_.DELETED_MILLIS = ?"
303+
);
304+
});
305+
ctx.statement(it -> {
306+
it.sql(
307+
"select tb_1_.ID, tb_1_.NAME, tb_1_.GENDER " +
308+
"from EMPLOYEE tb_1_ " +
309+
"where tb_1_.DEPARTMENT_ID = ? and tb_1_.DELETED_MILLIS = ?"
310+
);
311+
});
297312
ctx.value(
298313
"[" +
299314
"--->{" +
@@ -399,22 +414,16 @@ public void testIssue1000BySimple() {
399414
"merge into DEPARTMENT tb_1_ " +
400415
"using(values(?, ?)) tb_2_(NAME, DELETED_MILLIS) " +
401416
"--->on tb_1_.NAME = tb_2_.NAME and tb_1_.DELETED_MILLIS = tb_2_.DELETED_MILLIS " +
402-
"when matched then " +
403-
"--->update set /* fake update to return all ids */ DELETED_MILLIS = tb_2_.DELETED_MILLIS " +
404417
"when not matched then " +
405418
"--->insert(NAME, DELETED_MILLIS) values(tb_2_.NAME, tb_2_.DELETED_MILLIS)"
406419
);
407420
});
408-
// 1. `INSERT_IF_ABSENT` will be translated to
409-
// `UPSERT + UpsertMask` when fetcher is specified
410-
// 2, `UpsertMask` or `SaveRule` may cause
411-
// jimmer cannot do non-query optimization
412-
// even if the shape of modified object is enough
413421
ctx.statement(it -> {
422+
it.queryReason(QueryReason.FETCHER);
414423
it.sql(
415424
"select tb_1_.ID, tb_1_.NAME " +
416425
"from DEPARTMENT tb_1_ " +
417-
"where tb_1_.ID = ? and tb_1_.DELETED_MILLIS = ?"
426+
"where tb_1_.NAME = ? and tb_1_.DELETED_MILLIS = ?"
418427
);
419428
});
420429
ctx.value(
@@ -456,22 +465,16 @@ public void testIssue1000ByBatch() {
456465
"merge into DEPARTMENT tb_1_ " +
457466
"using(values(?, ?)) tb_2_(NAME, DELETED_MILLIS) " +
458467
"--->on tb_1_.NAME = tb_2_.NAME and tb_1_.DELETED_MILLIS = tb_2_.DELETED_MILLIS " +
459-
"when matched then " +
460-
"--->update set /* fake update to return all ids */ DELETED_MILLIS = tb_2_.DELETED_MILLIS " +
461468
"when not matched then " +
462469
"--->insert(NAME, DELETED_MILLIS) values(tb_2_.NAME, tb_2_.DELETED_MILLIS)"
463470
);
464471
});
465-
// 1. `INSERT_IF_ABSENT` will be translated to
466-
// `UPSERT + UpsertMask` when fetcher is specified
467-
// 2, `UpsertMask` or `SaveRule` may cause
468-
// jimmer cannot do non-query optimization
469-
// even if the shape of modified object is enough
470472
ctx.statement(it -> {
473+
it.queryReason(QueryReason.FETCHER);
471474
it.sql(
472475
"select tb_1_.ID, tb_1_.NAME " +
473476
"from DEPARTMENT tb_1_ " +
474-
"where tb_1_.ID = any(?) and tb_1_.DELETED_MILLIS = ?"
477+
"where tb_1_.NAME = ? and tb_1_.DELETED_MILLIS = ?"
475478
);
476479
});
477480
ctx.value(

0 commit comments

Comments
 (0)