View Javadoc
1   /*
2    *    Copyright 2016-2025 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    *       https://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.update.render;
17  
18  import java.util.Objects;
19  import java.util.Optional;
20  import java.util.stream.Collectors;
21  
22  import org.jspecify.annotations.Nullable;
23  import org.mybatis.dynamic.sql.common.OrderByModel;
24  import org.mybatis.dynamic.sql.common.OrderByRenderer;
25  import org.mybatis.dynamic.sql.render.ExplicitTableAliasCalculator;
26  import org.mybatis.dynamic.sql.render.RenderedParameterInfo;
27  import org.mybatis.dynamic.sql.render.RenderingContext;
28  import org.mybatis.dynamic.sql.render.RenderingStrategy;
29  import org.mybatis.dynamic.sql.render.TableAliasCalculator;
30  import org.mybatis.dynamic.sql.update.UpdateModel;
31  import org.mybatis.dynamic.sql.util.FragmentAndParameters;
32  import org.mybatis.dynamic.sql.util.FragmentCollector;
33  import org.mybatis.dynamic.sql.util.Validator;
34  import org.mybatis.dynamic.sql.where.EmbeddedWhereModel;
35  
36  public class UpdateRenderer {
37      private final UpdateModel updateModel;
38      private final RenderingContext renderingContext;
39      private final SetPhraseVisitor visitor;
40  
41      private UpdateRenderer(Builder builder) {
42          updateModel = Objects.requireNonNull(builder.updateModel);
43          TableAliasCalculator tableAliasCalculator = builder.updateModel.tableAlias()
44                  .map(a -> ExplicitTableAliasCalculator.of(updateModel.table(), a))
45                  .orElseGet(TableAliasCalculator::empty);
46          renderingContext = RenderingContext
47                  .withRenderingStrategy(Objects.requireNonNull(builder.renderingStrategy))
48                  .withTableAliasCalculator(tableAliasCalculator)
49                  .withStatementConfiguration(updateModel.statementConfiguration())
50                  .build();
51          visitor = new SetPhraseVisitor(renderingContext);
52      }
53  
54      public UpdateStatementProvider render() {
55          FragmentCollector fragmentCollector = new FragmentCollector();
56  
57          fragmentCollector.add(calculateUpdateStatementStart());
58          fragmentCollector.add(calculateSetPhrase());
59          calculateWhereClause().ifPresent(fragmentCollector::add);
60          calculateOrderByClause().ifPresent(fragmentCollector::add);
61          calculateLimitClause().ifPresent(fragmentCollector::add);
62  
63          return toUpdateStatementProvider(fragmentCollector);
64      }
65  
66      private UpdateStatementProvider toUpdateStatementProvider(FragmentCollector fragmentCollector) {
67          return DefaultUpdateStatementProvider
68                  .withUpdateStatement(fragmentCollector.collectFragments(Collectors.joining(" "))) //$NON-NLS-1$
69                  .withParameters(fragmentCollector.parameters())
70                  .build();
71      }
72  
73      private FragmentAndParameters calculateUpdateStatementStart() {
74          String aliasedTableName = renderingContext.aliasedTableName(updateModel.table());
75          return FragmentAndParameters.fromFragment("update " + aliasedTableName); //$NON-NLS-1$
76      }
77  
78      private FragmentAndParameters calculateSetPhrase() {
79          FragmentCollector fragmentCollector = updateModel.columnMappings()
80                  .map(m -> m.accept(visitor))
81                  .flatMap(Optional::stream)
82                  .collect(FragmentCollector.collect());
83  
84          Validator.assertFalse(fragmentCollector.isEmpty(), "ERROR.18"); //$NON-NLS-1$
85  
86          return fragmentCollector.toFragmentAndParameters(
87                          Collectors.joining(", ", "set ", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
88      }
89  
90      private Optional<FragmentAndParameters> calculateWhereClause() {
91          return updateModel.whereModel().flatMap(this::renderWhereClause);
92      }
93  
94      private Optional<FragmentAndParameters> renderWhereClause(EmbeddedWhereModel whereModel) {
95          return whereModel.render(renderingContext);
96      }
97  
98      private Optional<FragmentAndParameters> calculateLimitClause() {
99          return updateModel.limit().map(this::renderLimitClause);
100     }
101 
102     private FragmentAndParameters renderLimitClause(Long limit) {
103         RenderedParameterInfo parameterInfo = renderingContext.calculateLimitParameterInfo();
104 
105         return FragmentAndParameters.withFragment("limit " + parameterInfo.renderedPlaceHolder()) //$NON-NLS-1$
106                 .withParameter(parameterInfo.parameterMapKey(), limit)
107                 .build();
108     }
109 
110     private Optional<FragmentAndParameters> calculateOrderByClause() {
111         return updateModel.orderByModel().map(this::renderOrderByClause);
112     }
113 
114     private FragmentAndParameters renderOrderByClause(OrderByModel orderByModel) {
115         return new OrderByRenderer(renderingContext).render(orderByModel);
116     }
117 
118     public static Builder withUpdateModel(UpdateModel updateModel) {
119         return new Builder().withUpdateModel(updateModel);
120     }
121 
122     public static class Builder {
123         private @Nullable UpdateModel updateModel;
124         private @Nullable RenderingStrategy renderingStrategy;
125 
126         public Builder withUpdateModel(UpdateModel updateModel) {
127             this.updateModel = updateModel;
128             return this;
129         }
130 
131         public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) {
132             this.renderingStrategy = renderingStrategy;
133             return this;
134         }
135 
136         public UpdateRenderer build() {
137             return new UpdateRenderer(this);
138         }
139     }
140 }