Skip to content

Commit e9cacd6

Browse files
committed
Add the ability to update a field with a sub-query
1 parent 4afa320 commit e9cacd6

File tree

9 files changed

+190
-58
lines changed

9 files changed

+190
-58
lines changed

src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@
2626
import org.mybatis.dynamic.sql.SqlCriterion;
2727
import org.mybatis.dynamic.sql.SqlTable;
2828
import org.mybatis.dynamic.sql.VisitableCondition;
29+
import org.mybatis.dynamic.sql.select.SelectModel;
2930
import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider;
3031
import org.mybatis.dynamic.sql.util.ArithmeticConstantMapping;
3132
import org.mybatis.dynamic.sql.util.ArithmeticOperation;
33+
import org.mybatis.dynamic.sql.util.Buildable;
3234
import org.mybatis.dynamic.sql.util.ConstantMapping;
3335
import org.mybatis.dynamic.sql.util.NullMapping;
36+
import org.mybatis.dynamic.sql.util.SelectMapping;
3437
import org.mybatis.dynamic.sql.util.StringConstantMapping;
3538
import org.mybatis.dynamic.sql.util.UpdateMapping;
3639
import org.mybatis.dynamic.sql.util.ValueMapping;
@@ -118,6 +121,11 @@ public UpdateDSL<R> equalTo(Supplier<T> valueSupplier) {
118121
return UpdateDSL.this;
119122
}
120123

124+
public UpdateDSL<R> equalTo(Buildable<SelectModel> buildable) {
125+
columnsAndValues.add(SelectMapping.of(column, buildable));
126+
return UpdateDSL.this;
127+
}
128+
121129
public UpdateDSL<R> equalToWhenPresent(T value) {
122130
return equalToWhenPresent(() -> value);
123131
}

src/main/java/org/mybatis/dynamic/sql/update/render/SetPhraseVisitor.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,31 @@
1515
*/
1616
package org.mybatis.dynamic.sql.update.render;
1717

18+
import java.util.Objects;
1819
import java.util.concurrent.atomic.AtomicInteger;
1920
import java.util.function.Function;
2021

2122
import org.mybatis.dynamic.sql.SqlColumn;
2223
import org.mybatis.dynamic.sql.render.RenderingStrategy;
24+
import org.mybatis.dynamic.sql.select.render.SelectRenderer;
25+
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
2326
import org.mybatis.dynamic.sql.util.ArithmeticConstantMapping;
2427
import org.mybatis.dynamic.sql.util.ConstantMapping;
2528
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
2629
import org.mybatis.dynamic.sql.util.NullMapping;
30+
import org.mybatis.dynamic.sql.util.SelectMapping;
2731
import org.mybatis.dynamic.sql.util.StringConstantMapping;
2832
import org.mybatis.dynamic.sql.util.UpdateMappingVisitor;
2933
import org.mybatis.dynamic.sql.util.ValueMapping;
3034

