SqlColumn.java
/*
* Copyright 2016-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mybatis.dynamic.sql;
import java.sql.JDBCType;
import java.util.Objects;
import java.util.Optional;
import org.jetbrains.annotations.NotNull;
import org.mybatis.dynamic.sql.render.RenderingContext;
import org.mybatis.dynamic.sql.render.RenderingStrategy;
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
import org.mybatis.dynamic.sql.util.StringUtilities;
public class SqlColumn<T> implements BindableColumn<T>, SortSpecification {
protected final String name;
protected final SqlTable table;
protected final JDBCType jdbcType;
protected final boolean isDescending;
protected final String alias;
protected final String typeHandler;
protected final RenderingStrategy renderingStrategy;
protected final ParameterTypeConverter<T, ?> parameterTypeConverter;
protected final String tableQualifier;
protected final Class<T> javaType;
private SqlColumn(Builder<T> builder) {
name = Objects.requireNonNull(builder.name);
table = Objects.requireNonNull(builder.table);
jdbcType = builder.jdbcType;
isDescending = builder.isDescending;
alias = builder.alias;
typeHandler = builder.typeHandler;
renderingStrategy = builder.renderingStrategy;
parameterTypeConverter = Objects.requireNonNull(builder.parameterTypeConverter);
tableQualifier = builder.tableQualifier;
javaType = builder.javaType;
}
public String name() {
return name;
}
public SqlTable table() {
return table;
}
@Override
public Optional<JDBCType> jdbcType() {
return Optional.ofNullable(jdbcType);
}
@Override
public Optional<String> alias() {
return Optional.ofNullable(alias);
}
@Override
public Optional<String> typeHandler() {
return Optional.ofNullable(typeHandler);
}
@Override
public Optional<Class<T>> javaType() {
return Optional.ofNullable(javaType);
}
@Override
public Object convertParameterType(T value) {
return parameterTypeConverter.convert(value);
}
@Override
public SortSpecification descending() {
Builder<T> b = copy();
return b.withDescending(true).build();
}
@Override
public SqlColumn<T> as(String alias) {
Builder<T> b = copy();
return b.withAlias(alias).build();
}
/**
* Override the calculated table qualifier if there is one. This is useful for sub-queries
* where the calculated table qualifier may not be correct in all cases.
*
* @param tableQualifier the table qualifier to apply to the rendered column name
* @return a new column that will be rendered with the specified table qualifier
*/
public SqlColumn<T> qualifiedWith(String tableQualifier) {
Builder<T> b = copy();
b.withTableQualifier(tableQualifier);
return b.build();
}
/**
* Set an alias with a camel cased string based on the column name. This can be useful for queries using
* the {@link org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper} where the columns are placed into
* a map based on the column name returned from the database.
*
* <p>A camel case string is mixed case, and most databases do not support unquoted mixed case strings
* as identifiers. Therefore, the generated alias will be surrounded by double quotes thereby making it a
* quoted identifier. Most databases will respect quoted mixed case identifiers.
*
* @return a new column aliased with a camel case version of the column name
*/
public SqlColumn<T> asCamelCase() {
Builder<T> b = copy();
return b.withAlias("\"" + StringUtilities.toCamelCase(name) + "\"").build(); //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
public boolean isDescending() {
return isDescending;
}
@Override
public String orderByName() {
return alias().orElse(name);
}
@Override
public FragmentAndParameters render(RenderingContext renderingContext) {
if (tableQualifier == null) {
return FragmentAndParameters.fromFragment(renderingContext.aliasedColumnName(this));
} else {
return FragmentAndParameters.fromFragment(renderingContext.aliasedColumnName(this, tableQualifier));
}
}
@Override
public Optional<RenderingStrategy> renderingStrategy() {
return Optional.ofNullable(renderingStrategy);
}
@NotNull
public <S> SqlColumn<S> withTypeHandler(String typeHandler) {
Builder<S> b = copy();
return b.withTypeHandler(typeHandler).build();
}
@NotNull
public <S> SqlColumn<S> withRenderingStrategy(RenderingStrategy renderingStrategy) {
Builder<S> b = copy();
return b.withRenderingStrategy(renderingStrategy).build();
}
@NotNull
public <S> SqlColumn<S> withParameterTypeConverter(ParameterTypeConverter<S, ?> parameterTypeConverter) {
Builder<S> b = copy();
return b.withParameterTypeConverter(parameterTypeConverter).build();
}
@NotNull
public <S> SqlColumn<S> withJavaType(Class<S> javaType) {
Builder<S> b = copy();
return b.withJavaType(javaType).build();
}
/**
* This method helps us tell a bit of fiction to the Java compiler. Java, for better or worse,
* does not carry generic type information through chained methods. We want to enable method
* chaining in the "with" methods. With this bit of fiction, we force the compiler to delay type
* inference to the last method in the chain.
*
* @param <S> the type. Will be the same as T for this usage.
* @return a new SqlColumn of type S (S is the same as T)
*/
@SuppressWarnings("unchecked")
private <S> Builder<S> copy() {
return new Builder<S>()
.withName(this.name)
.withTable(this.table)
.withJdbcType(this.jdbcType)
.withDescending(this.isDescending)
.withAlias(this.alias)
.withTypeHandler(this.typeHandler)
.withRenderingStrategy(this.renderingStrategy)
.withParameterTypeConverter((ParameterTypeConverter<S, ?>) this.parameterTypeConverter)
.withTableQualifier(this.tableQualifier)
.withJavaType((Class<S>) this.javaType);
}
public static <T> SqlColumn<T> of(String name, SqlTable table) {
return new Builder<T>().withName(name)
.withTable(table)
.build();
}
public static <T> SqlColumn<T> of(String name, SqlTable table, JDBCType jdbcType) {
return new Builder<T>().withName(name)
.withTable(table)
.withJdbcType(jdbcType)
.build();
}
public static class Builder<T> {
protected String name;
protected SqlTable table;
protected JDBCType jdbcType;
protected boolean isDescending = false;
protected String alias;
protected String typeHandler;
protected RenderingStrategy renderingStrategy;
protected ParameterTypeConverter<T, ?> parameterTypeConverter = v -> v;
protected String tableQualifier;
protected Class<T> javaType;
public Builder<T> withName(String name) {
this.name = name;
return this;
}
public Builder<T> withTable(SqlTable table) {
this.table = table;
return this;
}
public Builder<T> withJdbcType(JDBCType jdbcType) {
this.jdbcType = jdbcType;
return this;
}
public Builder<T> withDescending(boolean isDescending) {
this.isDescending = isDescending;
return this;
}
public Builder<T> withAlias(String alias) {
this.alias = alias;
return this;
}
public Builder<T> withTypeHandler(String typeHandler) {
this.typeHandler = typeHandler;
return this;
}
public Builder<T> withRenderingStrategy(RenderingStrategy renderingStrategy) {
this.renderingStrategy = renderingStrategy;
return this;
}
public Builder<T> withParameterTypeConverter(ParameterTypeConverter<T, ?> parameterTypeConverter) {
this.parameterTypeConverter = parameterTypeConverter;
return this;
}
private Builder<T> withTableQualifier(String tableQualifier) {
this.tableQualifier = tableQualifier;
return this;
}
public Builder<T> withJavaType(Class<T> javaType) {
this.javaType = javaType;
return this;
}
public SqlColumn<T> build() {
return new SqlColumn<>(this);
}
}
}