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;
17
18 import java.sql.JDBCType;
19 import java.util.Objects;
20 import java.util.Optional;
21
22 import org.jspecify.annotations.Nullable;
23 import org.mybatis.dynamic.sql.render.RenderingContext;
24 import org.mybatis.dynamic.sql.render.RenderingStrategy;
25 import org.mybatis.dynamic.sql.util.FragmentAndParameters;
26 import org.mybatis.dynamic.sql.util.StringUtilities;
27
28 /**
29 * This class represents the definition of a column in a table.
30 *
31 * <p>The class contains many attributes that are helpful for use in MyBatis and Spring runtime
32 * environments, but the only required attributes are the name of the column and a reference to
33 * the {@link SqlTable} the column is a part of.
34 *
35 * <p>The class can be extended if you wish to associate additional attributes with a column for your
36 * own purposes. Extending the class is a bit more challenging than you might expect because you may need to
37 * handle the covariant types for many methods in {@code SqlColumn}. Additionally, many methods in {@code SqlColumn}
38 * create new instances of the class in keeping with the library's primary strategy of immutability. You will also
39 * need to ensure that these methods create instances of your extended class, rather than the base {@code SqlColumn}
40 * class. We have worked to keep this process as simple as possible.
41 *
42 * <p>Extending the class involves the following activities:
43 * <ol>
44 * <li>Create a class that extends {@link SqlColumn}</li>
45 * <li>In your extended class, create a static builder class that extends {@link SqlColumn.AbstractBuilder}</li>
46 * <li>Add your desired attributes to the class and the builder</li>
47 * <li>You MUST override the {@link SqlColumn#copyBuilder()} method and return a new instance of
48 * your builder with all attributes set. In the overridden method you should call the superclass
49 * {@link SqlColumn#populateBaseBuilder(AbstractBuilder)} method
50 * to set the attributes from the base {@code SqlColumn}, then populate your extended attributes. During normal
51 * usage, the library may create additional instances of your class. If you do not override the
52 * {@link SqlColumn#copyBuilder()} method properly, then your extended attributes will be lost.
53 * </li>
54 * <li>You MAY override the following methods. These methods are used with regular operations in the library and
55 * create new instances of the class. However, these methods are not typically chained, so losing the specific
56 * type may not be a problem. If you want to preserve the type, then you can override these methods
57 * to specify the covariant return type. See below for usage of the {@link SqlColumn#cast(SqlColumn)} method
58 * to make it easier to override these methods.
59 * <ul>
60 * <li>{@link SqlColumn#as(String)}</li>
61 * <li>{@link SqlColumn#asCamelCase()}</li>
62 * <li>{@link SqlColumn#descending()}</li>
63 * <li>{@link SqlColumn#qualifiedWith(String)}</li>
64 * </ul>
65 * </li>
66 * <li>You SHOULD override the following methods. These methods can be used to add additional attributes to a
67 * column by creating a new instance with a specified attribute set. These methods are used during the
68 * construction of columns. If you do not override these methods, and a user calls them, then the specific type
69 * will be lost. If you want to preserve the type, then you can override these methods
70 * to specify the covariant return type. See below for usage of the {@link SqlColumn#cast(SqlColumn)} method
71 * to make it easier to override these methods.
72 * <ul>
73 * <li>{@link SqlColumn#withJavaProperty(String)}</li>
74 * <li>{@link SqlColumn#withRenderingStrategy(RenderingStrategy)}</li>
75 * <li>{@link SqlColumn#withTypeHandler(String)}</li>
76 * <li>{@link SqlColumn#withJavaType(Class)}</li>
77 * <li>{@link SqlColumn#withParameterTypeConverter(ParameterTypeConverter)}</li>
78 * </ul>
79 * </li>
80 * </ol>
81 *
82 * <p>For all overridden methods except {@code copyBuilder()}, the process is to call the superclass
83 * method and cast the result properly. We provide a {@link SqlColumn#cast(SqlColumn)} method to aid with this
84 * process. For example, overriding the {@code descending} method could look like this:
85 *
86 * <pre>
87 * {@code
88 * @Override
89 * public MyExtendedColumn<T> descending() {
90 * return cast(super.descending());
91 * }
92 * }
93 * </pre>
94 *
95 * <p>The test code for this library contains an example of a fully executed extension of this class.
96 *
97 * @param <T> the Java type associated with the column
98 */
99 public class SqlColumn<T> implements BindableColumn<T>, SortSpecification {
100
101 protected final String name;
102 protected final SqlTable table;
103 protected final @Nullable JDBCType jdbcType;
104 protected final String descendingPhrase;
105 protected final @Nullable String alias;
106 protected final @Nullable String typeHandler;
107 protected final @Nullable RenderingStrategy renderingStrategy;
108 protected final ParameterTypeConverter<T, ?> parameterTypeConverter;
109 protected final @Nullable String tableQualifier;
110 protected final @Nullable Class<T> javaType;
111 protected final @Nullable String javaProperty;
112
113 protected SqlColumn(AbstractBuilder<T, ?, ?> builder) {
114 name = Objects.requireNonNull(builder.name);
115 table = Objects.requireNonNull(builder.table);
116 jdbcType = builder.jdbcType;
117 descendingPhrase = builder.descendingPhrase;
118 alias = builder.alias;
119 typeHandler = builder.typeHandler;
120 renderingStrategy = builder.renderingStrategy;
121 parameterTypeConverter = Objects.requireNonNull(builder.parameterTypeConverter);
122 tableQualifier = builder.tableQualifier;
123 javaType = builder.javaType;
124 javaProperty = builder.javaProperty;
125 }
126
127 public String name() {
128 return name;
129 }
130
131 public SqlTable table() {
132 return table;
133 }
134
135 @Override
136 public Optional<JDBCType> jdbcType() {
137 return Optional.ofNullable(jdbcType);
138 }
139
140 @Override
141 public Optional<String> alias() {
142 return Optional.ofNullable(alias);
143 }
144
145 @Override
146 public Optional<String> typeHandler() {
147 return Optional.ofNullable(typeHandler);
148 }
149
150 @Override
151 public Optional<Class<T>> javaType() {
152 return Optional.ofNullable(javaType);
153 }
154
155 public Optional<String> javaProperty() {
156 return Optional.ofNullable(javaProperty);
157 }
158
159 @Override
160 public @Nullable Object convertParameterType(@Nullable T value) {
161 return value == null ? null : parameterTypeConverter.convert(value);
162 }
163
164 /**
165 * Create a new column instance that will render as descending when used in an order by phrase.
166 *
167 * @return a new column instance that will render as descending when used in an order by phrase
168 */
169 @Override
170 public SqlColumn<T> descending() {
171 return copyBuilder().withDescendingPhrase(" DESC").build(); //$NON-NLS-1$
172 }
173
174 /**
175 * Create a new column instance with the specified alias that will render as "as alias" in a column list.
176 *
177 * @param alias
178 * the column alias to set
179 *
180 * @return a new column instance with the specified alias
181 */
182 @Override
183 public SqlColumn<T> as(String alias) {
184 return copyBuilder().withAlias(alias).build();
185 }
186
187 /**
188 * Override the calculated table qualifier if there is one. This is useful for sub-queries
189 * where the calculated table qualifier may not be correct in all cases.
190 *
191 * @param tableQualifier the table qualifier to apply to the rendered column name
192 * @return a new column that will be rendered with the specified table qualifier
193 */
194 public SqlColumn<T> qualifiedWith(String tableQualifier) {
195 return copyBuilder().withTableQualifier(tableQualifier).build();
196 }
197
198 /**
199 * Set an alias with a camel-cased string based on the column name. This can be useful for queries using
200 * the {@link org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper} where the columns are placed into
201 * a map based on the column name returned from the database.
202 *
203 * <p>A camel case string is a mixed case string, and most databases do not support unquoted mixed case strings
204 * as identifiers. Therefore, the generated alias will be surrounded by double quotes thereby making it a
205 * quoted identifier. Most databases will respect quoted mixed case identifiers.
206 *
207 * @return a new column aliased with a camel case version of the column name
208 */
209 public SqlColumn<T> asCamelCase() {
210 return copyBuilder()
211 .withAlias("\"" + StringUtilities.toCamelCase(name) + "\"") //$NON-NLS-1$ //$NON-NLS-2$
212 .build();
213 }
214
215 @Override
216 public FragmentAndParameters renderForOrderBy(RenderingContext renderingContext) {
217 return FragmentAndParameters.fromFragment(alias().orElse(name) + descendingPhrase);
218 }
219
220 @Override
221 public FragmentAndParameters render(RenderingContext renderingContext) {
222 if (tableQualifier == null) {
223 return FragmentAndParameters.fromFragment(renderingContext.aliasedColumnName(this));
224 } else {
225 return FragmentAndParameters.fromFragment(renderingContext.aliasedColumnName(this, tableQualifier));
226 }
227 }
228
229 @Override
230 public Optional<RenderingStrategy> renderingStrategy() {
231 return Optional.ofNullable(renderingStrategy);
232 }
233
234 /**
235 * Create a new column instance with the specified type handler.
236 *
237 * <p>This method uses a different type (S). This allows it to be chained with the other
238 * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain.
239 * Without this different type (for example, if we used T), the compiler would erase the type after the call
240 * and method chaining would not work. This is a workaround for Java's lack of reification.
241 *
242 * @param typeHandler the type handler to set
243 * @param <S> the type of the new column (will be the same as T)
244 * @return a new column instance with the specified type handler
245 */
246 public <S> SqlColumn<S> withTypeHandler(String typeHandler) {
247 return cast(copyBuilder().withTypeHandler(typeHandler).build());
248 }
249
250 /**
251 * Create a new column instance with the specified rendering strategy.
252 *
253 * <p>This method uses a different type (S). This allows it to be chained with the other
254 * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain.
255 * Without this different type (for example, if we used T), the compiler would erase the type after the call
256 * and method chaining would not work. This is a workaround for Java's lack of reification.
257 *
258 * @param renderingStrategy the rendering strategy to set
259 * @param <S> the type of the new column (will be the same as T)
260 * @return a new column instance with the specified type handler
261 */
262 public <S> SqlColumn<S> withRenderingStrategy(RenderingStrategy renderingStrategy) {
263 return cast(copyBuilder().withRenderingStrategy(renderingStrategy).build());
264 }
265
266 /**
267 * Create a new column instance with the specified parameter type converter.
268 *
269 * <p>Parameter type converters are useful with Spring JDBC. Typically, they are not needed for MyBatis.
270 *
271 * <p>This method uses a different type (S). This allows it to be chained with the other
272 * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain.
273 * Without this different type (for example, if we used T), the compiler would erase the type after the call
274 * and method chaining would not work. This is a workaround for Java's lack of reification.
275 *
276 * @param parameterTypeConverter the parameter type converter to set
277 * @param <S> the type of the new column (will be the same as T)
278 * @return a new column instance with the specified type handler
279 */
280 @SuppressWarnings("unchecked")
281 public <S> SqlColumn<S> withParameterTypeConverter(ParameterTypeConverter<S, ?> parameterTypeConverter) {
282 return cast(copyBuilder().withParameterTypeConverter((ParameterTypeConverter<T, ?>) parameterTypeConverter)
283 .build());
284 }
285
286 /**
287 * Create a new column instance with the specified Java type.
288 *
289 * <p>Specifying a Java type will force rendering of the Java type for MyBatis parameters. This can be useful
290 * with some MyBatis type handlers.
291 *
292 * <p>This method uses a different type (S). This allows it to be chained with the other
293 * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain.
294 * Without this different type (for example, if we used T), the compiler would erase the type after the call
295 * and method chaining would not work. This is a workaround for Java's lack of reification.
296 *
297 * @param javaType the Java type to set
298 * @param <S> the type of the new column (will be the same as T)
299 * @return a new column instance with the specified type handler
300 */
301 @SuppressWarnings("unchecked")
302 public <S> SqlColumn<S> withJavaType(Class<S> javaType) {
303 return cast(copyBuilder().withJavaType((Class<T>) javaType).build());
304 }
305
306 /**
307 * Create a new column instance with the specified Java property.
308 *
309 * <p>Specifying a Java property in the column will allow usage of the column as a "mapped column" in record-based
310 * insert statements.
311 *
312 * <p>This method uses a different type (S). This allows it to be chained with the other
313 * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain.
314 * Without this different type (for example, if we used T), the compiler would erase the type after the call
315 * and method chaining would not work. This is a workaround for Java's lack of reification.
316 *
317 * @param javaProperty the Java property to set
318 * @param <S> the type of the new column (will be the same as T)
319 * @return a new column instance with the specified type handler
320 */
321 public <S> SqlColumn<S> withJavaProperty(String javaProperty) {
322 return cast(copyBuilder().withJavaProperty(javaProperty).build());
323 }
324
325 /**
326 * Create a new Builder, then populate all attributes in the builder with current values.
327 *
328 * <p>This method is used to create copies of the class during normal operations (e.g. when calling the
329 * {@link SqlColumn#as(String)} method). Any subclass of {@code SqlColumn} MUST override this method.
330 *
331 * @return a new Builder instance with all current values populated
332 */
333 protected AbstractBuilder<T, ?, ?> copyBuilder() {
334 return populateBaseBuilder(new Builder<>());
335 }
336
337 @SuppressWarnings("unchecked")
338 protected <S extends SqlColumn<?>> S cast(SqlColumn<?> column) {
339 return (S) column;
340 }
341
342 /**
343 * This method will add all current attributes to the specified builder. It is useful when creating
344 * new class instances that only change one attribute - we set all current attributes, then
345 * change the one attribute. This utility can be used with the with* methods and other methods that
346 * create new instances.
347 *
348 * @param <B> the concrete builder type
349 * @return the populated builder
350 */
351 @SuppressWarnings("unchecked")
352 protected <B extends AbstractBuilder<T, ?, ?>> B populateBaseBuilder(B builder) {
353 return (B) builder
354 .withName(this.name)
355 .withTable(this.table)
356 .withJdbcType(this.jdbcType)
357 .withDescendingPhrase(this.descendingPhrase)
358 .withAlias(this.alias)
359 .withTypeHandler(this.typeHandler)
360 .withRenderingStrategy(this.renderingStrategy)
361 .withParameterTypeConverter(this.parameterTypeConverter)
362 .withTableQualifier(this.tableQualifier)
363 .withJavaType(this.javaType)
364 .withJavaProperty(this.javaProperty);
365 }
366
367 public static <T> SqlColumn<T> of(String name, SqlTable table) {
368 return new Builder<T>().withName(name)
369 .withTable(table)
370 .build();
371 }
372
373 public static <T> SqlColumn<T> of(String name, SqlTable table, JDBCType jdbcType) {
374 return new Builder<T>().withName(name)
375 .withTable(table)
376 .withJdbcType(jdbcType)
377 .build();
378 }
379
380 public abstract static class AbstractBuilder<T, C extends SqlColumn<T>, B extends AbstractBuilder<T, C, B>> {
381 protected @Nullable String name;
382 protected @Nullable SqlTable table;
383 protected @Nullable JDBCType jdbcType;
384 protected String descendingPhrase = ""; //$NON-NLS-1$
385 protected @Nullable String alias;
386 protected @Nullable String typeHandler;
387 protected @Nullable RenderingStrategy renderingStrategy;
388 protected ParameterTypeConverter<T, ?> parameterTypeConverter = v -> v;
389 protected @Nullable String tableQualifier;
390 protected @Nullable Class<T> javaType;
391 protected @Nullable String javaProperty;
392
393 public B withName(String name) {
394 this.name = name;
395 return getThis();
396 }
397
398 public B withTable(SqlTable table) {
399 this.table = table;
400 return getThis();
401 }
402
403 public B withJdbcType(@Nullable JDBCType jdbcType) {
404 this.jdbcType = jdbcType;
405 return getThis();
406 }
407
408 public B withDescendingPhrase(String descendingPhrase) {
409 this.descendingPhrase = descendingPhrase;
410 return getThis();
411 }
412
413 public B withAlias(@Nullable String alias) {
414 this.alias = alias;
415 return getThis();
416 }
417
418 public B withTypeHandler(@Nullable String typeHandler) {
419 this.typeHandler = typeHandler;
420 return getThis();
421 }
422
423 public B withRenderingStrategy(@Nullable RenderingStrategy renderingStrategy) {
424 this.renderingStrategy = renderingStrategy;
425 return getThis();
426 }
427
428 public B withParameterTypeConverter(ParameterTypeConverter<T, ?> parameterTypeConverter) {
429 this.parameterTypeConverter = parameterTypeConverter;
430 return getThis();
431 }
432
433 public B withTableQualifier(@Nullable String tableQualifier) {
434 this.tableQualifier = tableQualifier;
435 return getThis();
436 }
437
438 public B withJavaType(@Nullable Class<T> javaType) {
439 this.javaType = javaType;
440 return getThis();
441 }
442
443 public B withJavaProperty(@Nullable String javaProperty) {
444 this.javaProperty = javaProperty;
445 return getThis();
446 }
447
448 protected abstract B getThis();
449
450 public abstract C build();
451 }
452
453 public static class Builder<T> extends AbstractBuilder<T, SqlColumn<T>, Builder<T>> {
454 @Override
455 public SqlColumn<T> build() {
456 return new SqlColumn<>(this);
457 }
458
459 @Override
460 protected Builder<T> getThis() {
461 return this;
462 }
463 }
464 }