diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java index bdf0ddfa6..7e9c1e3c9 100644 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -7,7 +7,7 @@ * "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 + * http://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 @@ -30,7 +30,7 @@ import java.util.concurrent.ThreadLocalRandom; public final class MavenWrapperDownloader { - private static final String WRAPPER_VERSION = "3.3.2"; + private static final String WRAPPER_VERSION = "3.3.4"; private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE")); @@ -45,8 +45,11 @@ public static void main(String[] args) { try { log(" - Downloader started"); final URL wrapperUrl = URI.create(args[0]).toURL(); - final String jarPath = args[1].replace("..", ""); // Sanitize path - final Path wrapperJarPath = Paths.get(jarPath).toAbsolutePath().normalize(); + final Path baseDir = Paths.get(".").toAbsolutePath().normalize(); + final Path wrapperJarPath = baseDir.resolve(args[1]).normalize(); + if (!wrapperJarPath.startsWith(baseDir)) { + throw new IOException("Invalid path: outside of allowed directory"); + } downloadFileFromURL(wrapperUrl, wrapperJarPath); log("Done"); } catch (IOException e) { diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index b06697c61..7bb288288 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,20 +1,4 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -wrapperVersion=3.3.2 +wrapperVersion=3.3.4 distributionType=source distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar diff --git a/mvnw b/mvnw index 668388825..1ddd97b9e 100755 --- a/mvnw +++ b/mvnw @@ -8,7 +8,7 @@ # "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 +# http://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 @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.2 +# Apache Maven Wrapper startup batch script, version 3.3.4 # # Required ENV vars: # ------------------ @@ -201,6 +201,14 @@ MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} export MAVEN_PROJECTBASEDIR log "$MAVEN_PROJECTBASEDIR" +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. @@ -212,15 +220,13 @@ else log "Couldn't find $wrapperJarPath, downloading it ..." if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" fi while IFS="=" read -r key value; do - # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) - safeValue=$(echo "$value" | tr -d '\r') case "$key" in wrapperUrl) - wrapperUrl="$safeValue" + wrapperUrl=$(trim "${value-}") break ;; esac @@ -235,17 +241,17 @@ else log "Found wget ... using wget" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + wget ${QUIET:+"$QUIET"} "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + wget ${QUIET:+"$QUIET"} --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi elif command -v curl >/dev/null; then log "Found curl ... using curl" [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + curl ${QUIET:+"$QUIET"} -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + curl ${QUIET:+"$QUIET"} --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi else log "Falling back to using Java to download" @@ -276,7 +282,7 @@ fi wrapperSha256Sum="" while IFS="=" read -r key value; do case "$key" in wrapperSha256Sum) - wrapperSha256Sum=$value + wrapperSha256Sum=$(trim "${value-}") break ;; esac @@ -284,7 +290,7 @@ done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" if [ -n "$wrapperSha256Sum" ]; then wrapperSha256Result=false if command -v sha256sum >/dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c - >/dev/null 2>&1; then wrapperSha256Result=true fi elif command -v shasum >/dev/null; then diff --git a/mvnw.cmd b/mvnw.cmd index da4fe4dd9..8366e2170 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -7,7 +7,7 @@ @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM -@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @@ -18,7 +18,7 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM Apache Maven Wrapper startup batch script, version 3.3.4 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @@ -119,7 +119,7 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B @@ -133,7 +133,7 @@ if exist %WRAPPER_JAR% ( ) ) else ( if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java b/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java index 0d3a9ec44..b3dcf9513 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java @@ -25,6 +25,73 @@ import org.mybatis.dynamic.sql.util.FragmentAndParameters; import org.mybatis.dynamic.sql.util.StringUtilities; +/** + * This class represents the definition of a column in a table. + * + *

The class contains many attributes that are helpful for use in MyBatis and Spring runtime + * environments, but the only required attributes are the name of the column and a reference to + * the {@link SqlTable} the column is a part of. + * + *

The class can be extended if you wish to associate additional attributes with a column for your + * own purposes. Extending the class is a bit more challenging than you might expect because you will need to + * handle the covariant types for many methods in {@code SqlColumn}. Additionally, many methods in {@code SqlColumn} + * create new instances of the class in keeping with the library's primary strategy of immutability. You will also + * need to ensure that these methods create instances of your extended class, rather than the base {@code SqlColumn} + * class. We have worked to keep this process as simple as possible. + * + *

Extending the class involves the following activities: + *

    + *
  1. Create a class that extends {@link SqlColumn}
  2. + *
  3. In your extended class, create a static builder class that extends {@link SqlColumn.AbstractBuilder}
  4. + *
  5. Add your desired attributes to the class and the builder
  6. + *
  7. In your extended class, override the {@link SqlColumn#copyBuilder()} method and return a new instance of + * your builder with all attributes set. You should call the + * {@link SqlColumn#populateBaseBuilder(AbstractBuilder)} method + * to set the attributes from {@code SqlColumn}, then populate your extended attributes. + *
  8. + *
  9. You MUST override the following methods. These methods are used with regular operations in the library. + * If you do not override these methods, it is likely that your extended attributes will be lost during + * regular usage. For example, if you do not override the {@code as} method and a user calls the method to + * apply an alias, then the base {@code SqlColumn} class would create a new instance of {@code SqlColumn}, NOT + * your extended class. + * + *
  10. + *
  11. You SHOULD override the following methods. These methods can be used to add additional attributes to a + * column by creating a new instance with a specified attribute set. These methods are used during the + * construction of columns. If you do not override these methods, and a user calls them, then a new + * {@code SqlColumn} will be created that does not contain your extended attributes. + * + *
  12. + *
+ * + *

For all overridden methods except {@code copyBuilder()}, the process is to call the superclass + * method and cast the result properly. We provide a {@link SqlColumn#cast(SqlColumn)} method to aid with this + * process. For example, overriding the {@code descending} method could look like this: + * + *

+ * {@code
+ * @Override
+ * public MyExtendedColumn descending() {
+ *     return cast(super.descending());
+ * }
+ * }
+ * 
+ * + *

The test code for this library contains an example of a proper extension of this class. + * + * @param the Java type associated with the column + */ public class SqlColumn implements BindableColumn, SortSpecification { protected final String name; @@ -39,7 +106,7 @@ public class SqlColumn implements BindableColumn, SortSpecification { protected final @Nullable Class javaType; protected final @Nullable String javaProperty; - private SqlColumn(Builder builder) { + protected SqlColumn(AbstractBuilder builder) { name = Objects.requireNonNull(builder.name); table = Objects.requireNonNull(builder.table); jdbcType = builder.jdbcType; @@ -90,16 +157,27 @@ public Optional javaProperty() { return value == null ? null : parameterTypeConverter.convert(value); } + /** + * Create a new column instance that will render as descending when used in an order by phrase. + * + * @return a new column instance that will render as descending when used in an order by phrase + */ @Override - public SortSpecification descending() { - Builder b = copy(); - return b.withDescendingPhrase(" DESC").build(); //$NON-NLS-1$ + public SqlColumn descending() { + return cast(copyBuilder().withDescendingPhrase(" DESC").build()); //$NON-NLS-1$ } + /** + * Create a new column instance with the specified alias that will render as "as alias" in a column list. + * + * @param alias + * the column alias to set + * + * @return a new column instance with the specified alias + */ @Override public SqlColumn as(String alias) { - Builder b = copy(); - return b.withAlias(alias).build(); + return cast(copyBuilder().withAlias(alias).build()); } /** @@ -110,25 +188,24 @@ public SqlColumn as(String alias) { * @return a new column that will be rendered with the specified table qualifier */ public SqlColumn qualifiedWith(String tableQualifier) { - Builder b = copy(); - b.withTableQualifier(tableQualifier); - return b.build(); + return cast(copyBuilder().withTableQualifier(tableQualifier).build()); } /** - * Set an alias with a camel cased string based on the column name. This can be useful for queries using + * 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. * - *

A camel case string is mixed case, and most databases do not support unquoted mixed case strings + *

A camel case string is a mixed case string, 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 asCamelCase() { - Builder b = copy(); - return b.withAlias("\"" + StringUtilities.toCamelCase(name) + "\"").build(); //$NON-NLS-1$ //$NON-NLS-2$ + return cast(copyBuilder() + .withAlias("\"" + StringUtilities.toCamelCase(name) + "\"") //$NON-NLS-1$ //$NON-NLS-2$ + .build()); } @Override @@ -150,43 +227,118 @@ public Optional renderingStrategy() { return Optional.ofNullable(renderingStrategy); } + /** + * Create a new column instance with the specified type handler. + * + *

This method uses a different type (S). This allows it to be chained with the other + * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain. + * Without this different type (for example, if we used T), the compiler would erase the type after the call + * and method chaining would not work. This is a workaround for Java's lack of reification. + * + * @param typeHandler the type handler to set + * @param the type of the new column (will be the same as T) + * @return a new column instance with the specified type handler + */ public SqlColumn withTypeHandler(String typeHandler) { - Builder b = copy(); - return b.withTypeHandler(typeHandler).build(); + return cast(copyBuilder().withTypeHandler(typeHandler).build()); } + /** + * Create a new column instance with the specified rendering strategy. + * + *

This method uses a different type (S). This allows it to be chained with the other + * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain. + * Without this different type (for example, if we used T), the compiler would erase the type after the call + * and method chaining would not work. This is a workaround for Java's lack of reification. + * + * @param renderingStrategy the rendering strategy to set + * @param the type of the new column (will be the same as T) + * @return a new column instance with the specified type handler + */ public SqlColumn withRenderingStrategy(RenderingStrategy renderingStrategy) { - Builder b = copy(); - return b.withRenderingStrategy(renderingStrategy).build(); + return cast(copyBuilder().withRenderingStrategy(renderingStrategy).build()); } + /** + * Create a new column instance with the specified parameter type converter. + * + *

Parameter type converters are useful with Spring JDBC. Typically, they are not needed for MyBatis. + * + *

This method uses a different type (S). This allows it to be chained with the other + * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain. + * Without this different type (for example, if we used T), the compiler would erase the type after the call + * and method chaining would not work. This is a workaround for Java's lack of reification. + * + * @param parameterTypeConverter the parameter type converter to set + * @param the type of the new column (will be the same as T) + * @return a new column instance with the specified type handler + */ + @SuppressWarnings("unchecked") public SqlColumn withParameterTypeConverter(ParameterTypeConverter parameterTypeConverter) { - Builder b = copy(); - return b.withParameterTypeConverter(parameterTypeConverter).build(); + return cast(copyBuilder().withParameterTypeConverter((ParameterTypeConverter) parameterTypeConverter) + .build()); } + /** + * Create a new column instance with the specified Java type. + * + *

Specifying a Java type will force rendering of the Java type for MyBatis parameters. This can be useful + * with some MyBatis type handlers. + * + *

This method uses a different type (S). This allows it to be chained with the other + * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain. + * Without this different type (for example, if we used T), the compiler would erase the type after the call + * and method chaining would not work. This is a workaround for Java's lack of reification. + * + * @param javaType the Java type to set + * @param the type of the new column (will be the same as T) + * @return a new column instance with the specified type handler + */ + @SuppressWarnings("unchecked") public SqlColumn withJavaType(Class javaType) { - Builder b = copy(); - return b.withJavaType(javaType).build(); + return cast(copyBuilder().withJavaType((Class) javaType).build()); } + /** + * Create a new column instance with the specified Java property. + * + *

Specifying a Java property in the column will allow usage of the column as a "mapped column" in record-based + * insert statements. + * + *

This method uses a different type (S). This allows it to be chained with the other + * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain. + * Without this different type (for example, if we used T), the compiler would erase the type after the call + * and method chaining would not work. This is a workaround for Java's lack of reification. + * + * @param javaProperty the Java property to set + * @param the type of the new column (will be the same as T) + * @return a new column instance with the specified type handler + */ public SqlColumn withJavaProperty(String javaProperty) { - Builder b = copy(); - return b.withJavaProperty(javaProperty).build(); + return cast(copyBuilder().withJavaProperty(javaProperty).build()); + } + + protected AbstractBuilder copyBuilder() { + return populateBaseBuilder(new Builder<>()); + } + + @SuppressWarnings("unchecked") + protected > S cast(SqlColumn column) { + return (S) column; } /** - * 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. + * This method will add all current attributes to the specified builder. It is useful when creating + * new class instances that only change one attribute - we set all current attributes, then + * change the one attribute. This utility can be used with the with* methods and other methods that + * create new instances. * - * @param the type. Will be the same as T for this usage. - * @return a new SqlColumn of type S (S is the same as T) + * @param the concrete builder type + * @return the populated builder */ @SuppressWarnings("unchecked") - private Builder copy() { - return new Builder() + protected > B populateBaseBuilder(B builder) { + return (B) builder .withName(this.name) .withTable(this.table) .withJdbcType(this.jdbcType) @@ -194,9 +346,9 @@ private Builder copy() { .withAlias(this.alias) .withTypeHandler(this.typeHandler) .withRenderingStrategy(this.renderingStrategy) - .withParameterTypeConverter((ParameterTypeConverter) this.parameterTypeConverter) + .withParameterTypeConverter(this.parameterTypeConverter) .withTableQualifier(this.tableQualifier) - .withJavaType((Class) this.javaType) + .withJavaType(this.javaType) .withJavaProperty(this.javaProperty); } @@ -213,7 +365,7 @@ public static SqlColumn of(String name, SqlTable table, JDBCType jdbcType .build(); } - public static class Builder { + public abstract static class AbstractBuilder, B extends AbstractBuilder> { protected @Nullable String name; protected @Nullable SqlTable table; protected @Nullable JDBCType jdbcType; @@ -226,63 +378,75 @@ public static class Builder { protected @Nullable Class javaType; protected @Nullable String javaProperty; - public Builder withName(String name) { + public B withName(String name) { this.name = name; - return this; + return getThis(); } - public Builder withTable(SqlTable table) { + public B withTable(SqlTable table) { this.table = table; - return this; + return getThis(); } - public Builder withJdbcType(@Nullable JDBCType jdbcType) { + public B withJdbcType(@Nullable JDBCType jdbcType) { this.jdbcType = jdbcType; - return this; + return getThis(); } - public Builder withDescendingPhrase(String descendingPhrase) { + public B withDescendingPhrase(String descendingPhrase) { this.descendingPhrase = descendingPhrase; - return this; + return getThis(); } - public Builder withAlias(@Nullable String alias) { + public B withAlias(@Nullable String alias) { this.alias = alias; - return this; + return getThis(); } - public Builder withTypeHandler(@Nullable String typeHandler) { + public B withTypeHandler(@Nullable String typeHandler) { this.typeHandler = typeHandler; - return this; + return getThis(); } - public Builder withRenderingStrategy(@Nullable RenderingStrategy renderingStrategy) { + public B withRenderingStrategy(@Nullable RenderingStrategy renderingStrategy) { this.renderingStrategy = renderingStrategy; - return this; + return getThis(); } - public Builder withParameterTypeConverter(ParameterTypeConverter parameterTypeConverter) { + public B withParameterTypeConverter(ParameterTypeConverter parameterTypeConverter) { this.parameterTypeConverter = parameterTypeConverter; - return this; + return getThis(); } - private Builder withTableQualifier(@Nullable String tableQualifier) { + public B withTableQualifier(@Nullable String tableQualifier) { this.tableQualifier = tableQualifier; - return this; + return getThis(); } - public Builder withJavaType(@Nullable Class javaType) { + public B withJavaType(@Nullable Class javaType) { this.javaType = javaType; - return this; + return getThis(); } - public Builder withJavaProperty(@Nullable String javaProperty) { + public B withJavaProperty(@Nullable String javaProperty) { this.javaProperty = javaProperty; - return this; + return getThis(); } + protected abstract B getThis(); + + public abstract C build(); + } + + public static class Builder extends AbstractBuilder, Builder> { + @Override public SqlColumn build() { return new SqlColumn<>(this); } + + @Override + protected Builder getThis() { + return this; + } } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToWhenPresent.java index ccb868c94..970ef0775 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToWhenPresent.java @@ -26,16 +26,16 @@ public class IsGreaterThanOrEqualToWhenPresent extends AbstractSingleValueCon implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { private static final IsGreaterThanOrEqualToWhenPresent EMPTY = new IsGreaterThanOrEqualToWhenPresent(-1) { - @Override - public Object value() { - throw new NoSuchElementException("No value present"); //$NON-NLS-1$ - } + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } - @Override - public boolean isEmpty() { - return true; - } - }; + @Override + public boolean isEmpty() { + return true; + } + }; public static IsGreaterThanOrEqualToWhenPresent empty() { @SuppressWarnings("unchecked") diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitiveWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitiveWhenPresent.java index ae8398aef..0f06e3311 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitiveWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitiveWhenPresent.java @@ -28,16 +28,16 @@ public class IsLikeCaseInsensitiveWhenPresent extends AbstractSingleValueCond AbstractSingleValueCondition.Mappable { private static final IsLikeCaseInsensitiveWhenPresent EMPTY = new IsLikeCaseInsensitiveWhenPresent<>("") { //$NON-NLS-1$ - @Override - public String value() { - throw new NoSuchElementException("No value present"); //$NON-NLS-1$ - } + @Override + public String value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } - @Override - public boolean isEmpty() { - return true; - } - }; + @Override + public boolean isEmpty() { + return true; + } + }; public static IsLikeCaseInsensitiveWhenPresent empty() { @SuppressWarnings("unchecked") diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitiveWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitiveWhenPresent.java index df07202f9..880b20ab8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitiveWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitiveWhenPresent.java @@ -28,16 +28,16 @@ public class IsNotLikeCaseInsensitiveWhenPresent extends AbstractSingleValueC AbstractSingleValueCondition.Mappable { private static final IsNotLikeCaseInsensitiveWhenPresent EMPTY = new IsNotLikeCaseInsensitiveWhenPresent<>("") { //$NON-NLS-1$ - @Override - public String value() { - throw new NoSuchElementException("No value present"); //$NON-NLS-1$ - } + @Override + public String value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } - @Override - public boolean isEmpty() { - return true; - } - }; + @Override + public boolean isEmpty() { + return true; + } + }; public static IsNotLikeCaseInsensitiveWhenPresent empty() { @SuppressWarnings("unchecked") diff --git a/src/test/java/examples/simple/ExtendedColumnTest.java b/src/test/java/examples/simple/ExtendedColumnTest.java new file mode 100644 index 000000000..af89355ed --- /dev/null +++ b/src/test/java/examples/simple/ExtendedColumnTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2016-2025 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 examples.simple; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.mybatis.dynamic.sql.ParameterTypeConverter; +import org.mybatis.dynamic.sql.SqlTable; +import org.mybatis.dynamic.sql.render.RenderingStrategies; + +class ExtendedColumnTest { + + private final SqlTable table = SqlTable.of("foo"); + private final PrimaryKeyColumn bar = new PrimaryKeyColumn.Builder() + .withName("first_name") + .withTable(table) + .isPrimaryKeyColumn(true) + .build(); + private final ParameterTypeConverter ptc = Object::toString; + + @Test + void testPropagatedDescending() { + var baz = bar.descending(); + + assertThat(baz.isPrimaryKeyColumn()).isTrue(); + } + + @Test + void testPropagatedAlias() { + var baz = bar.as("fred"); + + assertThat(baz.alias()).hasValue("fred"); + assertThat(baz.isPrimaryKeyColumn()).isTrue(); + } + + @Test + void testPropagatedQualifiedWith() { + var baz = bar.qualifiedWith("fred"); + + assertThat(baz.isPrimaryKeyColumn()).isTrue(); + } + + @Test + void testPropagatedAsCamelCase() { + var baz = bar.asCamelCase(); + + assertThat(baz.isPrimaryKeyColumn()).isTrue(); + } + + @Test + void testPropagatedWithTypeHandler() { + var baz = bar.withTypeHandler("barney"); + + assertThat(baz.typeHandler()).hasValue("barney"); + assertThat(baz.isPrimaryKeyColumn()).isTrue(); + } + + @Test + void testPropagatedRenderingStrategy() { + var baz = bar.withRenderingStrategy(RenderingStrategies.MYBATIS3); + + assertThat(baz.renderingStrategy()).hasValue(RenderingStrategies.MYBATIS3); + assertThat(baz.isPrimaryKeyColumn()).isTrue(); + } + + @Test + void testPropagatedParameterTypeConverter() { + var baz = bar.withParameterTypeConverter(ptc); + + assertThat(baz.convertParameterType(11)).isEqualTo("11"); + assertThat(baz.isPrimaryKeyColumn()).isTrue(); + } + + @Test + void testPropagatedJavaType() { + var baz = bar.withJavaType(Integer.class); + + assertThat(baz.javaType()).hasValue(Integer.class); + assertThat(baz.isPrimaryKeyColumn()).isTrue(); + } + + @Test + void testPropagatedJavaProperty() { + var baz = bar.withJavaProperty("id"); + + assertThat(baz.javaProperty()).hasValue("id"); + assertThat(baz.isPrimaryKeyColumn()).isTrue(); + } + + @Test + void testAll() { + PrimaryKeyColumn baz = bar.descending() + .as("fred") + .qualifiedWith("fred") + .asCamelCase() + .withTypeHandler("barney") + .withRenderingStrategy(RenderingStrategies.MYBATIS3) + .withParameterTypeConverter(ptc) + .withJavaType(Integer.class) + .withJavaProperty("id"); + + assertThat(baz.alias()).hasValue("\"firstName\""); + assertThat(baz.typeHandler()).hasValue("barney"); + assertThat(baz.renderingStrategy()).hasValue(RenderingStrategies.MYBATIS3); + assertThat(baz.convertParameterType(11)).isEqualTo("11"); + assertThat(baz.javaType()).hasValue(Integer.class); + assertThat(baz.javaProperty()).hasValue("id"); + assertThat(baz.isPrimaryKeyColumn()).isTrue(); + } +} diff --git a/src/test/java/examples/simple/PersonDynamicSqlSupport.java b/src/test/java/examples/simple/PersonDynamicSqlSupport.java index ba31eea5d..62d3c0840 100644 --- a/src/test/java/examples/simple/PersonDynamicSqlSupport.java +++ b/src/test/java/examples/simple/PersonDynamicSqlSupport.java @@ -23,7 +23,7 @@ public final class PersonDynamicSqlSupport { public static final Person person = new Person(); - public static final SqlColumn id = person.id; + public static final PrimaryKeyColumn id = person.id; public static final SqlColumn firstName = person.firstName; public static final SqlColumn lastName = person.lastName; public static final SqlColumn birthDate = person.birthDate; @@ -32,7 +32,13 @@ public final class PersonDynamicSqlSupport { public static final SqlColumn addressId = person.addressId; public static final class Person extends SqlTable { - public final SqlColumn id = column("id", JDBCType.INTEGER).withJavaProperty("id"); + public final PrimaryKeyColumn id = new PrimaryKeyColumn.Builder() + .withTable(this) + .withName("id") + .withJdbcType(JDBCType.INTEGER) + .withJavaProperty("id") + .isPrimaryKeyColumn(true) + .build(); public final SqlColumn firstName = column("first_name", JDBCType.VARCHAR) .withJavaProperty("firstName"); public final SqlColumn lastName = diff --git a/src/test/java/examples/simple/PrimaryKeyColumn.java b/src/test/java/examples/simple/PrimaryKeyColumn.java new file mode 100644 index 000000000..30d51f72a --- /dev/null +++ b/src/test/java/examples/simple/PrimaryKeyColumn.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016-2025 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 examples.simple; + +import org.mybatis.dynamic.sql.ParameterTypeConverter; +import org.mybatis.dynamic.sql.SqlColumn; +import org.mybatis.dynamic.sql.render.RenderingStrategy; + +/** + * This class is an example of a properly extended {@link SqlColumn}. + * + * @param the Java type associated with this column + */ +public class PrimaryKeyColumn extends SqlColumn { + private final boolean isPrimaryKeyColumn; + + private PrimaryKeyColumn(Builder builder) { + super(builder); + isPrimaryKeyColumn = builder.isPrimaryKeyColumn; + } + + public boolean isPrimaryKeyColumn() { + return isPrimaryKeyColumn; + } + + @Override + public PrimaryKeyColumn descending() { + return cast(super.descending()); + } + + @Override + public PrimaryKeyColumn as(String alias) { + return cast(super.as(alias)); + } + + @Override + public PrimaryKeyColumn qualifiedWith(String tableQualifier) { + return cast(super.qualifiedWith(tableQualifier)); + } + + @Override + public PrimaryKeyColumn asCamelCase() { + return cast(super.asCamelCase()); + } + + @Override + public PrimaryKeyColumn withTypeHandler(String typeHandler) { + return cast(super.withTypeHandler(typeHandler)); + } + + @Override + public PrimaryKeyColumn withRenderingStrategy(RenderingStrategy renderingStrategy) { + return cast(super.withRenderingStrategy(renderingStrategy)); + } + + @Override + public PrimaryKeyColumn withParameterTypeConverter(ParameterTypeConverter parameterTypeConverter) { + return cast(super.withParameterTypeConverter(parameterTypeConverter)); + } + + @Override + public PrimaryKeyColumn withJavaType(Class javaType) { + return cast(super.withJavaType(javaType)); + } + + @Override + public PrimaryKeyColumn withJavaProperty(String javaProperty) { + return cast(super.withJavaProperty(javaProperty)); + } + + @Override + protected Builder copyBuilder() { + return populateBaseBuilder(new Builder<>()).isPrimaryKeyColumn(isPrimaryKeyColumn); + } + + public static class Builder extends AbstractBuilder, Builder> { + private boolean isPrimaryKeyColumn; + + public Builder isPrimaryKeyColumn(boolean isPrimaryKeyColumn) { + this.isPrimaryKeyColumn = isPrimaryKeyColumn; + return this; + } + + @Override + public PrimaryKeyColumn build() { + return new PrimaryKeyColumn<>(this); + } + + @Override + protected Builder getThis() { + return this; + } + } +}