3135
public class SetPhraseVisitor implements UpdateMappingVisitor<FragmentAndParameters> {
3236

33-
private AtomicInteger sequence = new AtomicInteger(1);
37+
private AtomicInteger sequence;
3438
private RenderingStrategy renderingStrategy;
3539

36-
public SetPhraseVisitor(RenderingStrategy renderingStrategy) {
37-
this.renderingStrategy = renderingStrategy;
40+
public SetPhraseVisitor(AtomicInteger sequence, RenderingStrategy renderingStrategy) {
41+
this.sequence = Objects.requireNonNull(sequence);
42+
this.renderingStrategy = Objects.requireNonNull(renderingStrategy);
3843
}
3944

4045
@Override
@@ -63,7 +68,7 @@ public FragmentAndParameters visit(StringConstantMapping mapping) {
6368

6469
@Override
6570
public <T> FragmentAndParameters visit(ValueMapping<T> mapping) {
66-
String mapKey = "up" + sequence.getAndIncrement(); //$NON-NLS-1$
71+
String mapKey = "p" + sequence.getAndIncrement(); //$NON-NLS-1$
6772

6873
String jdbcPlaceholder = mapping.mapColumn(toJdbcPlaceholder(mapKey));
6974
String setPhrase = mapping.mapColumn(SqlColumn::name)
@@ -88,6 +93,24 @@ public <S> FragmentAndParameters visit(ArithmeticConstantMapping<S> mapping) {
8893
.build();
8994
}
9095

96+
@Override
97+
public FragmentAndParameters visit(SelectMapping mapping) {
98+
SelectStatementProvider selectStatement = SelectRenderer.withSelectModel(mapping.selectModel())
99+
.withRenderingStrategy(renderingStrategy)
100+
.withSequence(sequence)
101+
.build()
102+
.render();
103+
104+
String fragment = mapping.mapColumn(SqlColumn::name)
105+
+ " = (" //$NON-NLS-1$
106+
+ selectStatement.getSelectStatement()
107+
+ ")"; //$NON-NLS-1$
108+
109+
return FragmentAndParameters.withFragment(fragment)
110+
.withParameters(selectStatement.getParameters())
111+
.build();
112+
}
113+
91114
private Function<SqlColumn<?>, String> toJdbcPlaceholder(String parameterName) {
92115
return column -> renderingStrategy
93116
.getFormattedJdbcPlaceholder(column, "parameters", parameterName); //$NON-NLS-1$

src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2016-2017 the original author or authors.
2+
* Copyright 2016-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,28 +16,32 @@
1616
package org.mybatis.dynamic.sql.update.render;
1717

1818
import java.util.Objects;
19+
import java.util.concurrent.atomic.AtomicInteger;
1920
import java.util.function.Function;
2021
import java.util.stream.Collectors;
2122

2223
import org.mybatis.dynamic.sql.render.RenderingStrategy;
24+
import org.mybatis.dynamic.sql.render.TableAliasCalculator;
2325
import org.mybatis.dynamic.sql.update.UpdateModel;
2426
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
2527
import org.mybatis.dynamic.sql.util.FragmentCollector;
2628
import org.mybatis.dynamic.sql.util.UpdateMapping;
2729
import org.mybatis.dynamic.sql.where.WhereModel;
2830
import org.mybatis.dynamic.sql.where.render.WhereClauseProvider;
31+
import org.mybatis.dynamic.sql.where.render.WhereRenderer;
2932

3033
public class UpdateRenderer {
3134
private UpdateModel updateModel;
3235
private RenderingStrategy renderingStrategy;
36+
private AtomicInteger sequence = new AtomicInteger(1);
3337

3438
private UpdateRenderer(Builder builder) {
3539
updateModel = Objects.requireNonNull(builder.updateModel);
3640
renderingStrategy = Objects.requireNonNull(builder.renderingStrategy);
3741
}
3842

3943
public UpdateStatementProvider render() {
40-
SetPhraseVisitor visitor = new SetPhraseVisitor(renderingStrategy);
44+
SetPhraseVisitor visitor = new SetPhraseVisitor(sequence, renderingStrategy);
4145

4246
FragmentCollector fc = updateModel.mapColumnValues(toFragmentAndParameters(visitor))
4347
.collect(FragmentCollector.collect());
@@ -48,14 +52,19 @@ public UpdateStatementProvider render() {
4852
.withWhereClause(updateModel.whereModel().map(this::renderWhereClause))
4953
.build();
5054
}
51-
55+
5256
private String calculateSetPhrase(FragmentCollector collector) {
5357
return collector.fragments()
5458
.collect(Collectors.joining(", ", "set ", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
5559
}
5660

5761
private WhereClauseProvider renderWhereClause(WhereModel whereModel) {
58-
return whereModel.render(renderingStrategy);
62+
return WhereRenderer.withWhereModel(whereModel)
63+
.withRenderingStrategy(renderingStrategy)
64+
.withSequence(sequence)
65+
.withTableAliasCalculator(TableAliasCalculator.empty())
66+
.build()
67+
.render();
5968
}
6069

6170
private Function<UpdateMapping, FragmentAndParameters> toFragmentAndParameters(SetPhraseVisitor visitor) {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Copyright 2016-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql.util;
17+
18+
import org.mybatis.dynamic.sql.SqlColumn;
19+
import org.mybatis.dynamic.sql.select.SelectModel;
20+
21+
public class SelectMapping extends AbstractColumnMapping implements UpdateMapping {
22+
23+
private SelectModel selectModel;
24+
25+
private SelectMapping(SqlColumn<?> column, Buildable<SelectModel> selectModelBuilder) {
26+
super(column);
27+
selectModel = selectModelBuilder.build();
28+
}
29+
30+
public SelectModel selectModel() {
31+
return selectModel;
32+
}
33+
34+
@Override
35+
public <R> R accept(UpdateMappingVisitor<R> visitor) {
36+
return visitor.visit(this);
37+
}
38+
39+
public static SelectMapping of(SqlColumn<?> column, Buildable<SelectModel> selectModelBuilder) {
40+
return new SelectMapping(column, selectModelBuilder);
41+
}
42+
}

src/main/java/org/mybatis/dynamic/sql/util/UpdateMappingVisitor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ public interface UpdateMappingVisitor<T> {
2525
<S> T visit(ValueMapping<S> mapping);
2626

2727
<S> T visit(ArithmeticConstantMapping<S> mapping);
28+
29+
T visit(SelectMapping mapping);
2830
}

src/site/markdown/docs/update.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Notice the `set` method. It is used to set the value of a database column. Ther
2424
5. `set(column).equalTo(Supplier<T> valueSupplier)` will set a value into a column. The value will be bound to the SQL statement as a prepared statement parameter
2525
6. `set(column).equalToWhenPresent(T value)` will set a value into a column if the value is non-null. The value of the property will be bound to the SQL statement as a prepared statement parameter. This is used to generate a "selective" update as defined in MyBatis Generator.
2626
7. `set(column).equalToWhenPresent(Supplier<T> valueSupplier)` will set a value into a column if the value is non-null. The value of the property will be bound to the SQL statement as a prepared statement parameter. This is used to generate a "selective" update as defined in MyBatis Generator.
27+
8. `set(column).equalTo(Buildable<SelectModel> selectModelBuilder)` will set the result of a sub-query into a column. The query should only have one column and the type of the returned column must be able to be converted by the database if it is not the same type. These constraints are NOT validated by the library.
2728

2829
You can also build an update statement without a where clause. This will update every row in a table.
2930
For example:

src/test/java/examples/animal/data/AnimalDataTest.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2016-2017 the original author or authors.
2+
* Copyright 2016-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -1456,4 +1456,31 @@ public void testInsertSelectWithoutColumnList() {
14561456
sqlSession.close();
14571457
}
14581458
}
1459+
1460+
@Test
1461+
public void testUpdateWithSelect() {
1462+
SqlSession sqlSession = sqlSessionFactory.openSession();
1463+
try {
1464+
AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class);
1465+
1466+
UpdateStatementProvider updateStatement = update(animalData)
1467+
.set(brainWeight).equalTo(select(avg(brainWeight)).from(animalData).where(brainWeight, isGreaterThan(22.0)))
1468+
.where(brainWeight, isLessThan(1.0))
1469+
.build()
1470+
.render(RenderingStrategy.MYBATIS3);
1471+
1472+
String expected = "update AnimalData "
1473+
+ "set brain_weight = (select avg(brain_weight) from AnimalData where brain_weight > #{parameters.p1,jdbcType=DOUBLE}) "
1474+
+ "where brain_weight < #{parameters.p2,jdbcType=DOUBLE}";
1475+
assertThat(updateStatement.getUpdateStatement()).isEqualTo(expected);
1476+
assertThat(updateStatement.getParameters().size()).isEqualTo(2);
1477+
assertThat(updateStatement.getParameters().get("p1")).isEqualTo(22.0);
1478+
assertThat(updateStatement.getParameters().get("p2")).isEqualTo(1.0);
1479+
1480+
int rows = mapper.update(updateStatement);
1481+
assertThat(rows).isEqualTo(20);
1482+
} finally {
1483+
sqlSession.close();
1484+
}
1485+
}
14591486
}

src/test/java/org/mybatis/dynamic/sql/mybatis3/UpdateStatementTest.java

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2016-2017 the original author or authors.
2+
* Copyright 2016-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,16 +46,16 @@ public void testUpdateParameter() {
4646
.build()
4747
.render(RenderingStrategy.MYBATIS3);
4848

49-
String expected = "update foo set firstName = #{parameters.up1,jdbcType=VARCHAR}, "
50-
+ "lastName = #{parameters.up2,jdbcType=VARCHAR}, "
49+
String expected = "update foo set firstName = #{parameters.p1,jdbcType=VARCHAR}, "
50+
+ "lastName = #{parameters.p2,jdbcType=VARCHAR}, "
5151
+ "occupation = null "
52-
+ "where id = #{parameters.p1,jdbcType=INTEGER}";
52+
+ "where id = #{parameters.p3,jdbcType=INTEGER}";
5353

5454
assertThat(updateStatement.getUpdateStatement()).isEqualTo(expected);
5555
assertThat(updateStatement.getParameters().size()).isEqualTo(3);
56-
assertThat(updateStatement.getParameters().get("up1")).isEqualTo("fred");
57-
assertThat(updateStatement.getParameters().get("up2")).isEqualTo("jones");
58-
assertThat(updateStatement.getParameters().get("p1")).isEqualTo(3);
56+
assertThat(updateStatement.getParameters().get("p1")).isEqualTo("fred");
57+
assertThat(updateStatement.getParameters().get("p2")).isEqualTo("jones");
58+
assertThat(updateStatement.getParameters().get("p3")).isEqualTo(3);
5959
}
6060

6161
@Test
@@ -70,16 +70,16 @@ public void testUpdateParameterStartWithNull() {
7070
.render(RenderingStrategy.MYBATIS3);
7171

7272
String expectedSetClause = "update foo set occupation = null, "
73-
+ "firstName = #{parameters.up1,jdbcType=VARCHAR}, "
74-
+ "lastName = #{parameters.up2,jdbcType=VARCHAR} "
75-
+ "where id = #{parameters.p1,jdbcType=INTEGER} "
76-
+ "and firstName = #{parameters.p2,jdbcType=VARCHAR}";
73+
+ "firstName = #{parameters.p1,jdbcType=VARCHAR}, "
74+
+ "lastName = #{parameters.p2,jdbcType=VARCHAR} "
75+
+ "where id = #{parameters.p3,jdbcType=INTEGER} "
76+
+ "and firstName = #{parameters.p4,jdbcType=VARCHAR}";
7777

7878
assertThat(updateStatement.getUpdateStatement()).isEqualTo(expectedSetClause);
7979
assertThat(updateStatement.getParameters().size()).isEqualTo(4);
80-
assertThat(updateStatement.getParameters().get("up1")).isEqualTo("fred");
81-
assertThat(updateStatement.getParameters().get("up2")).isEqualTo("jones");
82-
assertThat(updateStatement.getParameters().get("p1")).isEqualTo(3);
83-
assertThat(updateStatement.getParameters().get("p2")).isEqualTo("barney");
80+
assertThat(updateStatement.getParameters().get("p1")).isEqualTo("fred");
81+
assertThat(updateStatement.getParameters().get("p2")).isEqualTo("jones");
82+
assertThat(updateStatement.getParameters().get("p3")).isEqualTo(3);
83+
assertThat(updateStatement.getParameters().get("p4")).isEqualTo("barney");
8484
}
8585
}

0 commit comments

Comments
 (0)