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.render;
17  
18  import static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore;
19  
20  import java.util.Objects;
21  import java.util.concurrent.atomic.AtomicInteger;
22  
23  import org.jspecify.annotations.Nullable;
24  import org.mybatis.dynamic.sql.BindableColumn;
25  import org.mybatis.dynamic.sql.SqlColumn;
26  import org.mybatis.dynamic.sql.SqlTable;
27  import org.mybatis.dynamic.sql.configuration.StatementConfiguration;
28  
29  /**
30   * This class encapsulates all the supporting items related to rendering, and contains many utility methods
31   * used during the rendering process.
32   *
33   * @since 1.5.1
34   * @author Jeff Butler
35   */
36  public class RenderingContext {
37  
38      private final RenderingStrategy renderingStrategy;
39      private final AtomicInteger sequence;
40      private final TableAliasCalculator tableAliasCalculator;
41      private final @Nullable String configuredParameterName;
42      private final String calculatedParameterName;
43      private final StatementConfiguration statementConfiguration;
44  
45      private RenderingContext(Builder builder) {
46          renderingStrategy = Objects.requireNonNull(builder.renderingStrategy);
47          configuredParameterName = builder.parameterName;
48          tableAliasCalculator = Objects.requireNonNull(builder.tableAliasCalculator);
49          statementConfiguration = Objects.requireNonNull(builder.statementConfiguration);
50  
51          // reasonable defaults
52          sequence = builder.sequence == null ? new AtomicInteger(1) : builder.sequence;
53          calculatedParameterName = builder.parameterName == null ? RenderingStrategy.DEFAULT_PARAMETER_PREFIX
54                  : builder.parameterName + "." + RenderingStrategy.DEFAULT_PARAMETER_PREFIX;  //$NON-NLS-1$
55      }
56  
57      private String nextMapKey() {
58          return renderingStrategy.formatParameterMapKey(sequence);
59      }
60  
61      private <T> String renderedPlaceHolder(String mapKey, BindableColumn<T> column) {
62          return  column.renderingStrategy().orElse(renderingStrategy)
63                  .getFormattedJdbcPlaceholder(column, calculatedParameterName, mapKey);
64      }
65  
66      public RenderedParameterInfo calculateFetchFirstRowsParameterInfo() {
67          String mapKey = renderingStrategy.formatParameterMapKeyForFetchFirstRows(sequence);
68          return new RenderedParameterInfo(mapKey,
69                  renderingStrategy.getFormattedJdbcPlaceholderForPagingParameters(calculatedParameterName, mapKey));
70      }
71  
72      public RenderedParameterInfo calculateLimitParameterInfo() {
73          String mapKey = renderingStrategy.formatParameterMapKeyForLimit(sequence);
74          return new RenderedParameterInfo(mapKey,
75                  renderingStrategy.getFormattedJdbcPlaceholderForPagingParameters(calculatedParameterName, mapKey));
76      }
77  
78      public RenderedParameterInfo calculateOffsetParameterInfo() {
79          String mapKey = renderingStrategy.formatParameterMapKeyForOffset(sequence);
80          return new RenderedParameterInfo(mapKey,
81                  renderingStrategy.getFormattedJdbcPlaceholderForPagingParameters(calculatedParameterName, mapKey));
82      }
83  
84      public <T> RenderedParameterInfo calculateParameterInfo(BindableColumn<T> column) {
85          String mapKey = nextMapKey();
86          return new RenderedParameterInfo(mapKey, renderedPlaceHolder(mapKey, column));
87      }
88  
89      public <T> String aliasedColumnName(SqlColumn<T> column) {
90          return tableAliasCalculator.aliasForColumn(column.table())
91                  .map(alias -> aliasedColumnName(column, alias))
92                  .orElseGet(column::name);
93      }
94  
95      public <T> String aliasedColumnName(SqlColumn<T> column, String explicitAlias) {
96          return explicitAlias + "." + column.name();  //$NON-NLS-1$
97      }
98  
99      public String aliasedTableName(SqlTable table) {
100         return tableAliasCalculator.aliasForTable(table)
101                 .map(a -> table.tableName() + spaceBefore(a))
102                 .orElseGet(table::tableName);
103     }
104 
105     public boolean isNonRenderingClauseAllowed() {
106         return statementConfiguration.isNonRenderingWhereClauseAllowed();
107     }
108 
109     /**
110      * Create a new rendering context based on this, with the table alias calculator modified to include the
111      * specified child table alias calculator. This is used by the query expression renderer when the alias calculator
112      * may change during rendering.
113      *
114      * @param childTableAliasCalculator the child table alias calculator
115      * @return a new rendering context whose table alias calculator is composed of the former calculator as parent, and
116      *     the new child calculator
117      */
118     public RenderingContext withChildTableAliasCalculator(TableAliasCalculator childTableAliasCalculator) {
119         TableAliasCalculator tac = new TableAliasCalculatorWithParent.Builder()
120                 .withParent(tableAliasCalculator)
121                 .withChild(childTableAliasCalculator)
122                 .build();
123 
124         return new Builder()
125                 .withRenderingStrategy(this.renderingStrategy)
126                 .withSequence(this.sequence)
127                 .withParameterName(this.configuredParameterName)
128                 .withTableAliasCalculator(tac)
129                 .withStatementConfiguration(statementConfiguration)
130                 .build();
131     }
132 
133     public static Builder withRenderingStrategy(RenderingStrategy renderingStrategy) {
134         return new Builder().withRenderingStrategy(renderingStrategy);
135     }
136 
137     public static class Builder {
138         private @Nullable RenderingStrategy renderingStrategy;
139         private @Nullable AtomicInteger sequence;
140         private @Nullable TableAliasCalculator tableAliasCalculator = TableAliasCalculator.empty();
141         private @Nullable String parameterName;
142         private @Nullable StatementConfiguration statementConfiguration;
143 
144         public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) {
145             this.renderingStrategy = renderingStrategy;
146             return this;
147         }
148 
149         public Builder withSequence(AtomicInteger sequence) {
150             this.sequence = sequence;
151             return this;
152         }
153 
154         public Builder withTableAliasCalculator(TableAliasCalculator tableAliasCalculator) {
155             this.tableAliasCalculator = tableAliasCalculator;
156             return this;
157         }
158 
159         public Builder withParameterName(@Nullable String parameterName) {
160             this.parameterName = parameterName;
161             return this;
162         }
163 
164         public Builder withStatementConfiguration(StatementConfiguration statementConfiguration) {
165             this.statementConfiguration = statementConfiguration;
166             return this;
167         }
168 
169         public RenderingContext build() {
170             return new RenderingContext(this);
171         }
172     }
173 }