diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 43119e8b4..66bba3618 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,14 +2,17 @@ name: Java CI on: [workflow_dispatch, push, pull_request] +permissions: read-all + jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: + cache: [maven] + distribution: [temurin] + java: [17, 21, 24, 25-ea] os: [ubuntu-latest] - java: [17, 21, 22-ea] - distribution: ['temurin'] fail-fast: false max-parallel: 4 name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} @@ -21,5 +24,6 @@ jobs: with: java-version: ${{ matrix.java }} distribution: ${{ matrix.distribution }} + cache: ${{ matrix.cache }} - name: Test with Maven run: ./mvnw test -B -V --no-transfer-progress -D"license.skip=true" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 57452d157..8e5c9c4b6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,90 +1,49 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# name: "CodeQL" on: push: - branches: [ "master" ] + branches: [ master ] pull_request: - branches: [ "master" ] + branches: [ master ] schedule: - cron: '26 13 * * 4' jobs: analyze: name: Analyze - # Runner size impacts CodeQL analysis time. To learn more, please see: - # - https://gh.io/recommended-hardware-resources-for-running-codeql - # - https://gh.io/supported-runners-and-hardware-resources - # - https://gh.io/using-larger-runners - # Consider using larger runners for possible analysis time improvements. runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} permissions: - # required for all workflows - security-events: write - - # only required for workflows in private repositories actions: read contents: read + security-events: write strategy: fail-fast: false matrix: language: [ 'java-kotlin' ] - # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] - # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both - # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Java - uses: actions/setup-java@v4 - with: - java-version: 17 - distribution: 'temurin' - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + cache: maven + distribution: 'temurin' + java-version: 21 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/coveralls.yaml b/.github/workflows/coveralls.yaml index a898e3793..b4aaaa991 100644 --- a/.github/workflows/coveralls.yaml +++ b/.github/workflows/coveralls.yaml @@ -2,6 +2,8 @@ name: Coveralls on: [push, pull_request] +permissions: read-all + jobs: build: if: github.repository_owner == 'mybatis' @@ -11,8 +13,9 @@ jobs: - name: Set up JDK uses: actions/setup-java@v4 with: + cache: maven + distribution: temurin java-version: 21 - distribution: zulu - name: Report Coverage to Coveralls for Pull Requests if: github.event_name == 'pull_request' run: ./mvnw -B -V test jacoco:report coveralls:report -q -Dlicense.skip=true -DrepoToken=$GITHUB_TOKEN -DserviceName=github -DpullRequest=$PR_NUMBER --no-transfer-progress diff --git a/.github/workflows/site.yaml b/.github/workflows/site.yaml index 746afc0ec..de1babe41 100644 --- a/.github/workflows/site.yaml +++ b/.github/workflows/site.yaml @@ -5,6 +5,9 @@ on: branches: - site +permissions: + contents: write + jobs: build: if: github.repository_owner == 'mybatis' && ! contains(toJSON(github.event.head_commit.message), '[maven-release-plugin]') @@ -14,21 +17,16 @@ jobs: - name: Set up JDK uses: actions/setup-java@v4 with: + cache: maven + distribution: temurin java-version: 21 - distribution: zulu - - uses: webfactory/ssh-agent@master - with: - ssh-private-key: ${{ secrets.DEPLOY_KEY }} - name: Build site - run: ./mvnw site site:stage -DskipTests -B -V --no-transfer-progress -Dlicense.skip=true + run: ./mvnw site site:stage -DskipTests -Dlicense.skip=true -B -V --no-transfer-progress --settings ./.mvn/settings.xml env: - CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NVD_API_KEY: ${{ secrets.NVD_API_KEY }} - name: Deploy Site to gh-pages - uses: JamesIves/github-pages-deploy-action@v4.6.1 + uses: JamesIves/github-pages-deploy-action@v4 with: - ssh-key: true branch: gh-pages folder: target/staging - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sonar.yaml b/.github/workflows/sonar.yaml index e4bf41ac8..33c70609c 100644 --- a/.github/workflows/sonar.yaml +++ b/.github/workflows/sonar.yaml @@ -5,6 +5,8 @@ on: branches: - master +permissions: read-all + jobs: build: if: github.repository_owner == 'mybatis' @@ -17,10 +19,11 @@ jobs: - name: Set up JDK uses: actions/setup-java@v4 with: + cache: maven + distribution: temurin java-version: 21 - distribution: zulu - name: Analyze with SonarCloud - run: ./mvnw verify jacoco:report sonar:sonar -B -Dsonar.projectKey=mybatis_mybatis-dynamic-sql -Dsonar.organization=mybatis -Dsonar.host.url=https://sonarcloud.io -Dsonar.token=$SONAR_TOKEN -Dlicense.skip=true --no-transfer-progress + run: ./mvnw verify jacoco:report sonar:sonar -B -V -Dsonar.projectKey=mybatis_mybatis-dynamic-sql -Dsonar.organization=mybatis -Dsonar.host.url=https://sonarcloud.io -Dsonar.token=$SONAR_TOKEN -Dlicense.skip=true --no-transfer-progress env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/sonatype.yaml b/.github/workflows/sonatype.yaml index 72086bc1d..c922f382c 100644 --- a/.github/workflows/sonatype.yaml +++ b/.github/workflows/sonatype.yaml @@ -5,6 +5,8 @@ on: branches: - master +permissions: read-all + jobs: build: if: github.repository_owner == 'mybatis' && ! contains(toJSON(github.event.head_commit.message), '[maven-release-plugin]') @@ -14,8 +16,9 @@ jobs: - name: Set up JDK uses: actions/setup-java@v4 with: + cache: maven + distribution: temurin java-version: 21 - distribution: zulu - name: Deploy to Sonatype run: ./mvnw deploy -DskipTests -B -V --no-transfer-progress --settings ./.mvn/settings.xml -Dlicense.skip=true env: diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index 2f7f4c3a1..fa0b7e819 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -1,7 +1,7 @@ - ossrh + central ${env.CI_DEPLOY_USERNAME} ${env.CI_DEPLOY_PASSWORD} @@ -39,7 +39,6 @@ github - ${env.CI_DEPLOY_USERNAME} ${env.GITHUB_TOKEN} diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java index f6cb0fa0b..bdf0ddfa6 100644 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -30,7 +30,7 @@ import java.util.concurrent.ThreadLocalRandom; public final class MavenWrapperDownloader { - private static final String WRAPPER_VERSION = "3.3.1"; + private static final String WRAPPER_VERSION = "3.3.2"; private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE")); diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 34d543889..0d35f6dbb 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -14,6 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -wrapperVersion=3.3.1 -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.1/maven-wrapper-3.3.1.jar +wrapperVersion=3.3.2 +distributionType=source +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.10/apache-maven-3.9.10-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar diff --git a/CHANGELOG.md b/CHANGELOG.md index d80d14ee3..c65f2c09c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,112 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are available on the GitHub milestone pages. +## Release 2.0.0 - Unreleased + +Release 2.0.0 is a significant milestone for the library. We have moved to Java 17 as the minimum version supported. If +you are unable to move to this version of Java then the releases in the 1.x line can be used with Java 8. + +In addition, we have taken the opportunity to make changes to the library that may break existing code. We have +worked to make these changes as minimal as possible. + +### Potentially Breaking Changes: + +- If you use this library with MyBatis' Spring Batch integration, you will need to make changes as we have + refactored that support to be more flexible. Please see the + [Spring Batch](https://mybatis.org/mybatis-dynamic-sql/docs/springBatch.html) documentation page to see the new usage + details. +- If you have created any custom implementations of `SortSpecification`, you will need to update those + implementations due to a new rendering strategy for ORDER BY phrases. The old methods `isDescending` and `orderByName` + are removed in favor of a new method `renderForOrderBy` +- If you have implemented any custom functions, you will likely need to make changes. The supplied base classes now + hold an instance of `BasicColumn` rather than `BindableColumn`. This change was made to make the functions more + useful in variety of circumstances. If you follow the patterns shown on the + [Extending the Library](https://mybatis.org/mybatis-dynamic-sql/docs/extending.html) page, the change should be + limited to changing the private constructor to accept `BasicColumn` rather than `BindableColumn`. + +### Adoption of JSpecify (https://jspecify.dev/) + +Following the lead of many other projects (including The Spring Framework), we have adopted JSpecify to fully +document the null handling properties of this library. JSpecify is now a runtime dependency - as is +recommended practice with JSpecify. + +This change should not impact the running of any existing code, but depending on your usage you may see new IDE or +tooling warnings based on the declared nullability of methods in the library. You may choose to ignore the +warnings and things should continue to function. Of course, we recommend that you do not ignore these warnings! + +In general, the library does not expect that you will pass a null value into any method. There are two exceptions to +this rule: + +1. Some builder methods will accept a null value if the target object will properly handle null values through the + use of java.util.Optional +2. Methods with names that include "WhenPresent" will properly handle null parameters + (for example, "isEqualToWhenPresent") + +As you might expect, standardizing null handling revealed some issues in the library that may impact you. + +Fixing compiler warnings and errors: + +1. We expect that most of the warnings you encounter will be related to passing null values into a where condition. + These warnings should be resolved by changing your code to use the "WhenPresent" versions of methods as those + methods handle null values in a predictable way. +2. Java Classes that extend "AliasableSqlTable" will likely see IDE warnings about non-null type arguments. This can be + resolved by adding a "@NullMarked" annotation to the class or package. This issue does not affect Kotlin classes + that extend "AliasableSqlTable". +3. Similarly, if you have coded any functions for use with your queries, you can resolve most IDE warnings by adding + the "@NullMarked" annotation. +4. If you have coded any Kotlin functions that operate on a generic Java class from the library, then you should + change the type parameter definition to specify a non-nullable type. For example... + + ```kotlin + import org.mybatis.dynamic.sql.SqlColumn + + fun foo(column: SqlColumn) { + } + ``` + + Should change to: + + ```kotlin + import org.mybatis.dynamic.sql.SqlColumn + + fun foo(column: SqlColumn) { + } + ``` + +Runtime behavior changes: + +1. The where conditions (isEqualTo, isLessThan, etc.) can be filtered and result in an "empty" condition - + similar to java.util.Optional. Previously, calling a "value" method of the condition would return null. Now + those methods will throw "NoSuchElementException". This should not impact you in normal usage. +2. We have updated the "ParameterTypeConverter" used in Spring applications to maintain compatibility with Spring's + "Converter" interface. The primary change is that the framework will no longer call a type converter if the + input value is null. This should simplify the coding of converters and foster reuse with existing Spring converters. +3. The "map" method on the "WhenPresent" conditions will accept a mapper function that may return a null value. The + conditions will now properly handle this outcome + +### Other important changes: + +- The library now requires Java 17 +- Deprecated code from prior releases is removed +- We now allow CASE expressions in ORDER BY Clauses +- The "In" conditions will now throw `InvalidSqlException` during rendering if the list of values is empty. Previously + an empty In condition would render as invalid SQL and would usually cause a runtime exception from the database. + With this change, the exception thrown is more predictable and the error is caught before sending the SQL to the + database. +- All the paging methods (limit, offset, fetchFirst) now have "WhenPresent" variations that will drop the phrase from + rendering if a null value is passed in +- The JOIN syntax is updated and now allows full boolean expressions like a WHERE clause. The prior JOIN syntax + is deprecated and will be removed in a future release. +- Add support for locking options in select statements (for update, for share, etc.) This is not an abstraction of + these concepts for different databases it simply adds known clauses to a generated SQL statement. You should always + test to make sure these functions work in your target database. Currently, we support, and test, the options + supported by PostgreSQL. +- Rendering for all the conditions (isEqualTo, etc.) has changed. This should be transparent to most users unless you + have coded a direct implementation of `VisitableCondition`. The change makes it easier to code custom conditions that + are not supported by the library out of the box. The statement renderers now call methods `renderCondition` and + `renderLeftColumn` that you can override to implement any rendering you need. In addition, we've made `filter` and + `map` support optional if you implement custom conditions + ## Release 1.5.2 - June 3, 2024 This is a small maintenance release with the following changes: diff --git a/README.md b/README.md index 12dac83b3..3a536a47a 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,10 @@ The library test cases provide several complete examples of using the library in | Kotlin | Spring JDBC | Example using Kotlin utility classes for Spring JDBC Template | [../examples/kotlin/spring/canonical](src/test/kotlin/examples/kotlin/spring/canonical) | -## Requirements +## Requirements and Dependencies -The library has no dependencies. Java 8 or higher is required. +Version 2.x requires Java 17 and has a required runtime dependency on JSpecify (https://jspecify.dev/). Version 1.x +requires Java 8 and has no required runtime dependencies. + +All versions have support for MyBatis3, Spring Framework, and Kotlin - all those dependencies are optional. The library +should work in those environments as the dependencies will be made available at runtime. diff --git a/checkstyle-override.xml b/checkstyle-override.xml index 1e54f5578..17355d976 100644 --- a/checkstyle-override.xml +++ b/checkstyle-override.xml @@ -1,7 +1,7 @@ + http://localhost:9000 official - 1.19.8 + 1.21.3 org.mybatis.dynamic.sql.*;version=${project.version};-noimport:=true - 1717449261 + 1717449335 + + org.jspecify + jspecify + 1.0.0 + org.jetbrains.kotlin kotlin-stdlib-jdk8 @@ -92,14 +103,14 @@ org.springframework spring-jdbc - 6.1.8 + 6.2.8 provided true org.mybatis mybatis - 3.5.16 + 3.5.19 provided true @@ -125,19 +136,19 @@ org.assertj assertj-core - 3.26.0 + 3.27.3 test org.mybatis mybatis-spring - 3.0.3 + 3.0.5 test org.hsqldb hsqldb - 2.7.3 + 2.7.4 test @@ -161,33 +172,25 @@ ch.qos.logback logback-classic - 1.5.6 + 1.5.18 test - - - org.hamcrest - hamcrest - 2.2 - test - - org.testcontainers - postgresql + junit-jupiter ${test.containers.version} test org.testcontainers - junit-jupiter + postgresql ${test.containers.version} test org.postgresql postgresql - 42.7.3 + 42.7.7 test @@ -199,7 +202,19 @@ org.mariadb.jdbc mariadb-java-client - 3.4.0 + 3.5.4 + test + + + org.testcontainers + mysql + ${test.containers.version} + test + + + com.mysql + mysql-connector-j + 9.3.0 test @@ -329,7 +344,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.6.0 + 3.6.1 diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractColumnComparisonCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractColumnComparisonCondition.java index 4e419d968..65e45aafe 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractColumnComparisonCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractColumnComparisonCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,7 +15,12 @@ */ package org.mybatis.dynamic.sql; -public abstract class AbstractColumnComparisonCondition implements VisitableCondition { +import static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore; + +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; + +public abstract class AbstractColumnComparisonCondition implements RenderableCondition { protected final BasicColumn rightColumn; @@ -23,14 +28,10 @@ protected AbstractColumnComparisonCondition(BasicColumn rightColumn) { this.rightColumn = rightColumn; } - public BasicColumn rightColumn() { - return rightColumn; - } + public abstract String operator(); @Override - public R accept(ConditionVisitor visitor) { - return visitor.visit(this); + public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn leftColumn) { + return rightColumn.render(renderingContext).mapFragment(f -> operator() + spaceBefore(f)); } - - public abstract String operator(); } diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java index 104859de8..e178c6bf3 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -23,7 +23,13 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -public abstract class AbstractListValueCondition implements VisitableCondition { +import org.jspecify.annotations.NonNull; +import org.mybatis.dynamic.sql.render.RenderedParameterInfo; +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; +import org.mybatis.dynamic.sql.util.FragmentCollector; + +public abstract class AbstractListValueCondition implements RenderableCondition { protected final Collection values; protected AbstractListValueCondition(Collection values) { @@ -39,19 +45,14 @@ public boolean isEmpty() { return values.isEmpty(); } - @Override - public R accept(ConditionVisitor visitor) { - return visitor.visit(this); - } - private Collection applyMapper(Function mapper) { Objects.requireNonNull(mapper); - return values.stream().map(mapper).collect(Collectors.toList()); + return values().map(mapper).collect(Collectors.toList()); } private Collection applyFilter(Predicate predicate) { Objects.requireNonNull(predicate); - return values.stream().filter(predicate).collect(Collectors.toList()); + return values().filter(predicate).toList(); } protected > S filterSupport(Predicate predicate, @@ -73,15 +74,71 @@ protected > S mapSupport(Function leftColumn) { + return values().map(v -> toFragmentAndParameters(v, renderingContext, leftColumn)) + .collect(FragmentCollector.collect()) + .toFragmentAndParameters(Collectors.joining(",", //$NON-NLS-1$ + operator() + " (", ")")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + private FragmentAndParameters toFragmentAndParameters(T value, RenderingContext renderingContext, + BindableColumn leftColumn) { + RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(leftColumn); + return FragmentAndParameters.withFragment(parameterInfo.renderedPlaceHolder()) + .withParameter(parameterInfo.parameterMapKey(), leftColumn.convertParameterType(value)) + .build(); + } + /** - * If not empty, apply the predicate to each value in the list and return a new condition with the filtered values. - * Else returns an empty condition (this). + * Conditions may implement Filterable to add optionality to rendering. + * + *

If a condition is Filterable, then a user may add a filter to the usage of the condition that makes a decision + * whether to render the condition at runtime. Conditions that fail the filter will be dropped from the + * rendered SQL. * - * @param predicate predicate applied to the values, if not empty + *

Implementations of Filterable may call + * {@link AbstractListValueCondition#filterSupport(Predicate, Function, AbstractListValueCondition, Supplier)} as + * a common implementation of the filtering algorithm. * - * @return a new condition with filtered values if renderable, otherwise an empty condition + * @param the Java type related to the database column type */ - public abstract AbstractListValueCondition filter(Predicate predicate); + public interface Filterable { + /** + * If renderable and the value matches the predicate, returns this condition. Else returns a condition + * that will not render. + * + * @param predicate predicate applied to the value, if renderable + * @return this condition if renderable and the value matches the predicate, otherwise a condition + * that will not render. + */ + AbstractListValueCondition filter(Predicate predicate); + } - public abstract String operator(); + /** + * Conditions may implement Mappable to alter condition values or types during rendering. + * + *

If a condition is Mappable, then a user may add a mapper to the usage of the condition that can alter the + * values of a condition, or change that datatype. + * + *

Implementations of Mappable may call + * {@link AbstractListValueCondition#mapSupport(Function, Function, Supplier)} as + * a common implementation of the mapping algorithm. + * + * @param the Java type related to the database column type + */ + public interface Mappable { + /** + * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a + * condition that will not render (this). + * + * @param mapper a mapping function to apply to the value, if renderable + * @param type of the new condition + * @return a new condition with the result of applying the mapper to the value of this condition, + * if renderable, otherwise a condition that will not render. + */ + AbstractListValueCondition map(Function mapper); + } } diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java index d6b1c384b..71daa7763 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,12 +18,10 @@ import java.util.function.BooleanSupplier; import java.util.function.Supplier; -public abstract class AbstractNoValueCondition implements VisitableCondition { +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; - @Override - public R accept(ConditionVisitor visitor) { - return visitor.visit(this); - } +public abstract class AbstractNoValueCondition implements RenderableCondition { protected > S filterSupport(BooleanSupplier booleanSupplier, Supplier emptySupplier, S self) { @@ -35,4 +33,36 @@ protected > S filterSupport(BooleanSupplie } public abstract String operator(); + + @Override + public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn leftColumn) { + return FragmentAndParameters.fromFragment(operator()); + } + + /** + * Conditions may implement Filterable to add optionality to rendering. + * + *

If a condition is Filterable, then a user may add a filter to the usage of the condition that makes a decision + * whether to render the condition at runtime. Conditions that fail the filter will be dropped from the + * rendered SQL. + * + *

Implementations of Filterable may call + * {@link AbstractNoValueCondition#filterSupport(BooleanSupplier, Supplier, AbstractNoValueCondition)} as + * a common implementation of the filtering algorithm. + */ + public interface Filterable { + /** + * If renderable and the supplier returns true, returns this condition. Else returns a condition that will not + * render. + * + * @param booleanSupplier + * function that specifies whether the condition should render + * @param + * condition type - not used except for compilation compliance + * + * @return this condition if renderable and the supplier returns true, otherwise a condition that will not + * render. + */ + AbstractNoValueCondition filter(BooleanSupplier booleanSupplier); + } } diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java index 1e70adb31..eb56eef39 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,11 +15,18 @@ */ package org.mybatis.dynamic.sql; +import static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore; + import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; -public abstract class AbstractSingleValueCondition implements VisitableCondition { +import org.jspecify.annotations.NonNull; +import org.mybatis.dynamic.sql.render.RenderedParameterInfo; +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; + +public abstract class AbstractSingleValueCondition implements RenderableCondition { protected final T value; protected AbstractSingleValueCondition(T value) { @@ -30,11 +37,6 @@ public T value() { return value; } - @Override - public R accept(ConditionVisitor visitor) { - return visitor.visit(this); - } - protected > S filterSupport(Predicate predicate, Supplier emptySupplier, S self) { if (isEmpty()) { @@ -53,15 +55,65 @@ protected > S mapSupport(Function leftColumn) { + RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(leftColumn); + String finalFragment = operator() + spaceBefore(parameterInfo.renderedPlaceHolder()); + + return FragmentAndParameters.withFragment(finalFragment) + .withParameter(parameterInfo.parameterMapKey(), leftColumn.convertParameterType(value())) + .build(); + } + /** - * If renderable and the value matches the predicate, returns this condition. Else returns a condition - * that will not render. + * Conditions may implement Filterable to add optionality to rendering. * - * @param predicate predicate applied to the value, if renderable - * @return this condition if renderable and the value matches the predicate, otherwise a condition - * that will not render. + *

If a condition is Filterable, then a user may add a filter to the usage of the condition that makes a decision + * whether to render the condition at runtime. Conditions that fail the filter will be dropped from the + * rendered SQL. + * + *

Implementations of Filterable may call + * {@link AbstractSingleValueCondition#filterSupport(Predicate, Supplier, AbstractSingleValueCondition)} as + * a common implementation of the filtering algorithm. + * + * @param the Java type related to the database column type */ - public abstract AbstractSingleValueCondition filter(Predicate predicate); + public interface Filterable { + /** + * If renderable and the value matches the predicate, returns this condition. Else returns a condition + * that will not render. + * + * @param predicate predicate applied to the value, if renderable + * @return this condition if renderable and the value matches the predicate, otherwise a condition + * that will not render. + */ + AbstractSingleValueCondition filter(Predicate predicate); + } - public abstract String operator(); + /** + * Conditions may implement Mappable to alter condition values or types during rendering. + * + *

If a condition is Mappable, then a user may add a mapper to the usage of the condition that can alter the + * values of a condition, or change that datatype. + * + *

Implementations of Mappable may call + * {@link AbstractSingleValueCondition#mapSupport(Function, Function, Supplier)} as + * a common implementation of the mapping algorithm. + * + * @param the Java type related to the database column type + */ + public interface Mappable { + /** + * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a + * condition that will not render (this). + * + * @param mapper a mapping function to apply to the value, if renderable + * @param type of the new condition + * @return a new condition with the result of applying the mapper to the value of this condition, + * if renderable, otherwise a condition that will not render. + */ + AbstractSingleValueCondition map(Function mapper); + } } diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractSubselectCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractSubselectCondition.java index f17e29066..dcfbd4b3c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractSubselectCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractSubselectCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,24 +15,28 @@ */ package org.mybatis.dynamic.sql; +import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.select.SelectModel; +import org.mybatis.dynamic.sql.select.render.SubQueryRenderer; import org.mybatis.dynamic.sql.util.Buildable; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; -public abstract class AbstractSubselectCondition implements VisitableCondition { +public abstract class AbstractSubselectCondition implements RenderableCondition { private final SelectModel selectModel; protected AbstractSubselectCondition(Buildable selectModelBuilder) { this.selectModel = selectModelBuilder.build(); } - public SelectModel selectModel() { - return selectModel; - } + public abstract String operator(); @Override - public R accept(ConditionVisitor visitor) { - return visitor.visit(this); + public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn leftColumn) { + return SubQueryRenderer.withSelectModel(selectModel) + .withRenderingContext(renderingContext) + .withPrefix(operator() + " (") //$NON-NLS-1$ + .withSuffix(")") //$NON-NLS-1$ + .build() + .render(); } - - public abstract String operator(); } diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java index ee8b3c577..d409ffbb8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,13 +15,20 @@ */ package org.mybatis.dynamic.sql; +import static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore; + import java.util.function.BiFunction; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; -public abstract class AbstractTwoValueCondition implements VisitableCondition { +import org.jspecify.annotations.NonNull; +import org.mybatis.dynamic.sql.render.RenderedParameterInfo; +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; + +public abstract class AbstractTwoValueCondition implements RenderableCondition { protected final T value1; protected final T value2; @@ -38,11 +45,6 @@ public T value2() { return value2; } - @Override - public R accept(ConditionVisitor visitor) { - return visitor.visit(this); - } - protected > S filterSupport(BiPredicate predicate, Supplier emptySupplier, S self) { if (isEmpty()) { @@ -66,28 +68,98 @@ protected > S mapSupport(Function leftColumn) { + RenderedParameterInfo parameterInfo1 = renderingContext.calculateParameterInfo(leftColumn); + RenderedParameterInfo parameterInfo2 = renderingContext.calculateParameterInfo(leftColumn); + + String finalFragment = operator1() + + spaceBefore(parameterInfo1.renderedPlaceHolder()) + + spaceBefore(operator2()) + + spaceBefore(parameterInfo2.renderedPlaceHolder()); + + return FragmentAndParameters.withFragment(finalFragment) + .withParameter(parameterInfo1.parameterMapKey(), leftColumn.convertParameterType(value1())) + .withParameter(parameterInfo2.parameterMapKey(), leftColumn.convertParameterType(value2())) + .build(); + } + /** - * If renderable and the values match the predicate, returns this condition. Else returns a condition - * that will not render. + * Conditions may implement Filterable to add optionality to rendering. + * + *

If a condition is Filterable, then a user may add a filter to the usage of the condition that makes a decision + * whether to render the condition at runtime. Conditions that fail the filter will be dropped from the + * rendered SQL. * - * @param predicate predicate applied to the values, if renderable - * @return this condition if renderable and the values match the predicate, otherwise a condition - * that will not render. + *

Implementations of Filterable may call + * {@link AbstractTwoValueCondition#filterSupport(Predicate, Supplier, AbstractTwoValueCondition)} + * or {@link AbstractTwoValueCondition#filterSupport(BiPredicate, Supplier, AbstractTwoValueCondition)} as + * a common implementation of the filtering algorithm. + * + * @param the Java type related to the database column type */ - public abstract AbstractTwoValueCondition filter(BiPredicate predicate); + public interface Filterable { + /** + * If renderable and the values match the predicate, returns this condition. Else returns a condition + * that will not render. + * + * @param predicate predicate applied to the values, if renderable + * @return this condition if renderable and the values match the predicate, otherwise a condition + * that will not render. + */ + AbstractTwoValueCondition filter(BiPredicate predicate); + + /** + * If renderable and both values match the predicate, returns this condition. Else returns a condition + * that will not render. This function implements a short-circuiting test. If the + * first value does not match the predicate, then the second value will not be tested. + * + * @param predicate predicate applied to both values, if renderable + * @return this condition if renderable and the values match the predicate, otherwise a condition + * that will not render. + */ + AbstractTwoValueCondition filter(Predicate predicate); + } /** - * If renderable and both values match the predicate, returns this condition. Else returns a condition - * that will not render. This function implements a short-circuiting test. If the - * first value does not match the predicate, then the second value will not be tested. + * Conditions may implement Mappable to alter condition values or types during rendering. * - * @param predicate predicate applied to both values, if renderable - * @return this condition if renderable and the values match the predicate, otherwise a condition - * that will not render. + *

If a condition is Mappable, then a user may add a mapper to the usage of the condition that can alter the + * values of a condition, or change that datatype. + * + *

Implementations of Mappable may call + * {@link AbstractTwoValueCondition#mapSupport(Function, Function, BiFunction, Supplier)} as + * a common implementation of the mapping algorithm. + * + * @param the Java type related to the database column type */ - public abstract AbstractTwoValueCondition filter(Predicate predicate); - - public abstract String operator1(); + public interface Mappable { + /** + * If renderable, apply the mappings to the values and return a new condition with the new values. Else return a + * condition that will not render (this). + * + * @param mapper1 a mapping function to apply to the first value, if renderable + * @param mapper2 a mapping function to apply to the second value, if renderable + * @param type of the new condition + * @return a new condition with the result of applying the mappers to the values of this condition, + * if renderable, otherwise a condition that will not render. + */ + AbstractTwoValueCondition map(Function mapper1, + Function mapper2); - public abstract String operator2(); + /** + * If renderable, apply the mapping to both values and return a new condition with the new values. Else return a + * condition that will not render (this). + * + * @param mapper a mapping function to apply to both values, if renderable + * @param type of the new condition + * @return a new condition with the result of applying the mappers to the values of this condition, + * if renderable, otherwise a condition that will not render. + */ + AbstractTwoValueCondition map(Function mapper); + } } diff --git a/src/main/java/org/mybatis/dynamic/sql/AliasableSqlTable.java b/src/main/java/org/mybatis/dynamic/sql/AliasableSqlTable.java index 4e6c32386..915b6a561 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AliasableSqlTable.java +++ b/src/main/java/org/mybatis/dynamic/sql/AliasableSqlTable.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,9 +19,11 @@ import java.util.Optional; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + public abstract class AliasableSqlTable> extends SqlTable { - private String tableAlias; + private @Nullable String tableAlias; private final Supplier constructor; protected AliasableSqlTable(String tableName, Supplier constructor) { @@ -32,7 +34,7 @@ protected AliasableSqlTable(String tableName, Supplier constructor) { public T withAlias(String alias) { T newTable = constructor.get(); ((AliasableSqlTable) newTable).tableAlias = alias; - newTable.nameSupplier = nameSupplier; + newTable.tableName = tableName; return newTable; } @@ -48,7 +50,7 @@ public T withName(String name) { Objects.requireNonNull(name); T newTable = constructor.get(); ((AliasableSqlTable) newTable).tableAlias = tableAlias; - newTable.nameSupplier = () -> name; + newTable.tableName = name; return newTable; } diff --git a/src/main/java/org/mybatis/dynamic/sql/AndOrCriteriaGroup.java b/src/main/java/org/mybatis/dynamic/sql/AndOrCriteriaGroup.java index a85861904..ff630a036 100644 --- a/src/main/java/org/mybatis/dynamic/sql/AndOrCriteriaGroup.java +++ b/src/main/java/org/mybatis/dynamic/sql/AndOrCriteriaGroup.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,6 +21,8 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; + /** * This class represents a criteria group with either an AND or an OR connector. * This class is intentionally NOT derived from SqlCriterion because we only want it to be @@ -32,7 +34,7 @@ */ public class AndOrCriteriaGroup { private final String connector; - private final SqlCriterion initialCriterion; + private final @Nullable SqlCriterion initialCriterion; private final List subCriteria; private AndOrCriteriaGroup(Builder builder) { @@ -54,8 +56,8 @@ public List subCriteria() { } public static class Builder { - private String connector; - private SqlCriterion initialCriterion; + private @Nullable String connector; + private @Nullable SqlCriterion initialCriterion; private final List subCriteria = new ArrayList<>(); public Builder withConnector(String connector) { @@ -63,7 +65,7 @@ public Builder withConnector(String connector) { return this; } - public Builder withInitialCriterion(SqlCriterion initialCriterion) { + public Builder withInitialCriterion(@Nullable SqlCriterion initialCriterion) { this.initialCriterion = initialCriterion; return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/BasicColumn.java b/src/main/java/org/mybatis/dynamic/sql/BasicColumn.java index aaee827b1..2573b4529 100644 --- a/src/main/java/org/mybatis/dynamic/sql/BasicColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/BasicColumn.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,13 +15,12 @@ */ package org.mybatis.dynamic.sql; +import java.sql.JDBCType; import java.util.Optional; -import org.mybatis.dynamic.sql.exception.DynamicSqlException; import org.mybatis.dynamic.sql.render.RenderingContext; -import org.mybatis.dynamic.sql.render.TableAliasCalculator; +import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.util.FragmentAndParameters; -import org.mybatis.dynamic.sql.util.Messages; /** * Describes attributes of columns that are necessary for rendering if the column is not expected to @@ -59,24 +58,18 @@ public interface BasicColumn { * @return a rendered SQL fragment and, optionally, parameters associated with the fragment * @since 1.5.1 */ - default FragmentAndParameters render(RenderingContext renderingContext) { - // the default implementation ensures compatibility with prior releases. When the - // deprecated renderWithTableAlias method is removed, this function can become purely abstract. - // Also remove the method tableAliasCalculator() from RenderingContext. - return FragmentAndParameters.fromFragment(renderWithTableAlias(renderingContext.tableAliasCalculator())); + FragmentAndParameters render(RenderingContext renderingContext); + + default Optional jdbcType() { + return Optional.empty(); } - /** - * Returns the name of the item aliased with a table name if appropriate. - * For example, "a.foo". This is appropriate for where clauses and order by clauses. - * - * @param tableAliasCalculator the table alias calculator for the current renderer - * @return the item name with the table alias applied - * @deprecated Please replace this method by overriding the more general "render" method - */ - @Deprecated - default String renderWithTableAlias(TableAliasCalculator tableAliasCalculator) { - throw new DynamicSqlException(Messages.getString("ERROR.36")); //$NON-NLS-1$ + default Optional typeHandler() { + return Optional.empty(); + } + + default Optional renderingStrategy() { + return Optional.empty(); } /** diff --git a/src/main/java/org/mybatis/dynamic/sql/BindableColumn.java b/src/main/java/org/mybatis/dynamic/sql/BindableColumn.java index 274b52e25..0fd90b7c8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/BindableColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/BindableColumn.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,14 +15,13 @@ */ package org.mybatis.dynamic.sql; -import java.sql.JDBCType; import java.util.Optional; -import org.mybatis.dynamic.sql.render.RenderingStrategy; +import org.jspecify.annotations.Nullable; /** - * Describes additional attributes of columns that are necessary for binding the column as a JDBC parameter. - * Columns in where clauses are typically bound. + * Describes a column with a known data type. The type is only used by the compiler to assure type safety + * when building clauses with conditions. * * @author Jeff Butler * @@ -37,19 +36,7 @@ public interface BindableColumn extends BasicColumn { @Override BindableColumn as(String alias); - default Optional jdbcType() { - return Optional.empty(); - } - - default Optional typeHandler() { - return Optional.empty(); - } - - default Optional renderingStrategy() { - return Optional.empty(); - } - - default Object convertParameterType(T value) { + default @Nullable Object convertParameterType(T value) { return value; } diff --git a/src/main/java/org/mybatis/dynamic/sql/BoundValue.java b/src/main/java/org/mybatis/dynamic/sql/BoundValue.java index 7ac0bd26a..5151a5dad 100644 --- a/src/main/java/org/mybatis/dynamic/sql/BoundValue.java +++ b/src/main/java/org/mybatis/dynamic/sql/BoundValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/ColumnAndConditionCriterion.java b/src/main/java/org/mybatis/dynamic/sql/ColumnAndConditionCriterion.java index 9c0f47151..053c18f64 100644 --- a/src/main/java/org/mybatis/dynamic/sql/ColumnAndConditionCriterion.java +++ b/src/main/java/org/mybatis/dynamic/sql/ColumnAndConditionCriterion.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,9 +17,11 @@ import java.util.Objects; +import org.jspecify.annotations.Nullable; + public class ColumnAndConditionCriterion extends SqlCriterion { private final BindableColumn column; - private final VisitableCondition condition; + private final RenderableCondition condition; private ColumnAndConditionCriterion(Builder builder) { super(builder); @@ -31,7 +33,7 @@ public BindableColumn column() { return column; } - public VisitableCondition condition() { + public RenderableCondition condition() { return condition; } @@ -45,15 +47,15 @@ public static Builder withColumn(BindableColumn column) { } public static class Builder extends AbstractBuilder> { - private BindableColumn column; - private VisitableCondition condition; + private @Nullable BindableColumn column; + private @Nullable RenderableCondition condition; public Builder withColumn(BindableColumn column) { this.column = column; return this; } - public Builder withCondition(VisitableCondition condition) { + public Builder withCondition(RenderableCondition condition) { this.condition = condition; return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/ConditionVisitor.java b/src/main/java/org/mybatis/dynamic/sql/ConditionVisitor.java deleted file mode 100644 index c8e95a9bd..000000000 --- a/src/main/java/org/mybatis/dynamic/sql/ConditionVisitor.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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; - -public interface ConditionVisitor { - R visit(AbstractListValueCondition condition); - - R visit(AbstractNoValueCondition condition); - - R visit(AbstractSingleValueCondition condition); - - R visit(AbstractTwoValueCondition condition); - - R visit(AbstractSubselectCondition condition); - - R visit(AbstractColumnComparisonCondition condition); -} diff --git a/src/main/java/org/mybatis/dynamic/sql/Constant.java b/src/main/java/org/mybatis/dynamic/sql/Constant.java index d424f6391..90547765f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/Constant.java +++ b/src/main/java/org/mybatis/dynamic/sql/Constant.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,19 +18,20 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.FragmentAndParameters; public class Constant implements BindableColumn { - private final String alias; + private final @Nullable String alias; private final String value; private Constant(String value) { this(value, null); } - private Constant(String value, String alias) { + private Constant(String value, @Nullable String alias) { this.value = Objects.requireNonNull(value); this.alias = alias; } diff --git a/src/main/java/org/mybatis/dynamic/sql/CriteriaGroup.java b/src/main/java/org/mybatis/dynamic/sql/CriteriaGroup.java index 931b55722..476cb6b3f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/CriteriaGroup.java +++ b/src/main/java/org/mybatis/dynamic/sql/CriteriaGroup.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,6 +17,8 @@ import java.util.Optional; +import org.jspecify.annotations.Nullable; + /** * This class represents a criteria group without an AND or an OR connector. This is useful * in situations where the initial SqlCriterion in a list should be further grouped @@ -27,7 +29,7 @@ * @since 1.4.0 */ public class CriteriaGroup extends SqlCriterion { - private final SqlCriterion initialCriterion; + private final @Nullable SqlCriterion initialCriterion; protected CriteriaGroup(AbstractGroupBuilder builder) { super(builder); @@ -44,9 +46,9 @@ public R accept(SqlCriterionVisitor visitor) { } public abstract static class AbstractGroupBuilder> extends AbstractBuilder { - private SqlCriterion initialCriterion; + private @Nullable SqlCriterion initialCriterion; - public T withInitialCriterion(SqlCriterion initialCriterion) { + public T withInitialCriterion(@Nullable SqlCriterion initialCriterion) { this.initialCriterion = initialCriterion; return getThis(); } diff --git a/src/main/java/org/mybatis/dynamic/sql/DerivedColumn.java b/src/main/java/org/mybatis/dynamic/sql/DerivedColumn.java index 73ca62526..df19b8d6b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/DerivedColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/DerivedColumn.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,6 +19,7 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.FragmentAndParameters; @@ -34,10 +35,10 @@ */ public class DerivedColumn implements BindableColumn { private final String name; - private final String tableQualifier; - private final String columnAlias; - private final JDBCType jdbcType; - private final String typeHandler; + private final @Nullable String tableQualifier; + private final @Nullable String columnAlias; + private final @Nullable JDBCType jdbcType; + private final @Nullable String typeHandler; protected DerivedColumn(Builder builder) { this.name = Objects.requireNonNull(builder.name); @@ -93,18 +94,18 @@ public static DerivedColumn of(String name, String tableQualifier) { } public static class Builder { - private String name; - private String tableQualifier; - private String columnAlias; - private JDBCType jdbcType; - private String typeHandler; + private @Nullable String name; + private @Nullable String tableQualifier; + private @Nullable String columnAlias; + private @Nullable JDBCType jdbcType; + private @Nullable String typeHandler; public Builder withName(String name) { this.name = name; return this; } - public Builder withTableQualifier(String tableQualifier) { + public Builder withTableQualifier(@Nullable String tableQualifier) { this.tableQualifier = tableQualifier; return this; } @@ -114,12 +115,12 @@ public Builder withColumnAlias(String columnAlias) { return this; } - public Builder withJdbcType(JDBCType jdbcType) { + public Builder withJdbcType(@Nullable JDBCType jdbcType) { this.jdbcType = jdbcType; return this; } - public Builder withTypeHandler(String typeHandler) { + public Builder withTypeHandler(@Nullable String typeHandler) { this.typeHandler = typeHandler; return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/ExistsCriterion.java b/src/main/java/org/mybatis/dynamic/sql/ExistsCriterion.java index 711b85ffd..17cecdaa6 100644 --- a/src/main/java/org/mybatis/dynamic/sql/ExistsCriterion.java +++ b/src/main/java/org/mybatis/dynamic/sql/ExistsCriterion.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,6 +17,8 @@ import java.util.Objects; +import org.jspecify.annotations.Nullable; + public class ExistsCriterion extends SqlCriterion { private final ExistsPredicate existsPredicate; @@ -35,7 +37,7 @@ public R accept(SqlCriterionVisitor visitor) { } public static class Builder extends AbstractBuilder { - private ExistsPredicate existsPredicate; + private @Nullable ExistsPredicate existsPredicate; public Builder withExistsPredicate(ExistsPredicate existsPredicate) { this.existsPredicate = existsPredicate; diff --git a/src/main/java/org/mybatis/dynamic/sql/ExistsPredicate.java b/src/main/java/org/mybatis/dynamic/sql/ExistsPredicate.java index 8d68a3ac9..c22a84aa2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/ExistsPredicate.java +++ b/src/main/java/org/mybatis/dynamic/sql/ExistsPredicate.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,7 +17,6 @@ import java.util.Objects; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.util.Buildable; @@ -38,12 +37,10 @@ public Buildable selectModelBuilder() { return selectModelBuilder; } - @NotNull public static ExistsPredicate exists(Buildable selectModelBuilder) { return new ExistsPredicate("exists", selectModelBuilder); //$NON-NLS-1$ } - @NotNull public static ExistsPredicate notExists(Buildable selectModelBuilder) { return new ExistsPredicate("not exists", selectModelBuilder); //$NON-NLS-1$ } diff --git a/src/main/java/org/mybatis/dynamic/sql/NotCriterion.java b/src/main/java/org/mybatis/dynamic/sql/NotCriterion.java index cd9f5e980..f0a0010f8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/NotCriterion.java +++ b/src/main/java/org/mybatis/dynamic/sql/NotCriterion.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/ParameterTypeConverter.java b/src/main/java/org/mybatis/dynamic/sql/ParameterTypeConverter.java index 7df7572b1..4f4856e19 100644 --- a/src/main/java/org/mybatis/dynamic/sql/ParameterTypeConverter.java +++ b/src/main/java/org/mybatis/dynamic/sql/ParameterTypeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,6 +15,8 @@ */ package org.mybatis.dynamic.sql; +import org.jspecify.annotations.Nullable; + /** * A parameter type converter is used to change a parameter value from one type to another * during statement rendering and before the parameter is placed into the parameter map. This can be used @@ -50,5 +52,13 @@ */ @FunctionalInterface public interface ParameterTypeConverter { - T convert(S source); + /** + * Convert the value from one value to another. + * + *

The input value will never be null - the framework will automatically handle nulls. + * + * @param source value as specified in the condition, or after a map operation. Never null. + * @return Possibly null converted value. + */ + @Nullable T convert(S source); } diff --git a/src/main/java/org/mybatis/dynamic/sql/RenderableCondition.java b/src/main/java/org/mybatis/dynamic/sql/RenderableCondition.java new file mode 100644 index 000000000..51dc912e8 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/RenderableCondition.java @@ -0,0 +1,81 @@ +/* + * 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 org.mybatis.dynamic.sql; + +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; + +@FunctionalInterface +public interface RenderableCondition { + /** + * Render a condition - typically a condition in a WHERE clause. + * + *

A rendered condition includes an SQL fragment, and any associated parameters. For example, + * the isEqual condition should be rendered as "= ?" where "?" is a properly formatted + * parameter marker (the parameter marker can be computed from the RenderingContext). + * Note that a rendered condition should NOT include the left side of the phrase - that is rendered + * by the {@link RenderableCondition#renderLeftColumn(RenderingContext, BindableColumn)} method. + * + * @param renderingContext the current rendering context + * @param leftColumn the column related to this condition in a where clause + * @return the rendered condition. Should NOT include the column. + */ + FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn leftColumn); + + /** + * Render the column in a column and condition phrase - typically in a WHERE clause. + * + *

By default, the column will be rendered as the column alias if it exists, or the column name. + * This can be complicated if the column has a table qualifier, or if the "column" is a function or + * part of a CASE expression. Columns know how to render themselves, so we just call their "render" + * methods. + * + * @param renderingContext the current rendering context + * @param leftColumn the column related to this condition in a where clause + * @return the rendered column + */ + default FragmentAndParameters renderLeftColumn(RenderingContext renderingContext, BindableColumn leftColumn) { + return leftColumn.alias() + .map(FragmentAndParameters::fromFragment) + .orElseGet(() -> leftColumn.render(renderingContext)); + } + + /** + * Subclasses can override this to inform the renderer if the condition should not be included + * in the rendered SQL. Typically, conditions will not render if they are empty. + * + * @return true if the condition should render. + */ + default boolean shouldRender(RenderingContext renderingContext) { + return !isEmpty(); + } + + /** + * Subclasses can override this to indicate whether the condition is considered empty. This is primarily used in + * map and filter operations - the map and filter functions will not be applied if the condition is empty. + * + * @return true if the condition is empty. + */ + default boolean isEmpty() { + return false; + } + + /** + * This method will be called during rendering when {@link RenderableCondition#shouldRender(RenderingContext)} + * returns false. + */ + default void renderingSkipped() {} +} diff --git a/src/main/java/org/mybatis/dynamic/sql/SortSpecification.java b/src/main/java/org/mybatis/dynamic/sql/SortSpecification.java index a17557017..13cfdee55 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SortSpecification.java +++ b/src/main/java/org/mybatis/dynamic/sql/SortSpecification.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,6 +15,9 @@ */ package org.mybatis.dynamic.sql; +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; + /** * Defines attributes of columns that are necessary for rendering an order by expression. * @@ -30,17 +33,12 @@ public interface SortSpecification { SortSpecification descending(); /** - * Return the phrase that should be written into a rendered order by clause. This should - * NOT include the "DESC" word for descending sort specifications. - * - * @return the order by phrase - */ - String orderByName(); - - /** - * Return true if the sort order is descending. + * Return a fragment rendered for use in an ORDER BY clause. The fragment should include "DESC" if a + * descending order is desired. * - * @return true if the SortSpecification should render as descending + * @param renderingContext the current rendering context + * @return a rendered fragment and parameters if applicable + * @since 2.0.0 */ - boolean isDescending(); + FragmentAndParameters renderForOrderBy(RenderingContext renderingContext); } diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java index 6516696f6..c8fe5c3ba 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,6 +21,8 @@ import java.util.Objects; import java.util.function.Supplier; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.delete.DeleteDSL; import org.mybatis.dynamic.sql.delete.DeleteModel; import org.mybatis.dynamic.sql.insert.BatchInsertDSL; @@ -56,23 +58,23 @@ import org.mybatis.dynamic.sql.select.function.Substring; import org.mybatis.dynamic.sql.select.function.Subtract; import org.mybatis.dynamic.sql.select.function.Upper; -import org.mybatis.dynamic.sql.select.join.EqualTo; -import org.mybatis.dynamic.sql.select.join.EqualToValue; -import org.mybatis.dynamic.sql.select.join.JoinCondition; -import org.mybatis.dynamic.sql.select.join.JoinCriterion; import org.mybatis.dynamic.sql.update.UpdateDSL; import org.mybatis.dynamic.sql.update.UpdateModel; import org.mybatis.dynamic.sql.util.Buildable; import org.mybatis.dynamic.sql.where.WhereDSL; import org.mybatis.dynamic.sql.where.condition.IsBetween; +import org.mybatis.dynamic.sql.where.condition.IsBetweenWhenPresent; import org.mybatis.dynamic.sql.where.condition.IsEqualTo; import org.mybatis.dynamic.sql.where.condition.IsEqualToColumn; +import org.mybatis.dynamic.sql.where.condition.IsEqualToWhenPresent; import org.mybatis.dynamic.sql.where.condition.IsEqualToWithSubselect; import org.mybatis.dynamic.sql.where.condition.IsGreaterThan; import org.mybatis.dynamic.sql.where.condition.IsGreaterThanColumn; import org.mybatis.dynamic.sql.where.condition.IsGreaterThanOrEqualTo; import org.mybatis.dynamic.sql.where.condition.IsGreaterThanOrEqualToColumn; +import org.mybatis.dynamic.sql.where.condition.IsGreaterThanOrEqualToWhenPresent; import org.mybatis.dynamic.sql.where.condition.IsGreaterThanOrEqualToWithSubselect; +import org.mybatis.dynamic.sql.where.condition.IsGreaterThanWhenPresent; import org.mybatis.dynamic.sql.where.condition.IsGreaterThanWithSubselect; import org.mybatis.dynamic.sql.where.condition.IsIn; import org.mybatis.dynamic.sql.where.condition.IsInCaseInsensitive; @@ -83,13 +85,19 @@ import org.mybatis.dynamic.sql.where.condition.IsLessThanColumn; import org.mybatis.dynamic.sql.where.condition.IsLessThanOrEqualTo; import org.mybatis.dynamic.sql.where.condition.IsLessThanOrEqualToColumn; +import org.mybatis.dynamic.sql.where.condition.IsLessThanOrEqualToWhenPresent; import org.mybatis.dynamic.sql.where.condition.IsLessThanOrEqualToWithSubselect; +import org.mybatis.dynamic.sql.where.condition.IsLessThanWhenPresent; import org.mybatis.dynamic.sql.where.condition.IsLessThanWithSubselect; import org.mybatis.dynamic.sql.where.condition.IsLike; import org.mybatis.dynamic.sql.where.condition.IsLikeCaseInsensitive; +import org.mybatis.dynamic.sql.where.condition.IsLikeCaseInsensitiveWhenPresent; +import org.mybatis.dynamic.sql.where.condition.IsLikeWhenPresent; import org.mybatis.dynamic.sql.where.condition.IsNotBetween; +import org.mybatis.dynamic.sql.where.condition.IsNotBetweenWhenPresent; import org.mybatis.dynamic.sql.where.condition.IsNotEqualTo; import org.mybatis.dynamic.sql.where.condition.IsNotEqualToColumn; +import org.mybatis.dynamic.sql.where.condition.IsNotEqualToWhenPresent; import org.mybatis.dynamic.sql.where.condition.IsNotEqualToWithSubselect; import org.mybatis.dynamic.sql.where.condition.IsNotIn; import org.mybatis.dynamic.sql.where.condition.IsNotInCaseInsensitive; @@ -98,6 +106,8 @@ import org.mybatis.dynamic.sql.where.condition.IsNotInWithSubselect; import org.mybatis.dynamic.sql.where.condition.IsNotLike; import org.mybatis.dynamic.sql.where.condition.IsNotLikeCaseInsensitive; +import org.mybatis.dynamic.sql.where.condition.IsNotLikeCaseInsensitiveWhenPresent; +import org.mybatis.dynamic.sql.where.condition.IsNotLikeWhenPresent; import org.mybatis.dynamic.sql.where.condition.IsNotNull; import org.mybatis.dynamic.sql.where.condition.IsNull; @@ -253,7 +263,7 @@ static WhereDSL.StandaloneWhereFinisher where() { return new WhereDSL().where(); } - static WhereDSL.StandaloneWhereFinisher where(BindableColumn column, VisitableCondition condition, + static WhereDSL.StandaloneWhereFinisher where(BindableColumn column, RenderableCondition condition, AndOrCriteriaGroup... subCriteria) { return new WhereDSL().where(column, condition, subCriteria); } @@ -266,7 +276,7 @@ static WhereDSL.StandaloneWhereFinisher where(ExistsPredicate existsPredicate, A return new WhereDSL().where(existsPredicate, subCriteria); } - static HavingDSL.StandaloneHavingFinisher having(BindableColumn column, VisitableCondition condition, + static HavingDSL.StandaloneHavingFinisher having(BindableColumn column, RenderableCondition condition, AndOrCriteriaGroup... subCriteria) { return new HavingDSL().having(column, condition, subCriteria); } @@ -276,12 +286,12 @@ static HavingDSL.StandaloneHavingFinisher having(SqlCriterion initialCriterion, } // where condition connectors - static CriteriaGroup group(BindableColumn column, VisitableCondition condition, + static CriteriaGroup group(BindableColumn column, RenderableCondition condition, AndOrCriteriaGroup... subCriteria) { return group(column, condition, Arrays.asList(subCriteria)); } - static CriteriaGroup group(BindableColumn column, VisitableCondition condition, + static CriteriaGroup group(BindableColumn column, RenderableCondition condition, List subCriteria) { return new CriteriaGroup.Builder() .withInitialCriterion(new ColumnAndConditionCriterion.Builder().withColumn(column) @@ -319,12 +329,12 @@ static CriteriaGroup group(List subCriteria) { .build(); } - static NotCriterion not(BindableColumn column, VisitableCondition condition, + static NotCriterion not(BindableColumn column, RenderableCondition condition, AndOrCriteriaGroup... subCriteria) { return not(column, condition, Arrays.asList(subCriteria)); } - static NotCriterion not(BindableColumn column, VisitableCondition condition, + static NotCriterion not(BindableColumn column, RenderableCondition condition, List subCriteria) { return new NotCriterion.Builder() .withInitialCriterion(new ColumnAndConditionCriterion.Builder().withColumn(column) @@ -362,7 +372,7 @@ static NotCriterion not(List subCriteria) { .build(); } - static AndOrCriteriaGroup or(BindableColumn column, VisitableCondition condition, + static AndOrCriteriaGroup or(BindableColumn column, RenderableCondition condition, AndOrCriteriaGroup... subCriteria) { return new AndOrCriteriaGroup.Builder() .withInitialCriterion(ColumnAndConditionCriterion.withColumn(column) @@ -397,7 +407,7 @@ static AndOrCriteriaGroup or(List subCriteria) { .build(); } - static AndOrCriteriaGroup and(BindableColumn column, VisitableCondition condition, + static AndOrCriteriaGroup and(BindableColumn column, RenderableCondition condition, AndOrCriteriaGroup... subCriteria) { return new AndOrCriteriaGroup.Builder() .withInitialCriterion(ColumnAndConditionCriterion.withColumn(column) @@ -433,20 +443,36 @@ static AndOrCriteriaGroup and(List subCriteria) { } // join support - static JoinCriterion and(BindableColumn joinColumn, JoinCondition joinCondition) { - return new JoinCriterion.Builder() - .withConnector("and") //$NON-NLS-1$ - .withJoinColumn(joinColumn) - .withJoinCondition(joinCondition) + static ColumnAndConditionCriterion on(BindableColumn joinColumn, RenderableCondition joinCondition) { + return ColumnAndConditionCriterion.withColumn(joinColumn) + .withCondition(joinCondition) .build(); } - static JoinCriterion on(BindableColumn joinColumn, JoinCondition joinCondition) { - return new JoinCriterion.Builder() - .withConnector("on") //$NON-NLS-1$ - .withJoinColumn(joinColumn) - .withJoinCondition(joinCondition) - .build(); + /** + * Starting in version 2.0.0, this function is a synonym for {@link SqlBuilder#isEqualTo(BasicColumn)}. + * + * @param column the column + * @param the column type + * @return an IsEqualToColumn condition + * @deprecated since 2.0.0. Please replace with isEqualTo(column) + */ + @Deprecated(since = "2.0.0", forRemoval = true) + static IsEqualToColumn equalTo(BindableColumn column) { + return isEqualTo(column); + } + + /** + * Starting in version 2.0.0, this function is a synonym for {@link SqlBuilder#isEqualTo(Object)}. + * + * @param value the value + * @param the column type + * @return an IsEqualTo condition + * @deprecated since 2.0.0. Please replace with isEqualTo(value) + */ + @Deprecated(since = "2.0.0", forRemoval = true) + static IsEqualTo equalTo(T value) { + return isEqualTo(value); } // case expressions @@ -460,14 +486,6 @@ static SearchedCaseDSL case_() { return SearchedCaseDSL.searchedCase(); } - static EqualTo equalTo(BindableColumn column) { - return new EqualTo<>(column); - } - - static EqualToValue equalTo(T value) { - return new EqualToValue<>(value); - } - // aggregate support static CountAll count() { return new CountAll(); @@ -481,6 +499,10 @@ static CountDistinct countDistinct(BasicColumn column) { return CountDistinct.of(column); } + static SubQueryColumn subQuery(Buildable subQuery) { + return SubQueryColumn.of(subQuery.build()); + } + static Max max(BindableColumn column) { return Max.of(column); } @@ -497,7 +519,11 @@ static Sum sum(BindableColumn column) { return Sum.of(column); } - static Sum sum(BindableColumn column, VisitableCondition condition) { + static Sum sum(BasicColumn column) { + return Sum.of(column); + } + + static Sum sum(BindableColumn column, RenderableCondition condition) { return Sum.of(column, condition); } @@ -625,11 +651,11 @@ static IsEqualToColumn isEqualTo(BasicColumn column) { return IsEqualToColumn.of(column); } - static IsEqualTo isEqualToWhenPresent(T value) { - return IsEqualTo.of(value).filter(Objects::nonNull); + static IsEqualToWhenPresent isEqualToWhenPresent(@Nullable T value) { + return IsEqualToWhenPresent.of(value); } - static IsEqualTo isEqualToWhenPresent(Supplier valueSupplier) { + static IsEqualToWhenPresent isEqualToWhenPresent(Supplier<@Nullable T> valueSupplier) { return isEqualToWhenPresent(valueSupplier.get()); } @@ -649,11 +675,11 @@ static IsNotEqualToColumn isNotEqualTo(BasicColumn column) { return IsNotEqualToColumn.of(column); } - static IsNotEqualTo isNotEqualToWhenPresent(T value) { - return IsNotEqualTo.of(value).filter(Objects::nonNull); + static IsNotEqualToWhenPresent isNotEqualToWhenPresent(@Nullable T value) { + return IsNotEqualToWhenPresent.of(value); } - static IsNotEqualTo isNotEqualToWhenPresent(Supplier valueSupplier) { + static IsNotEqualToWhenPresent isNotEqualToWhenPresent(Supplier<@Nullable T> valueSupplier) { return isNotEqualToWhenPresent(valueSupplier.get()); } @@ -673,11 +699,11 @@ static IsGreaterThanColumn isGreaterThan(BasicColumn column) { return IsGreaterThanColumn.of(column); } - static IsGreaterThan isGreaterThanWhenPresent(T value) { - return IsGreaterThan.of(value).filter(Objects::nonNull); + static IsGreaterThanWhenPresent isGreaterThanWhenPresent(@Nullable T value) { + return IsGreaterThanWhenPresent.of(value); } - static IsGreaterThan isGreaterThanWhenPresent(Supplier valueSupplier) { + static IsGreaterThanWhenPresent isGreaterThanWhenPresent(Supplier<@Nullable T> valueSupplier) { return isGreaterThanWhenPresent(valueSupplier.get()); } @@ -698,11 +724,12 @@ static IsGreaterThanOrEqualToColumn isGreaterThanOrEqualTo(BasicColumn co return IsGreaterThanOrEqualToColumn.of(column); } - static IsGreaterThanOrEqualTo isGreaterThanOrEqualToWhenPresent(T value) { - return IsGreaterThanOrEqualTo.of(value).filter(Objects::nonNull); + static IsGreaterThanOrEqualToWhenPresent isGreaterThanOrEqualToWhenPresent(@Nullable T value) { + return IsGreaterThanOrEqualToWhenPresent.of(value); } - static IsGreaterThanOrEqualTo isGreaterThanOrEqualToWhenPresent(Supplier valueSupplier) { + static IsGreaterThanOrEqualToWhenPresent isGreaterThanOrEqualToWhenPresent( + Supplier<@Nullable T> valueSupplier) { return isGreaterThanOrEqualToWhenPresent(valueSupplier.get()); } @@ -722,11 +749,11 @@ static IsLessThanColumn isLessThan(BasicColumn column) { return IsLessThanColumn.of(column); } - static IsLessThan isLessThanWhenPresent(T value) { - return IsLessThan.of(value).filter(Objects::nonNull); + static IsLessThanWhenPresent isLessThanWhenPresent(@Nullable T value) { + return IsLessThanWhenPresent.of(value); } - static IsLessThan isLessThanWhenPresent(Supplier valueSupplier) { + static IsLessThanWhenPresent isLessThanWhenPresent(Supplier<@Nullable T> valueSupplier) { return isLessThanWhenPresent(valueSupplier.get()); } @@ -746,20 +773,20 @@ static IsLessThanOrEqualToColumn isLessThanOrEqualTo(BasicColumn column) return IsLessThanOrEqualToColumn.of(column); } - static IsLessThanOrEqualTo isLessThanOrEqualToWhenPresent(T value) { - return IsLessThanOrEqualTo.of(value).filter(Objects::nonNull); + static IsLessThanOrEqualToWhenPresent isLessThanOrEqualToWhenPresent(@Nullable T value) { + return IsLessThanOrEqualToWhenPresent.of(value); } - static IsLessThanOrEqualTo isLessThanOrEqualToWhenPresent(Supplier valueSupplier) { + static IsLessThanOrEqualToWhenPresent isLessThanOrEqualToWhenPresent(Supplier<@Nullable T> valueSupplier) { return isLessThanOrEqualToWhenPresent(valueSupplier.get()); } @SafeVarargs - static IsIn isIn(T... values) { + static IsIn isIn(@NonNull T... values) { return IsIn.of(values); } - static IsIn isIn(Collection values) { + static IsIn isIn(Collection<@NonNull T> values) { return IsIn.of(values); } @@ -768,20 +795,20 @@ static IsInWithSubselect isIn(Buildable selectModelBuilder) } @SafeVarargs - static IsInWhenPresent isInWhenPresent(T... values) { + static IsInWhenPresent isInWhenPresent(@Nullable T... values) { return IsInWhenPresent.of(values); } - static IsInWhenPresent isInWhenPresent(Collection values) { - return values == null ? IsInWhenPresent.empty() : IsInWhenPresent.of(values); + static IsInWhenPresent isInWhenPresent(@Nullable Collection<@Nullable T> values) { + return IsInWhenPresent.of(values); } @SafeVarargs - static IsNotIn isNotIn(T... values) { + static IsNotIn isNotIn(@NonNull T... values) { return IsNotIn.of(values); } - static IsNotIn isNotIn(Collection values) { + static IsNotIn isNotIn(Collection<@NonNull T> values) { return IsNotIn.of(values); } @@ -790,27 +817,27 @@ static IsNotInWithSubselect isNotIn(Buildable selectModelBui } @SafeVarargs - static IsNotInWhenPresent isNotInWhenPresent(T... values) { + static IsNotInWhenPresent isNotInWhenPresent(@Nullable T... values) { return IsNotInWhenPresent.of(values); } - static IsNotInWhenPresent isNotInWhenPresent(Collection values) { - return values == null ? IsNotInWhenPresent.empty() : IsNotInWhenPresent.of(values); + static IsNotInWhenPresent isNotInWhenPresent(@Nullable Collection<@Nullable T> values) { + return IsNotInWhenPresent.of(values); } static IsBetween.Builder isBetween(T value1) { return IsBetween.isBetween(value1); } - static IsBetween.Builder isBetween(Supplier valueSupplier1) { + static IsBetween.Builder isBetween(Supplier<@NonNull T> valueSupplier1) { return isBetween(valueSupplier1.get()); } - static IsBetween.WhenPresentBuilder isBetweenWhenPresent(T value1) { - return IsBetween.isBetweenWhenPresent(value1); + static IsBetweenWhenPresent.Builder isBetweenWhenPresent(@Nullable T value1) { + return IsBetweenWhenPresent.isBetweenWhenPresent(value1); } - static IsBetween.WhenPresentBuilder isBetweenWhenPresent(Supplier valueSupplier1) { + static IsBetweenWhenPresent.Builder isBetweenWhenPresent(Supplier<@Nullable T> valueSupplier1) { return isBetweenWhenPresent(valueSupplier1.get()); } @@ -818,15 +845,15 @@ static IsNotBetween.Builder isNotBetween(T value1) { return IsNotBetween.isNotBetween(value1); } - static IsNotBetween.Builder isNotBetween(Supplier valueSupplier1) { + static IsNotBetween.Builder isNotBetween(Supplier<@NonNull T> valueSupplier1) { return isNotBetween(valueSupplier1.get()); } - static IsNotBetween.WhenPresentBuilder isNotBetweenWhenPresent(T value1) { - return IsNotBetween.isNotBetweenWhenPresent(value1); + static IsNotBetweenWhenPresent.Builder isNotBetweenWhenPresent(@Nullable T value1) { + return IsNotBetweenWhenPresent.isNotBetweenWhenPresent(value1); } - static IsNotBetween.WhenPresentBuilder isNotBetweenWhenPresent(Supplier valueSupplier1) { + static IsNotBetweenWhenPresent.Builder isNotBetweenWhenPresent(Supplier<@Nullable T> valueSupplier1) { return isNotBetweenWhenPresent(valueSupplier1.get()); } @@ -839,11 +866,11 @@ static IsLike isLike(Supplier valueSupplier) { return isLike(valueSupplier.get()); } - static IsLike isLikeWhenPresent(T value) { - return IsLike.of(value).filter(Objects::nonNull); + static IsLikeWhenPresent isLikeWhenPresent(@Nullable T value) { + return IsLikeWhenPresent.of(value); } - static IsLike isLikeWhenPresent(Supplier valueSupplier) { + static IsLikeWhenPresent isLikeWhenPresent(Supplier<@Nullable T> valueSupplier) { return isLikeWhenPresent(valueSupplier.get()); } @@ -855,11 +882,11 @@ static IsNotLike isNotLike(Supplier valueSupplier) { return isNotLike(valueSupplier.get()); } - static IsNotLike isNotLikeWhenPresent(T value) { - return IsNotLike.of(value).filter(Objects::nonNull); + static IsNotLikeWhenPresent isNotLikeWhenPresent(@Nullable T value) { + return IsNotLikeWhenPresent.of(value); } - static IsNotLike isNotLikeWhenPresent(Supplier valueSupplier) { + static IsNotLikeWhenPresent isNotLikeWhenPresent(Supplier<@Nullable T> valueSupplier) { return isNotLikeWhenPresent(valueSupplier.get()); } @@ -873,69 +900,72 @@ static IsEqualTo isFalse() { } // conditions for strings only - static IsLikeCaseInsensitive isLikeCaseInsensitive(String value) { + static IsLikeCaseInsensitive isLikeCaseInsensitive(String value) { return IsLikeCaseInsensitive.of(value); } - static IsLikeCaseInsensitive isLikeCaseInsensitive(Supplier valueSupplier) { + static IsLikeCaseInsensitive isLikeCaseInsensitive(Supplier valueSupplier) { return isLikeCaseInsensitive(valueSupplier.get()); } - static IsLikeCaseInsensitive isLikeCaseInsensitiveWhenPresent(String value) { - return IsLikeCaseInsensitive.of(value).filter(Objects::nonNull); + static IsLikeCaseInsensitiveWhenPresent isLikeCaseInsensitiveWhenPresent(@Nullable String value) { + return IsLikeCaseInsensitiveWhenPresent.of(value); } - static IsLikeCaseInsensitive isLikeCaseInsensitiveWhenPresent(Supplier valueSupplier) { + static IsLikeCaseInsensitiveWhenPresent isLikeCaseInsensitiveWhenPresent( + Supplier<@Nullable String> valueSupplier) { return isLikeCaseInsensitiveWhenPresent(valueSupplier.get()); } - static IsNotLikeCaseInsensitive isNotLikeCaseInsensitive(String value) { + static IsNotLikeCaseInsensitive isNotLikeCaseInsensitive(String value) { return IsNotLikeCaseInsensitive.of(value); } - static IsNotLikeCaseInsensitive isNotLikeCaseInsensitive(Supplier valueSupplier) { + static IsNotLikeCaseInsensitive isNotLikeCaseInsensitive(Supplier valueSupplier) { return isNotLikeCaseInsensitive(valueSupplier.get()); } - static IsNotLikeCaseInsensitive isNotLikeCaseInsensitiveWhenPresent(String value) { - return IsNotLikeCaseInsensitive.of(value).filter(Objects::nonNull); + static IsNotLikeCaseInsensitiveWhenPresent isNotLikeCaseInsensitiveWhenPresent(@Nullable String value) { + return IsNotLikeCaseInsensitiveWhenPresent.of(value); } - static IsNotLikeCaseInsensitive isNotLikeCaseInsensitiveWhenPresent(Supplier valueSupplier) { + static IsNotLikeCaseInsensitiveWhenPresent isNotLikeCaseInsensitiveWhenPresent( + Supplier<@Nullable String> valueSupplier) { return isNotLikeCaseInsensitiveWhenPresent(valueSupplier.get()); } - static IsInCaseInsensitive isInCaseInsensitive(String... values) { + static IsInCaseInsensitive isInCaseInsensitive(String... values) { return IsInCaseInsensitive.of(values); } - static IsInCaseInsensitive isInCaseInsensitive(Collection values) { + static IsInCaseInsensitive isInCaseInsensitive(Collection values) { return IsInCaseInsensitive.of(values); } - static IsInCaseInsensitiveWhenPresent isInCaseInsensitiveWhenPresent(String... values) { + static IsInCaseInsensitiveWhenPresent isInCaseInsensitiveWhenPresent(@Nullable String... values) { return IsInCaseInsensitiveWhenPresent.of(values); } - static IsInCaseInsensitiveWhenPresent isInCaseInsensitiveWhenPresent(Collection values) { - return values == null ? IsInCaseInsensitiveWhenPresent.empty() : IsInCaseInsensitiveWhenPresent.of(values); + static IsInCaseInsensitiveWhenPresent isInCaseInsensitiveWhenPresent( + @Nullable Collection<@Nullable String> values) { + return IsInCaseInsensitiveWhenPresent.of(values); } - static IsNotInCaseInsensitive isNotInCaseInsensitive(String... values) { + static IsNotInCaseInsensitive isNotInCaseInsensitive(String... values) { return IsNotInCaseInsensitive.of(values); } - static IsNotInCaseInsensitive isNotInCaseInsensitive(Collection values) { + static IsNotInCaseInsensitive isNotInCaseInsensitive(Collection values) { return IsNotInCaseInsensitive.of(values); } - static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent(String... values) { + static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent(@Nullable String... values) { return IsNotInCaseInsensitiveWhenPresent.of(values); } - static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent(Collection values) { - return values == null ? IsNotInCaseInsensitiveWhenPresent.empty() : - IsNotInCaseInsensitiveWhenPresent.of(values); + static IsNotInCaseInsensitiveWhenPresent isNotInCaseInsensitiveWhenPresent( + @Nullable Collection<@Nullable String> values) { + return IsNotInCaseInsensitiveWhenPresent.of(values); } // order by support diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java b/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java index 76f40e15c..4c841d86b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,7 +19,7 @@ import java.util.Objects; import java.util.Optional; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.util.FragmentAndParameters; @@ -29,20 +29,20 @@ public class SqlColumn implements BindableColumn, 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 @Nullable JDBCType jdbcType; + protected final String descendingPhrase; + protected final @Nullable String alias; + protected final @Nullable String typeHandler; + protected final @Nullable RenderingStrategy renderingStrategy; protected final ParameterTypeConverter parameterTypeConverter; - protected final String tableQualifier; - protected final Class javaType; + protected final @Nullable String tableQualifier; + protected final @Nullable Class javaType; private SqlColumn(Builder builder) { name = Objects.requireNonNull(builder.name); table = Objects.requireNonNull(builder.table); jdbcType = builder.jdbcType; - isDescending = builder.isDescending; + descendingPhrase = builder.descendingPhrase; alias = builder.alias; typeHandler = builder.typeHandler; renderingStrategy = builder.renderingStrategy; @@ -80,14 +80,14 @@ public Optional> javaType() { } @Override - public Object convertParameterType(T value) { - return parameterTypeConverter.convert(value); + public @Nullable Object convertParameterType(@Nullable T value) { + return value == null ? null : parameterTypeConverter.convert(value); } @Override public SortSpecification descending() { Builder b = copy(); - return b.withDescending(true).build(); + return b.withDescendingPhrase(" DESC").build(); //$NON-NLS-1$ } @Override @@ -126,13 +126,8 @@ public SqlColumn asCamelCase() { } @Override - public boolean isDescending() { - return isDescending; - } - - @Override - public String orderByName() { - return alias().orElse(name); + public FragmentAndParameters renderForOrderBy(RenderingContext renderingContext) { + return FragmentAndParameters.fromFragment(alias().orElse(name) + descendingPhrase); } @Override @@ -149,25 +144,21 @@ public Optional renderingStrategy() { return Optional.ofNullable(renderingStrategy); } - @NotNull public SqlColumn withTypeHandler(String typeHandler) { Builder b = copy(); return b.withTypeHandler(typeHandler).build(); } - @NotNull public SqlColumn withRenderingStrategy(RenderingStrategy renderingStrategy) { Builder b = copy(); return b.withRenderingStrategy(renderingStrategy).build(); } - @NotNull public SqlColumn withParameterTypeConverter(ParameterTypeConverter parameterTypeConverter) { Builder b = copy(); return b.withParameterTypeConverter(parameterTypeConverter).build(); } - @NotNull public SqlColumn withJavaType(Class javaType) { Builder b = copy(); return b.withJavaType(javaType).build(); @@ -188,7 +179,7 @@ private Builder copy() { .withName(this.name) .withTable(this.table) .withJdbcType(this.jdbcType) - .withDescending(this.isDescending) + .withDescendingPhrase(this.descendingPhrase) .withAlias(this.alias) .withTypeHandler(this.typeHandler) .withRenderingStrategy(this.renderingStrategy) @@ -211,16 +202,16 @@ public static SqlColumn of(String name, SqlTable table, JDBCType jdbcType } public static class Builder { - protected String name; - protected SqlTable table; - protected JDBCType jdbcType; - protected boolean isDescending = false; - protected String alias; - protected String typeHandler; - protected RenderingStrategy renderingStrategy; + protected @Nullable String name; + protected @Nullable SqlTable table; + protected @Nullable JDBCType jdbcType; + protected String descendingPhrase = ""; //$NON-NLS-1$ + protected @Nullable String alias; + protected @Nullable String typeHandler; + protected @Nullable RenderingStrategy renderingStrategy; protected ParameterTypeConverter parameterTypeConverter = v -> v; - protected String tableQualifier; - protected Class javaType; + protected @Nullable String tableQualifier; + protected @Nullable Class javaType; public Builder withName(String name) { this.name = name; @@ -232,27 +223,27 @@ public Builder withTable(SqlTable table) { return this; } - public Builder withJdbcType(JDBCType jdbcType) { + public Builder withJdbcType(@Nullable JDBCType jdbcType) { this.jdbcType = jdbcType; return this; } - public Builder withDescending(boolean isDescending) { - this.isDescending = isDescending; + public Builder withDescendingPhrase(String descendingPhrase) { + this.descendingPhrase = descendingPhrase; return this; } - public Builder withAlias(String alias) { + public Builder withAlias(@Nullable String alias) { this.alias = alias; return this; } - public Builder withTypeHandler(String typeHandler) { + public Builder withTypeHandler(@Nullable String typeHandler) { this.typeHandler = typeHandler; return this; } - public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { + public Builder withRenderingStrategy(@Nullable RenderingStrategy renderingStrategy) { this.renderingStrategy = renderingStrategy; return this; } @@ -262,12 +253,12 @@ public Builder withParameterTypeConverter(ParameterTypeConverter parame return this; } - private Builder withTableQualifier(String tableQualifier) { + private Builder withTableQualifier(@Nullable String tableQualifier) { this.tableQualifier = tableQualifier; return this; } - public Builder withJavaType(Class javaType) { + public Builder withJavaType(@Nullable Class javaType) { this.javaType = javaType; return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlCriterion.java b/src/main/java/org/mybatis/dynamic/sql/SqlCriterion.java index ee3ddf414..2989f3125 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlCriterion.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlCriterion.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlCriterionVisitor.java b/src/main/java/org/mybatis/dynamic/sql/SqlCriterionVisitor.java index d6ef389ec..8431568b1 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlCriterionVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlCriterionVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlTable.java b/src/main/java/org/mybatis/dynamic/sql/SqlTable.java index d3b34eff0..fab3d8c2a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlTable.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlTable.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,110 +18,31 @@ import java.sql.JDBCType; import java.util.Objects; import java.util.Optional; -import java.util.function.Supplier; - -import org.jetbrains.annotations.NotNull; public class SqlTable implements TableExpression { - protected Supplier nameSupplier; + protected String tableName; protected SqlTable(String tableName) { - Objects.requireNonNull(tableName); - - this.nameSupplier = () -> tableName; - } - - /** - * Creates an SqlTable whose name can be changed at runtime. - * - * @param tableNameSupplier table name supplier - * @deprecated please use {@link AliasableSqlTable} if you need to change the table name at runtime - */ - @Deprecated - protected SqlTable(Supplier tableNameSupplier) { - Objects.requireNonNull(tableNameSupplier); - - this.nameSupplier = tableNameSupplier; - } - - /** - * Creates an SqlTable whose name can be changed at runtime. - * - * @param schemaSupplier schema supplier - * @param tableName table name - * @deprecated please use {@link AliasableSqlTable} if you need to change the table name at runtime - */ - @Deprecated - protected SqlTable(Supplier> schemaSupplier, String tableName) { - this(Optional::empty, schemaSupplier, tableName); - } - - /** - * Creates an SqlTable whose name can be changed at runtime. - * - * @param catalogSupplier catalog supplier - * @param schemaSupplier schema supplier - * @param tableName table name - * @deprecated please use {@link AliasableSqlTable} if you need to change the table name at runtime - */ - @Deprecated - protected SqlTable(Supplier> catalogSupplier, Supplier> schemaSupplier, - String tableName) { - Objects.requireNonNull(catalogSupplier); - Objects.requireNonNull(schemaSupplier); - Objects.requireNonNull(tableName); - - this.nameSupplier = () -> compose(catalogSupplier, schemaSupplier, tableName); - } - - private String compose(Supplier> catalogSupplier, Supplier> schemaSupplier, - String tableName) { - return catalogSupplier.get().map(c -> compose(c, schemaSupplier, tableName)) - .orElseGet(() -> compose(schemaSupplier, tableName)); - } - - private String compose(String catalog, Supplier> schemaSupplier, String tableName) { - return schemaSupplier.get().map(s -> composeCatalogSchemaAndTable(catalog, s, tableName)) - .orElseGet(() -> composeCatalogAndTable(catalog, tableName)); - } - - private String compose(Supplier> schemaSupplier, String tableName) { - return schemaSupplier.get().map(s -> composeSchemaAndTable(s, tableName)) - .orElse(tableName); - } - - private String composeCatalogAndTable(String catalog, String tableName) { - return catalog + ".." + tableName; //$NON-NLS-1$ - } - - private String composeSchemaAndTable(String schema, String tableName) { - return schema + "." + tableName; //$NON-NLS-1$ - } - - private String composeCatalogSchemaAndTable(String catalog, String schema, String tableName) { - return catalog + "." + schema + "." + tableName; //$NON-NLS-1$ //$NON-NLS-2$ + this.tableName = Objects.requireNonNull(tableName); } - public String tableNameAtRuntime() { - return nameSupplier.get(); + public String tableName() { + return tableName; } public BasicColumn allColumns() { return SqlColumn.of("*", this); //$NON-NLS-1$ } - @NotNull public SqlColumn column(String name) { return SqlColumn.of(name, this); } - @NotNull public SqlColumn column(String name, JDBCType jdbcType) { return SqlColumn.of(name, this, jdbcType); } - @NotNull public SqlColumn column(String name, JDBCType jdbcType, String typeHandler) { SqlColumn column = SqlColumn.of(name, this, jdbcType); return column.withTypeHandler(typeHandler); diff --git a/src/main/java/org/mybatis/dynamic/sql/StringConstant.java b/src/main/java/org/mybatis/dynamic/sql/StringConstant.java index bced8b446..ae9f0a75b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/StringConstant.java +++ b/src/main/java/org/mybatis/dynamic/sql/StringConstant.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,20 +18,21 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.FragmentAndParameters; import org.mybatis.dynamic.sql.util.StringUtilities; public class StringConstant implements BindableColumn { - private final String alias; + private final @Nullable String alias; private final String value; private StringConstant(String value) { this(value, null); } - private StringConstant(String value, String alias) { + private StringConstant(String value, @Nullable String alias) { this.value = Objects.requireNonNull(value); this.alias = alias; } diff --git a/src/main/java/org/mybatis/dynamic/sql/SubQueryColumn.java b/src/main/java/org/mybatis/dynamic/sql/SubQueryColumn.java new file mode 100644 index 000000000..cc35bdcad --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/SubQueryColumn.java @@ -0,0 +1,60 @@ +/* + * 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 org.mybatis.dynamic.sql; + +import java.util.Objects; +import java.util.Optional; + +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.select.SelectModel; +import org.mybatis.dynamic.sql.select.render.SubQueryRenderer; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; + +public class SubQueryColumn implements BasicColumn { + private final SelectModel selectModel; + private @Nullable String alias; + + private SubQueryColumn(SelectModel selectModel) { + this.selectModel = Objects.requireNonNull(selectModel); + } + + @Override + public Optional alias() { + return Optional.ofNullable(alias); + } + + @Override + public SubQueryColumn as(String alias) { + SubQueryColumn answer = new SubQueryColumn(selectModel); + answer.alias = alias; + return answer; + } + + @Override + public FragmentAndParameters render(RenderingContext renderingContext) { + return SubQueryRenderer.withSelectModel(selectModel) + .withRenderingContext(renderingContext) + .withPrefix("(") //$NON-NLS-1$ + .withSuffix(")") //$NON-NLS-1$ + .build() + .render(); + } + + public static SubQueryColumn of(SelectModel selectModel) { + return new SubQueryColumn(selectModel); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/TableExpression.java b/src/main/java/org/mybatis/dynamic/sql/TableExpression.java index 06699f93f..75ee1e8d4 100644 --- a/src/main/java/org/mybatis/dynamic/sql/TableExpression.java +++ b/src/main/java/org/mybatis/dynamic/sql/TableExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/TableExpressionVisitor.java b/src/main/java/org/mybatis/dynamic/sql/TableExpressionVisitor.java index bf232f325..407d7ebbb 100644 --- a/src/main/java/org/mybatis/dynamic/sql/TableExpressionVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/TableExpressionVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/VisitableCondition.java b/src/main/java/org/mybatis/dynamic/sql/VisitableCondition.java index f13076bfe..9969c3997 100644 --- a/src/main/java/org/mybatis/dynamic/sql/VisitableCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/VisitableCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,45 +17,20 @@ import org.mybatis.dynamic.sql.render.RenderingContext; -@FunctionalInterface -public interface VisitableCondition { - R accept(ConditionVisitor visitor); - - /** - * Subclasses can override this to inform the renderer if the condition should not be included - * in the rendered SQL. Typically, conditions will not render if they are empty. - * - * @return true if the condition should render. - */ - default boolean shouldRender(RenderingContext renderingContext) { - return !isEmpty(); - } - - /** - * Subclasses can override this to indicate whether the condition is considered empty. This is primarily used in - * map and filter operations - the map and filter functions will not be applied if the condition is empty. - * - * @return true if the condition is empty. - */ - default boolean isEmpty() { - return false; - } - - /** - * This method will be called during rendering when {@link VisitableCondition#shouldRender(RenderingContext)} - * returns false. - */ - default void renderingSkipped() {} - - /** - * This method is called during rendering. Its purpose is to allow conditions to change - * the value of the rendered left column. This is primarily used in the case-insensitive conditions - * where we surround the rendered column with "upper(" and ")". - * - * @param renderedLeftColumn the rendered left column - * @return the altered column - by default no change is applied - */ - default String overrideRenderedLeftColumn(String renderedLeftColumn) { - return renderedLeftColumn; - } -} +/** + * Deprecated interface. + * + *

Conditions are no longer rendered with a visitor, so the name is misleading. This change makes it far easier + * to implement custom conditions for functionality not supplied out of the box by the library. + * + *

If you created any direct implementations of this interface, you will need to change the rendering functions. + * The library now calls {@link RenderableCondition#renderCondition(RenderingContext, BindableColumn)} and + * {@link RenderableCondition#renderLeftColumn(RenderingContext, BindableColumn)} instead of the previous methods + * like operator, value, etc. Subclasses of the supplied abstract conditions should continue + * to function as before. + * + * @param the Java type related to the column this condition relates to. Used primarily for compiler type checking + * @deprecated since 2.0.0. Please use {@link RenderableCondition} instead. + */ +@Deprecated(since = "2.0.0", forRemoval = true) +public interface VisitableCondition extends RenderableCondition { } diff --git a/src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionDSL.java b/src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionDSL.java index 40f9cc0f7..2f817fb5f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,104 +19,90 @@ import java.util.Arrays; import java.util.List; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AndOrCriteriaGroup; import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.ColumnAndConditionCriterion; import org.mybatis.dynamic.sql.CriteriaGroup; import org.mybatis.dynamic.sql.ExistsCriterion; import org.mybatis.dynamic.sql.ExistsPredicate; +import org.mybatis.dynamic.sql.RenderableCondition; import org.mybatis.dynamic.sql.SqlCriterion; -import org.mybatis.dynamic.sql.VisitableCondition; import org.mybatis.dynamic.sql.util.Validator; public abstract class AbstractBooleanExpressionDSL> { - private SqlCriterion initialCriterion; // WARNING - may be null! + private @Nullable SqlCriterion initialCriterion; protected final List subCriteria = new ArrayList<>(); - @NotNull - public T and(BindableColumn column, VisitableCondition condition, + public T and(BindableColumn column, RenderableCondition condition, AndOrCriteriaGroup... subCriteria) { return and(column, condition, Arrays.asList(subCriteria)); } - @NotNull - public T and(BindableColumn column, VisitableCondition condition, + public T and(BindableColumn column, RenderableCondition condition, List subCriteria) { addSubCriteria("and", buildCriterion(column, condition), subCriteria); //$NON-NLS-1$ return getThis(); } - @NotNull public T and(ExistsPredicate existsPredicate, AndOrCriteriaGroup... subCriteria) { return and(existsPredicate, Arrays.asList(subCriteria)); } - @NotNull public T and(ExistsPredicate existsPredicate, List subCriteria) { addSubCriteria("and", buildCriterion(existsPredicate), subCriteria); //$NON-NLS-1$ return getThis(); } - @NotNull public T and(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) { return and(initialCriterion, Arrays.asList(subCriteria)); } - @NotNull public T and(SqlCriterion initialCriterion, List subCriteria) { addSubCriteria("and", buildCriterion(initialCriterion), subCriteria); //$NON-NLS-1$ return getThis(); } - @NotNull public T and(List criteria) { addSubCriteria("and", criteria); //$NON-NLS-1$ return getThis(); } - @NotNull - public T or(BindableColumn column, VisitableCondition condition, + public T or(BindableColumn column, RenderableCondition condition, AndOrCriteriaGroup... subCriteria) { return or(column, condition, Arrays.asList(subCriteria)); } - @NotNull - public T or(BindableColumn column, VisitableCondition condition, + public T or(BindableColumn column, RenderableCondition condition, List subCriteria) { addSubCriteria("or", buildCriterion(column, condition), subCriteria); //$NON-NLS-1$ return getThis(); } - @NotNull public T or(ExistsPredicate existsPredicate, AndOrCriteriaGroup... subCriteria) { return or(existsPredicate, Arrays.asList(subCriteria)); } - @NotNull public T or(ExistsPredicate existsPredicate, List subCriteria) { addSubCriteria("or", buildCriterion(existsPredicate), subCriteria); //$NON-NLS-1$ return getThis(); } - @NotNull public T or(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) { return or(initialCriterion, Arrays.asList(subCriteria)); } - @NotNull public T or(SqlCriterion initialCriterion, List subCriteria) { addSubCriteria("or", buildCriterion(initialCriterion), subCriteria); //$NON-NLS-1$ return getThis(); } - @NotNull public T or(List criteria) { addSubCriteria("or", criteria); //$NON-NLS-1$ return getThis(); } - private SqlCriterion buildCriterion(BindableColumn column, VisitableCondition condition) { + private SqlCriterion buildCriterion(BindableColumn column, RenderableCondition condition) { return ColumnAndConditionCriterion.withColumn(column).withCondition(condition).build(); } @@ -144,17 +130,16 @@ private void addSubCriteria(String connector, List criteria) .build()); } - protected void setInitialCriterion(SqlCriterion initialCriterion) { + protected void setInitialCriterion(@Nullable SqlCriterion initialCriterion) { this.initialCriterion = initialCriterion; } - protected void setInitialCriterion(SqlCriterion initialCriterion, StatementType statementType) { + protected void setInitialCriterion(@Nullable SqlCriterion initialCriterion, StatementType statementType) { Validator.assertTrue(this.initialCriterion == null, statementType.messageNumber()); setInitialCriterion(initialCriterion); } - // may be null! - protected SqlCriterion getInitialCriterion() { + protected @Nullable SqlCriterion getInitialCriterion() { return initialCriterion; } diff --git a/src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionModel.java b/src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionModel.java index 0913fe17c..edda8707f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,11 +20,12 @@ import java.util.List; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AndOrCriteriaGroup; import org.mybatis.dynamic.sql.SqlCriterion; public abstract class AbstractBooleanExpressionModel { - private final SqlCriterion initialCriterion; + private final @Nullable SqlCriterion initialCriterion; private final List subCriteria ; protected AbstractBooleanExpressionModel(AbstractBuilder builder) { @@ -41,10 +42,10 @@ public List subCriteria() { } public abstract static class AbstractBuilder> { - private SqlCriterion initialCriterion; + private @Nullable SqlCriterion initialCriterion; private final List subCriteria = new ArrayList<>(); - public T withInitialCriterion(SqlCriterion initialCriterion) { + public T withInitialCriterion(@Nullable SqlCriterion initialCriterion) { this.initialCriterion = initialCriterion; return getThis(); } diff --git a/src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionRenderer.java index 910e20bd3..a006b1834 100644 --- a/src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/common/AbstractBooleanExpressionRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,6 +21,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlCriterion; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.FragmentAndParameters; @@ -84,7 +85,7 @@ private String addPrefix(String fragment) { public abstract static class AbstractBuilder> { private final AbstractBooleanExpressionModel model; - private RenderingContext renderingContext; + private @Nullable RenderingContext renderingContext; protected AbstractBuilder(AbstractBooleanExpressionModel model) { this.model = model; diff --git a/src/main/java/org/mybatis/dynamic/sql/common/CommonBuilder.java b/src/main/java/org/mybatis/dynamic/sql/common/CommonBuilder.java index f25457398..269b857af 100644 --- a/src/main/java/org/mybatis/dynamic/sql/common/CommonBuilder.java +++ b/src/main/java/org/mybatis/dynamic/sql/common/CommonBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,6 +15,7 @@ */ package org.mybatis.dynamic.sql.common; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.where.EmbeddedWhereModel; @@ -25,34 +26,34 @@ * @param type of the implementing builder */ public abstract class CommonBuilder> { - private SqlTable table; - private String tableAlias; - private EmbeddedWhereModel whereModel; - private Long limit; - private OrderByModel orderByModel; - private StatementConfiguration statementConfiguration; + private @Nullable SqlTable table; + private @Nullable String tableAlias; + private @Nullable EmbeddedWhereModel whereModel; + private @Nullable Long limit; + private @Nullable OrderByModel orderByModel; + private @Nullable StatementConfiguration statementConfiguration; - public SqlTable table() { + public @Nullable SqlTable table() { return table; } - public String tableAlias() { + public @Nullable String tableAlias() { return tableAlias; } - public EmbeddedWhereModel whereModel() { + public @Nullable EmbeddedWhereModel whereModel() { return whereModel; } - public Long limit() { + public @Nullable Long limit() { return limit; } - public OrderByModel orderByModel() { + public @Nullable OrderByModel orderByModel() { return orderByModel; } - public StatementConfiguration statementConfiguration() { + public @Nullable StatementConfiguration statementConfiguration() { return statementConfiguration; } @@ -61,22 +62,22 @@ public T withTable(SqlTable table) { return getThis(); } - public T withTableAlias(String tableAlias) { + public T withTableAlias(@Nullable String tableAlias) { this.tableAlias = tableAlias; return getThis(); } - public T withWhereModel(EmbeddedWhereModel whereModel) { + public T withWhereModel(@Nullable EmbeddedWhereModel whereModel) { this.whereModel = whereModel; return getThis(); } - public T withLimit(Long limit) { + public T withLimit(@Nullable Long limit) { this.limit = limit; return getThis(); } - public T withOrderByModel(OrderByModel orderByModel) { + public T withOrderByModel(@Nullable OrderByModel orderByModel) { this.orderByModel = orderByModel; return getThis(); } diff --git a/src/main/java/org/mybatis/dynamic/sql/common/OrderByModel.java b/src/main/java/org/mybatis/dynamic/sql/common/OrderByModel.java index 3a05f49d9..42d2de4fb 100644 --- a/src/main/java/org/mybatis/dynamic/sql/common/OrderByModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/common/OrderByModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/common/OrderByRenderer.java b/src/main/java/org/mybatis/dynamic/sql/common/OrderByRenderer.java index f1a28ed4e..851bf73ea 100644 --- a/src/main/java/org/mybatis/dynamic/sql/common/OrderByRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/common/OrderByRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,24 +15,24 @@ */ package org.mybatis.dynamic.sql.common; +import java.util.Objects; import java.util.stream.Collectors; -import org.mybatis.dynamic.sql.SortSpecification; +import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.FragmentAndParameters; +import org.mybatis.dynamic.sql.util.FragmentCollector; public class OrderByRenderer { - public FragmentAndParameters render(OrderByModel orderByModel) { - String phrase = orderByModel.columns() - .map(this::calculateOrderByPhrase) - .collect(Collectors.joining(", ", "order by ", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - return FragmentAndParameters.fromFragment(phrase); + private final RenderingContext renderingContext; + + public OrderByRenderer(RenderingContext renderingContext) { + this.renderingContext = Objects.requireNonNull(renderingContext); } - private String calculateOrderByPhrase(SortSpecification column) { - String phrase = column.orderByName(); - if (column.isDescending()) { - phrase = phrase + " DESC"; //$NON-NLS-1$ - } - return phrase; + public FragmentAndParameters render(OrderByModel orderByModel) { + return orderByModel.columns().map(c -> c.renderForOrderBy(renderingContext)) + .collect(FragmentCollector.collect()) + .toFragmentAndParameters( + Collectors.joining(", ", "order by ", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinConditionVisitor.java b/src/main/java/org/mybatis/dynamic/sql/common/package-info.java similarity index 70% rename from src/main/java/org/mybatis/dynamic/sql/select/join/JoinConditionVisitor.java rename to src/main/java/org/mybatis/dynamic/sql/common/package-info.java index 582b332f1..f581c8034 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinConditionVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/common/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -13,10 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mybatis.dynamic.sql.select.join; +@NullMarked +package org.mybatis.dynamic.sql.common; -public interface JoinConditionVisitor { - R visit(TypedJoinCondition condition); - - R visit(ColumnBasedJoinCondition condition); -} +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalConfiguration.java b/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalConfiguration.java index 8e7054d78..670ddd935 100644 --- a/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalConfiguration.java +++ b/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Objects; import java.util.Properties; import org.mybatis.dynamic.sql.exception.DynamicSqlException; @@ -47,11 +48,7 @@ private void initializeProperties() { private String getConfigurationFileName() { String property = System.getProperty(CONFIGURATION_FILE_PROPERTY); - if (property == null) { - return DEFAULT_PROPERTY_FILE; - } else { - return property; - } + return Objects.requireNonNullElse(property, DEFAULT_PROPERTY_FILE); } void loadProperties(InputStream inputStream, String propertyFile) { diff --git a/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalContext.java b/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalContext.java index 0cdc5d902..6963374b6 100644 --- a/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalContext.java +++ b/src/main/java/org/mybatis/dynamic/sql/configuration/GlobalContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/configuration/StatementConfiguration.java b/src/main/java/org/mybatis/dynamic/sql/configuration/StatementConfiguration.java index bb5641f3f..ed187fe92 100644 --- a/src/main/java/org/mybatis/dynamic/sql/configuration/StatementConfiguration.java +++ b/src/main/java/org/mybatis/dynamic/sql/configuration/StatementConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/EqualToValue.java b/src/main/java/org/mybatis/dynamic/sql/configuration/package-info.java similarity index 65% rename from src/main/java/org/mybatis/dynamic/sql/select/join/EqualToValue.java rename to src/main/java/org/mybatis/dynamic/sql/configuration/package-info.java index 2d038aa21..41fcac6c7 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/EqualToValue.java +++ b/src/main/java/org/mybatis/dynamic/sql/configuration/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -13,15 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mybatis.dynamic.sql.select.join; +@NullMarked +package org.mybatis.dynamic.sql.configuration; -public class EqualToValue extends TypedJoinCondition { - public EqualToValue(T value) { - super(value); - } - - @Override - public String operator() { - return "="; //$NON-NLS-1$ - } -} +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java index e7c1ad184..aeebc5498 100644 --- a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,29 +21,28 @@ import java.util.function.Consumer; import java.util.function.Function; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.common.OrderByModel; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.util.Buildable; -import org.mybatis.dynamic.sql.util.Utilities; import org.mybatis.dynamic.sql.where.AbstractWhereFinisher; import org.mybatis.dynamic.sql.where.AbstractWhereStarter; import org.mybatis.dynamic.sql.where.EmbeddedWhereModel; -public class DeleteDSL extends AbstractWhereStarter.DeleteWhereBuilder, DeleteDSL> - implements Buildable { +public class DeleteDSL implements AbstractWhereStarter.DeleteWhereBuilder, DeleteDSL>, + Buildable { private final Function adapterFunction; private final SqlTable table; - private final String tableAlias; - private DeleteWhereBuilder whereBuilder; + private final @Nullable String tableAlias; + private @Nullable DeleteWhereBuilder whereBuilder; private final StatementConfiguration statementConfiguration = new StatementConfiguration(); - private Long limit; - private OrderByModel orderByModel; + private @Nullable Long limit; + private @Nullable OrderByModel orderByModel; - private DeleteDSL(SqlTable table, String tableAlias, Function adapterFunction) { + private DeleteDSL(SqlTable table, @Nullable String tableAlias, Function adapterFunction) { this.table = Objects.requireNonNull(table); this.tableAlias = tableAlias; this.adapterFunction = Objects.requireNonNull(adapterFunction); @@ -51,11 +50,15 @@ private DeleteDSL(SqlTable table, String tableAlias, Function ad @Override public DeleteWhereBuilder where() { - whereBuilder = Utilities.buildIfNecessary(whereBuilder, DeleteWhereBuilder::new); + whereBuilder = Objects.requireNonNullElseGet(whereBuilder, DeleteWhereBuilder::new); return whereBuilder; } public DeleteDSL limit(long limit) { + return limitWhenPresent(limit); + } + + public DeleteDSL limitWhenPresent(@Nullable Long limit) { this.limit = limit; return this; } @@ -75,7 +78,6 @@ public DeleteDSL orderBy(Collection columns) { * * @return the model class */ - @NotNull @Override public R build() { DeleteModel deleteModel = DeleteModel.withTable(table) @@ -96,7 +98,7 @@ public DeleteDSL configureStatement(Consumer consumer } public static DeleteDSL deleteFrom(Function adapterFunction, SqlTable table, - String tableAlias) { + @Nullable String tableAlias) { return new DeleteDSL<>(table, tableAlias, adapterFunction); } @@ -115,7 +117,11 @@ private DeleteWhereBuilder() { } public DeleteDSL limit(long limit) { - return DeleteDSL.this.limit(limit); + return limitWhenPresent(limit); + } + + public DeleteDSL limitWhenPresent(@Nullable Long limit) { + return DeleteDSL.this.limitWhenPresent(limit); } public DeleteDSL orderBy(SortSpecification... columns) { @@ -127,7 +133,6 @@ public DeleteDSL orderBy(Collection columns) { return DeleteDSL.this; } - @NotNull @Override public R build() { return DeleteDSL.this.build(); diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSLCompleter.java b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSLCompleter.java index 799ea7e5e..b1aeee392 100644 --- a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSLCompleter.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteDSLCompleter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteModel.java b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteModel.java index 54770d5d4..1f9dc606f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/delete/DeleteModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/DeleteModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,7 +18,7 @@ import java.util.Objects; import java.util.Optional; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.common.CommonBuilder; import org.mybatis.dynamic.sql.common.OrderByModel; @@ -30,10 +30,10 @@ public class DeleteModel { private final SqlTable table; - private final String tableAlias; - private final EmbeddedWhereModel whereModel; - private final Long limit; - private final OrderByModel orderByModel; + private final @Nullable String tableAlias; + private final @Nullable EmbeddedWhereModel whereModel; + private final @Nullable Long limit; + private final @Nullable OrderByModel orderByModel; private final StatementConfiguration statementConfiguration; private DeleteModel(Builder builder) { @@ -65,11 +65,13 @@ public Optional orderByModel() { return Optional.ofNullable(orderByModel); } - @NotNull + public StatementConfiguration statementConfiguration() { + return statementConfiguration; + } + public DeleteStatementProvider render(RenderingStrategy renderingStrategy) { return DeleteRenderer.withDeleteModel(this) .withRenderingStrategy(renderingStrategy) - .withStatementConfiguration(statementConfiguration) .build() .render(); } diff --git a/src/test/java/examples/schema_supplier/SchemaSupplier.java b/src/main/java/org/mybatis/dynamic/sql/delete/package-info.java similarity index 62% rename from src/test/java/examples/schema_supplier/SchemaSupplier.java rename to src/main/java/org/mybatis/dynamic/sql/delete/package-info.java index b5a3e8fbe..a7bfc9d26 100644 --- a/src/test/java/examples/schema_supplier/SchemaSupplier.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -13,14 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package examples.schema_supplier; +@NullMarked +package org.mybatis.dynamic.sql.delete; -import java.util.Optional; - -public class SchemaSupplier { - public static final String schema_property = "schemaToUse"; - - public static Optional schemaPropertyReader() { - return Optional.ofNullable(System.getProperty(schema_property)); - } -} +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/render/DefaultDeleteStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/delete/render/DefaultDeleteStatementProvider.java index 89b72333b..db5feef01 100644 --- a/src/main/java/org/mybatis/dynamic/sql/delete/render/DefaultDeleteStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/render/DefaultDeleteStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,13 +19,15 @@ import java.util.Map; import java.util.Objects; +import org.jspecify.annotations.Nullable; + public class DefaultDeleteStatementProvider implements DeleteStatementProvider { private final String deleteStatement; private final Map parameters; private DefaultDeleteStatementProvider(Builder builder) { deleteStatement = Objects.requireNonNull(builder.deleteStatement); - parameters = Objects.requireNonNull(builder.parameters); + parameters = builder.parameters; } @Override @@ -43,7 +45,7 @@ public static Builder withDeleteStatement(String deleteStatement) { } public static class Builder { - private String deleteStatement; + private @Nullable String deleteStatement; private final Map parameters = new HashMap<>(); public Builder withDeleteStatement(String deleteStatement) { diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java b/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java index ce46c4c10..d65fcdd58 100644 --- a/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,9 +19,9 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.common.OrderByModel; import org.mybatis.dynamic.sql.common.OrderByRenderer; -import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.delete.DeleteModel; import org.mybatis.dynamic.sql.render.ExplicitTableAliasCalculator; import org.mybatis.dynamic.sql.render.RenderedParameterInfo; @@ -44,7 +44,7 @@ private DeleteRenderer(Builder builder) { renderingContext = RenderingContext .withRenderingStrategy(Objects.requireNonNull(builder.renderingStrategy)) .withTableAliasCalculator(tableAliasCalculator) - .withStatementConfiguration(builder.statementConfiguration) + .withStatementConfiguration(deleteModel.statementConfiguration()) .build(); } @@ -84,7 +84,7 @@ private Optional calculateLimitClause() { } private FragmentAndParameters renderLimitClause(Long limit) { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(); + RenderedParameterInfo parameterInfo = renderingContext.calculateLimitParameterInfo(); return FragmentAndParameters.withFragment("limit " + parameterInfo.renderedPlaceHolder()) //$NON-NLS-1$ .withParameter(parameterInfo.parameterMapKey(), limit) @@ -96,7 +96,7 @@ private Optional calculateOrderByClause() { } private FragmentAndParameters renderOrderByClause(OrderByModel orderByModel) { - return new OrderByRenderer().render(orderByModel); + return new OrderByRenderer(renderingContext).render(orderByModel); } public static Builder withDeleteModel(DeleteModel deleteModel) { @@ -104,9 +104,8 @@ public static Builder withDeleteModel(DeleteModel deleteModel) { } public static class Builder { - private DeleteModel deleteModel; - private RenderingStrategy renderingStrategy; - private StatementConfiguration statementConfiguration; + private @Nullable DeleteModel deleteModel; + private @Nullable RenderingStrategy renderingStrategy; public Builder withDeleteModel(DeleteModel deleteModel) { this.deleteModel = deleteModel; @@ -118,11 +117,6 @@ public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { return this; } - public Builder withStatementConfiguration(StatementConfiguration statementConfiguration) { - this.statementConfiguration = statementConfiguration; - return this; - } - public DeleteRenderer build() { return new DeleteRenderer(this); } diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteStatementProvider.java index 9ccf16c98..743c84e2d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/delete/render/DeleteStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/delete/render/package-info.java b/src/main/java/org/mybatis/dynamic/sql/delete/render/package-info.java new file mode 100644 index 000000000..8b25d555d --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/delete/render/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.delete.render; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/exception/DuplicateTableAliasException.java b/src/main/java/org/mybatis/dynamic/sql/exception/DuplicateTableAliasException.java index d32e66e5e..21d43a927 100644 --- a/src/main/java/org/mybatis/dynamic/sql/exception/DuplicateTableAliasException.java +++ b/src/main/java/org/mybatis/dynamic/sql/exception/DuplicateTableAliasException.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,6 +15,7 @@ */ package org.mybatis.dynamic.sql.exception; +import java.io.Serial; import java.util.Objects; import org.mybatis.dynamic.sql.SqlTable; @@ -34,6 +35,7 @@ */ public class DuplicateTableAliasException extends DynamicSqlException { + @Serial private static final long serialVersionUID = -2631664872557787391L; public DuplicateTableAliasException(SqlTable table, String newAlias, String existingAlias) { @@ -43,6 +45,6 @@ public DuplicateTableAliasException(SqlTable table, String newAlias, String exis } private static String generateMessage(SqlTable table, String newAlias, String existingAlias) { - return Messages.getString("ERROR.1", table.tableNameAtRuntime(), newAlias, existingAlias); //$NON-NLS-1$ + return Messages.getString("ERROR.1", table.tableName(), newAlias, existingAlias); //$NON-NLS-1$ } } diff --git a/src/main/java/org/mybatis/dynamic/sql/exception/DynamicSqlException.java b/src/main/java/org/mybatis/dynamic/sql/exception/DynamicSqlException.java index 3a8bc5791..f1836b990 100644 --- a/src/main/java/org/mybatis/dynamic/sql/exception/DynamicSqlException.java +++ b/src/main/java/org/mybatis/dynamic/sql/exception/DynamicSqlException.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,7 +15,10 @@ */ package org.mybatis.dynamic.sql.exception; +import java.io.Serial; + public class DynamicSqlException extends RuntimeException { + @Serial private static final long serialVersionUID = 349021672061361244L; public DynamicSqlException(String message) { diff --git a/src/main/java/org/mybatis/dynamic/sql/exception/InvalidSqlException.java b/src/main/java/org/mybatis/dynamic/sql/exception/InvalidSqlException.java index 51ce3f787..44d407ea1 100644 --- a/src/main/java/org/mybatis/dynamic/sql/exception/InvalidSqlException.java +++ b/src/main/java/org/mybatis/dynamic/sql/exception/InvalidSqlException.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,7 +15,10 @@ */ package org.mybatis.dynamic.sql.exception; +import java.io.Serial; + public class InvalidSqlException extends DynamicSqlException { + @Serial private static final long serialVersionUID = 1666851020951347843L; public InvalidSqlException(String message) { diff --git a/src/main/java/org/mybatis/dynamic/sql/exception/NonRenderingWhereClauseException.java b/src/main/java/org/mybatis/dynamic/sql/exception/NonRenderingWhereClauseException.java index 3dfa0ed36..0561f2d42 100644 --- a/src/main/java/org/mybatis/dynamic/sql/exception/NonRenderingWhereClauseException.java +++ b/src/main/java/org/mybatis/dynamic/sql/exception/NonRenderingWhereClauseException.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,6 +15,8 @@ */ package org.mybatis.dynamic.sql.exception; +import java.io.Serial; + import org.mybatis.dynamic.sql.configuration.GlobalConfiguration; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.util.Messages; @@ -40,6 +42,7 @@ * @author Jeff Butler */ public class NonRenderingWhereClauseException extends DynamicSqlException { + @Serial private static final long serialVersionUID = 6619119078542625135L; public NonRenderingWhereClauseException() { diff --git a/src/main/java/org/mybatis/dynamic/sql/exception/package-info.java b/src/main/java/org/mybatis/dynamic/sql/exception/package-info.java new file mode 100644 index 000000000..4e802e370 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/exception/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.exception; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/AbstractMultiRowInsertModel.java b/src/main/java/org/mybatis/dynamic/sql/insert/AbstractMultiRowInsertModel.java index 01056f60f..4fc8a3b4d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/AbstractMultiRowInsertModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/AbstractMultiRowInsertModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -22,6 +22,7 @@ import java.util.Objects; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.util.AbstractColumnMapping; @@ -53,7 +54,7 @@ public int recordCount() { } public abstract static class AbstractBuilder> { - private SqlTable table; + private @Nullable SqlTable table; private final List records = new ArrayList<>(); private final List columnMappings = new ArrayList<>(); diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/BatchInsertDSL.java b/src/main/java/org/mybatis/dynamic/sql/insert/BatchInsertDSL.java index 9b5f69cec..71bc350d6 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/BatchInsertDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/BatchInsertDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,7 +21,7 @@ import java.util.List; import java.util.Objects; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.util.AbstractColumnMapping; @@ -48,7 +48,6 @@ public ColumnMappingFinisher map(SqlColumn column) { return new ColumnMappingFinisher<>(column); } - @NotNull @Override public BatchInsertModel build() { return BatchInsertModel.withRecords(records) @@ -58,11 +57,11 @@ public BatchInsertModel build() { } @SafeVarargs - public static IntoGatherer insert(T... records) { - return BatchInsertDSL.insert(Arrays.asList(records)); + public static BatchInsertDSL.IntoGatherer insert(T... records) { + return insert(Arrays.asList(records)); } - public static IntoGatherer insert(Collection records) { + public static BatchInsertDSL.IntoGatherer insert(Collection records) { return new IntoGatherer<>(records); } @@ -113,7 +112,7 @@ public BatchInsertDSL toRow() { public abstract static class AbstractBuilder> { final Collection records = new ArrayList<>(); - SqlTable table; + @Nullable SqlTable table; final List columnMappings = new ArrayList<>(); public B withRecords(Collection records) { diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/BatchInsertModel.java b/src/main/java/org/mybatis/dynamic/sql/insert/BatchInsertModel.java index b591753a2..275ce2745 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/BatchInsertModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/BatchInsertModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,7 +17,6 @@ import java.util.Collection; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.insert.render.BatchInsert; import org.mybatis.dynamic.sql.insert.render.BatchInsertRenderer; import org.mybatis.dynamic.sql.render.RenderingStrategy; @@ -31,7 +30,6 @@ private BatchInsertModel(Builder builder) { Validator.assertNotEmpty(columnMappings, "ERROR.5"); //$NON-NLS-1$ } - @NotNull public BatchInsert render(RenderingStrategy renderingStrategy) { return BatchInsertRenderer.withBatchInsertModel(this) .withRenderingStrategy(renderingStrategy) diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/GeneralInsertDSL.java b/src/main/java/org/mybatis/dynamic/sql/insert/GeneralInsertDSL.java index f36fd6544..5cba9063d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/GeneralInsertDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/GeneralInsertDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,7 +21,7 @@ import java.util.Objects; import java.util.function.Supplier; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; @@ -47,7 +47,6 @@ public SetClauseFinisher set(SqlColumn column) { return new SetClauseFinisher<>(column); } - @NotNull @Override public GeneralInsertModel build() { return new GeneralInsertModel.Builder() @@ -93,20 +92,20 @@ public GeneralInsertDSL toValue(Supplier valueSupplier) { return GeneralInsertDSL.this; } - public GeneralInsertDSL toValueOrNull(T value) { + public GeneralInsertDSL toValueOrNull(@Nullable T value) { return toValueOrNull(() -> value); } - public GeneralInsertDSL toValueOrNull(Supplier valueSupplier) { + public GeneralInsertDSL toValueOrNull(Supplier<@Nullable T> valueSupplier) { columnMappings.add(ValueOrNullMapping.of(column, valueSupplier)); return GeneralInsertDSL.this; } - public GeneralInsertDSL toValueWhenPresent(T value) { + public GeneralInsertDSL toValueWhenPresent(@Nullable T value) { return toValueWhenPresent(() -> value); } - public GeneralInsertDSL toValueWhenPresent(Supplier valueSupplier) { + public GeneralInsertDSL toValueWhenPresent(Supplier<@Nullable T> valueSupplier) { columnMappings.add(ValueWhenPresentMapping.of(column, valueSupplier)); return GeneralInsertDSL.this; } @@ -114,7 +113,7 @@ public GeneralInsertDSL toValueWhenPresent(Supplier valueSupplier) { public static class Builder { private final List columnMappings = new ArrayList<>(); - private SqlTable table; + private @Nullable SqlTable table; public Builder withTable(SqlTable table) { this.table = table; diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/GeneralInsertModel.java b/src/main/java/org/mybatis/dynamic/sql/insert/GeneralInsertModel.java index 7f219027d..873c98f5f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/GeneralInsertModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/GeneralInsertModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,7 +20,7 @@ import java.util.Objects; import java.util.stream.Stream; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.insert.render.GeneralInsertRenderer; @@ -50,19 +50,21 @@ public SqlTable table() { return table; } - @NotNull + public StatementConfiguration statementConfiguration() { + return statementConfiguration; + } + public GeneralInsertStatementProvider render(RenderingStrategy renderingStrategy) { return GeneralInsertRenderer.withInsertModel(this) .withRenderingStrategy(renderingStrategy) - .withStatementConfiguration(statementConfiguration) .build() .render(); } public static class Builder { - private SqlTable table; + private @Nullable SqlTable table; private final List insertMappings = new ArrayList<>(); - private StatementConfiguration statementConfiguration; + private @Nullable StatementConfiguration statementConfiguration; public Builder withTable(SqlTable table) { this.table = table; diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/InsertColumnListModel.java b/src/main/java/org/mybatis/dynamic/sql/insert/InsertColumnListModel.java index 818868573..56131f0e9 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/InsertColumnListModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/InsertColumnListModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,13 +20,14 @@ import java.util.Objects; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.util.Validator; public class InsertColumnListModel { private final List> columns = new ArrayList<>(); - private InsertColumnListModel(List> columns) { + private InsertColumnListModel(@Nullable List> columns) { Objects.requireNonNull(columns); Validator.assertNotEmpty(columns, "ERROR.4"); //$NON-NLS-1$ this.columns.addAll(columns); @@ -37,7 +38,7 @@ public Stream> columns() { return columns.stream(); } - public static InsertColumnListModel of(List> columns) { + public static InsertColumnListModel of(@Nullable List> columns) { return new InsertColumnListModel(columns); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/InsertDSL.java b/src/main/java/org/mybatis/dynamic/sql/insert/InsertDSL.java index 73ac1e71a..d622ced8c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/InsertDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/InsertDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,7 +21,7 @@ import java.util.Objects; import java.util.function.Supplier; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.util.AbstractColumnMapping; @@ -49,7 +49,6 @@ public ColumnMappingFinisher map(SqlColumn column) { return new ColumnMappingFinisher<>(column); } - @NotNull @Override public InsertModel build() { return InsertModel.withRow(row) @@ -113,8 +112,8 @@ public InsertDSL toRow() { } public static class Builder { - private T row; - private SqlTable table; + private @Nullable T row; + private @Nullable SqlTable table; private final List columnMappings = new ArrayList<>(); public Builder withRow(T row) { diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/InsertModel.java b/src/main/java/org/mybatis/dynamic/sql/insert/InsertModel.java index 7f740051e..4495e32fd 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/InsertModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/InsertModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,7 +20,7 @@ import java.util.Objects; import java.util.stream.Stream; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.insert.render.InsertRenderer; import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider; @@ -52,7 +52,6 @@ public SqlTable table() { return table; } - @NotNull public InsertStatementProvider render(RenderingStrategy renderingStrategy) { return InsertRenderer.withInsertModel(this) .withRenderingStrategy(renderingStrategy) @@ -65,8 +64,8 @@ public static Builder withRow(T row) { } public static class Builder { - private SqlTable table; - private T row; + private @Nullable SqlTable table; + private @Nullable T row; private final List columnMappings = new ArrayList<>(); public Builder withTable(SqlTable table) { diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/InsertSelectDSL.java b/src/main/java/org/mybatis/dynamic/sql/insert/InsertSelectDSL.java index 20fad06ad..e7f8b5e09 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/InsertSelectDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/InsertSelectDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,7 +20,7 @@ import java.util.Objects; import java.util.function.Consumer; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; @@ -31,7 +31,7 @@ public class InsertSelectDSL implements Buildable, ConfigurableStatement { private final SqlTable table; - private final InsertColumnListModel columnList; + private final @Nullable InsertColumnListModel columnList; private final SelectModel selectModel; private final StatementConfiguration statementConfiguration = new StatementConfiguration(); @@ -47,7 +47,6 @@ private InsertSelectDSL(SqlTable table, SelectModel selectModel) { this.columnList = null; } - @NotNull @Override public InsertSelectModel build() { return InsertSelectModel.withTable(table) diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/InsertSelectModel.java b/src/main/java/org/mybatis/dynamic/sql/insert/InsertSelectModel.java index 4da2cef1c..36051700c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/InsertSelectModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/InsertSelectModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,7 +18,7 @@ import java.util.Objects; import java.util.Optional; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.insert.render.InsertSelectRenderer; @@ -28,7 +28,7 @@ public class InsertSelectModel { private final SqlTable table; - private final InsertColumnListModel columnList; + private final @Nullable InsertColumnListModel columnList; private final SelectModel selectModel; private final StatementConfiguration statementConfiguration; @@ -51,11 +51,13 @@ public Optional columnList() { return Optional.ofNullable(columnList); } - @NotNull + public StatementConfiguration statementConfiguration() { + return statementConfiguration; + } + public InsertSelectStatementProvider render(RenderingStrategy renderingStrategy) { return InsertSelectRenderer.withInsertSelectModel(this) .withRenderingStrategy(renderingStrategy) - .withStatementConfiguration(statementConfiguration) .build() .render(); } @@ -65,17 +67,17 @@ public static Builder withTable(SqlTable table) { } public static class Builder { - private SqlTable table; - private InsertColumnListModel columnList; - private SelectModel selectModel; - private StatementConfiguration statementConfiguration; + private @Nullable SqlTable table; + private @Nullable InsertColumnListModel columnList; + private @Nullable SelectModel selectModel; + private @Nullable StatementConfiguration statementConfiguration; public Builder withTable(SqlTable table) { this.table = table; return this; } - public Builder withColumnList(InsertColumnListModel columnList) { + public Builder withColumnList(@Nullable InsertColumnListModel columnList) { this.columnList = columnList; return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/MultiRowInsertDSL.java b/src/main/java/org/mybatis/dynamic/sql/insert/MultiRowInsertDSL.java index 1b8feaca3..8e5f30e49 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/MultiRowInsertDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/MultiRowInsertDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,7 +20,6 @@ import java.util.List; import java.util.Objects; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.util.AbstractColumnMapping; @@ -47,7 +46,6 @@ public ColumnMappingFinisher map(SqlColumn column) { return new ColumnMappingFinisher<>(column); } - @NotNull @Override public MultiRowInsertModel build() { return MultiRowInsertModel.withRecords(records) @@ -57,11 +55,11 @@ public MultiRowInsertModel build() { } @SafeVarargs - public static IntoGatherer insert(T... records) { - return MultiRowInsertDSL.insert(Arrays.asList(records)); + public static MultiRowInsertDSL.IntoGatherer insert(T... records) { + return insert(Arrays.asList(records)); } - public static IntoGatherer insert(Collection records) { + public static MultiRowInsertDSL.IntoGatherer insert(Collection records) { return new IntoGatherer<>(records); } diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/MultiRowInsertModel.java b/src/main/java/org/mybatis/dynamic/sql/insert/MultiRowInsertModel.java index 406c02b7a..435267652 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/MultiRowInsertModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/MultiRowInsertModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,7 +17,6 @@ import java.util.Collection; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.insert.render.MultiRowInsertRenderer; import org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider; import org.mybatis.dynamic.sql.render.RenderingStrategy; @@ -31,7 +30,6 @@ private MultiRowInsertModel(Builder builder) { Validator.assertNotEmpty(columnMappings, "ERROR.8"); //$NON-NLS-1$ } - @NotNull public MultiRowInsertStatementProvider render(RenderingStrategy renderingStrategy) { return MultiRowInsertRenderer.withMultiRowInsertModel(this) .withRenderingStrategy(renderingStrategy) diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/package-info.java b/src/main/java/org/mybatis/dynamic/sql/insert/package-info.java new file mode 100644 index 000000000..5ef9f74ea --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/insert/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.insert; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/BatchInsert.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/BatchInsert.java index 2759bb907..8af9e30fe 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/BatchInsert.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/BatchInsert.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,7 +19,8 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; + +import org.jspecify.annotations.Nullable; public class BatchInsert { private final String insertStatement; @@ -38,7 +39,7 @@ private BatchInsert(Builder builder) { public List> insertStatements() { return records.stream() .map(this::toInsertStatement) - .collect(Collectors.toList()); + .toList(); } private InsertStatementProvider toInsertStatement(T row) { @@ -57,7 +58,7 @@ public String getInsertStatementSQL() { } public List getRecords() { - return Collections.unmodifiableList(records); + return records; } public static Builder withRecords(List records) { @@ -65,7 +66,7 @@ public static Builder withRecords(List records) { } public static class Builder { - private String insertStatement; + private @Nullable String insertStatement; private final List records = new ArrayList<>(); public Builder withInsertStatement(String insertStatement) { diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/BatchInsertRenderer.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/BatchInsertRenderer.java index 5242b459b..875820c02 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/BatchInsertRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/BatchInsertRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,6 +17,7 @@ import java.util.Objects; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.insert.BatchInsertModel; import org.mybatis.dynamic.sql.render.RenderingStrategy; @@ -27,7 +28,8 @@ public class BatchInsertRenderer { private BatchInsertRenderer(Builder builder) { model = Objects.requireNonNull(builder.model); - visitor = new MultiRowValuePhraseVisitor(builder.renderingStrategy, "row"); //$NON-NLS-1$) + visitor = new MultiRowValuePhraseVisitor(Objects.requireNonNull(builder.renderingStrategy), + "row"); //$NON-NLS-1$) } public BatchInsert render() { @@ -47,8 +49,8 @@ public static Builder withBatchInsertModel(BatchInsertModel model) { } public static class Builder { - private BatchInsertModel model; - private RenderingStrategy renderingStrategy; + private @Nullable BatchInsertModel model; + private @Nullable RenderingStrategy renderingStrategy; public Builder withBatchInsertModel(BatchInsertModel model) { this.model = model; diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/DefaultGeneralInsertStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/DefaultGeneralInsertStatementProvider.java index de1c41266..eaa3d0911 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/DefaultGeneralInsertStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/DefaultGeneralInsertStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,14 +19,16 @@ import java.util.Map; import java.util.Objects; +import org.jspecify.annotations.Nullable; + public class DefaultGeneralInsertStatementProvider implements GeneralInsertStatementProvider, InsertSelectStatementProvider { private final String insertStatement; - private final Map parameters = new HashMap<>(); + private final Map parameters; private DefaultGeneralInsertStatementProvider(Builder builder) { insertStatement = Objects.requireNonNull(builder.insertStatement); - parameters.putAll(builder.parameters); + parameters = builder.parameters; } @Override @@ -44,7 +46,7 @@ public static Builder withInsertStatement(String insertStatement) { } public static class Builder { - private String insertStatement; + private @Nullable String insertStatement; private final Map parameters = new HashMap<>(); public Builder withInsertStatement(String insertStatement) { diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/DefaultInsertStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/DefaultInsertStatementProvider.java index aae15c34e..a16967456 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/DefaultInsertStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/DefaultInsertStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,32 +17,18 @@ import java.util.Objects; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; public class DefaultInsertStatementProvider implements InsertStatementProvider { private final String insertStatement; - // need to keep both row and record for now so we don't break - // old code. The MyBatis reflection utilities don't handle - // the case where the attribute name is different from the getter. - // - // MyBatis Generator version 1.4.1 (March 8, 2022) changed to use "row" instead of "record". - // Target March 2023 for removing "record" from MyBatis Dynamic SQL. - private final T record; private final T row; private DefaultInsertStatementProvider(Builder builder) { insertStatement = Objects.requireNonNull(builder.insertStatement); row = Objects.requireNonNull(builder.row); - record = row; } @Override - public T getRecord() { - return record; - } - - @Override - @NotNull public T getRow() { return row; } @@ -57,8 +43,8 @@ public static Builder withRow(T row) { } public static class Builder { - private String insertStatement; - private T row; + private @Nullable String insertStatement; + private @Nullable T row; public Builder withInsertStatement(String insertStatement) { this.insertStatement = insertStatement; diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/DefaultMultiRowInsertStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/DefaultMultiRowInsertStatementProvider.java index 2bb031e60..db8c9f489 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/DefaultMultiRowInsertStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/DefaultMultiRowInsertStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,6 +20,8 @@ import java.util.List; import java.util.Objects; +import org.jspecify.annotations.Nullable; + public class DefaultMultiRowInsertStatementProvider implements MultiRowInsertStatementProvider { private final List records; @@ -42,7 +44,7 @@ public List getRecords() { public static class Builder { private final List records = new ArrayList<>(); - private String insertStatement; + private @Nullable String insertStatement; public Builder withRecords(List records) { this.records.addAll(records); diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/FieldAndValueAndParameters.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/FieldAndValueAndParameters.java index 5c13aa9f6..d923020c0 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/FieldAndValueAndParameters.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/FieldAndValueAndParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,6 +20,8 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; + public class FieldAndValueAndParameters { private final String fieldName; private final String valuePhrase; @@ -48,8 +50,8 @@ public static Builder withFieldName(String fieldName) { } public static class Builder { - private String fieldName; - private String valuePhrase; + private @Nullable String fieldName; + private @Nullable String valuePhrase; private final Map parameters = new HashMap<>(); public Builder withFieldName(String fieldName) { @@ -62,7 +64,10 @@ public Builder withValuePhrase(String valuePhrase) { return this; } - public Builder withParameter(String key, Object value) { + public Builder withParameter(String key, @Nullable Object value) { + // the value can be null because a parameter type converter may return null + + //noinspection DataFlowIssue parameters.put(key, value); return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/FieldAndValueCollector.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/FieldAndValueCollector.java index c6692b2c8..7af466766 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/FieldAndValueCollector.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/FieldAndValueCollector.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertRenderer.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertRenderer.java index afc1d4b98..08eddb54f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,7 +18,7 @@ import java.util.Objects; import java.util.Optional; -import org.mybatis.dynamic.sql.configuration.StatementConfiguration; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.insert.GeneralInsertModel; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.render.RenderingStrategy; @@ -31,8 +31,9 @@ public class GeneralInsertRenderer { private GeneralInsertRenderer(Builder builder) { model = Objects.requireNonNull(builder.model); - RenderingContext renderingContext = RenderingContext.withRenderingStrategy(builder.renderingStrategy) - .withStatementConfiguration(builder.statementConfiguration) + RenderingContext renderingContext = RenderingContext + .withRenderingStrategy(Objects.requireNonNull(builder.renderingStrategy)) + .withStatementConfiguration(model.statementConfiguration()) .build(); visitor = new GeneralInsertValuePhraseVisitor(renderingContext); } @@ -40,8 +41,7 @@ private GeneralInsertRenderer(Builder builder) { public GeneralInsertStatementProvider render() { FieldAndValueCollector collector = model.columnMappings() .map(m -> m.accept(visitor)) - .filter(Optional::isPresent) - .map(Optional::get) + .flatMap(Optional::stream) .collect(FieldAndValueCollector.collect()); Validator.assertFalse(collector.isEmpty(), "ERROR.9"); //$NON-NLS-1$ @@ -58,9 +58,8 @@ public static Builder withInsertModel(GeneralInsertModel model) { } public static class Builder { - private GeneralInsertModel model; - private RenderingStrategy renderingStrategy; - private StatementConfiguration statementConfiguration; + private @Nullable GeneralInsertModel model; + private @Nullable RenderingStrategy renderingStrategy; public Builder withInsertModel(GeneralInsertModel model) { this.model = model; @@ -72,11 +71,6 @@ public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { return this; } - public Builder withStatementConfiguration(StatementConfiguration statementConfiguration) { - this.statementConfiguration = statementConfiguration; - return this; - } - public GeneralInsertRenderer build() { return new GeneralInsertRenderer(this); } diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertStatementProvider.java index a3534059a..394a88ac2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertValuePhraseVisitor.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertValuePhraseVisitor.java index 7a7bcf25c..914d710cf 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertValuePhraseVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertValuePhraseVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,6 +18,7 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.render.RenderedParameterInfo; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.AbstractColumnMapping; @@ -74,7 +75,7 @@ public Optional visit(ValueWhenPresentMapping } private Optional buildValueFragment(AbstractColumnMapping mapping, - Object value) { + @Nullable Object value) { return buildFragment(mapping, value); } @@ -84,7 +85,7 @@ private Optional buildNullFragment(AbstractColumnMap .buildOptional(); } - private Optional buildFragment(AbstractColumnMapping mapping, Object value) { + private Optional buildFragment(AbstractColumnMapping mapping, @Nullable Object value) { RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(mapping.column()); return FieldAndValueAndParameters.withFieldName(mapping.columnName()) diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderer.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderer.java index 431b3df8f..9ff5858be 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,6 +18,7 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.insert.InsertModel; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.util.Validator; @@ -29,14 +30,13 @@ public class InsertRenderer { private InsertRenderer(Builder builder) { model = Objects.requireNonNull(builder.model); - visitor = new ValuePhraseVisitor(builder.renderingStrategy); + visitor = new ValuePhraseVisitor(Objects.requireNonNull(builder.renderingStrategy)); } public InsertStatementProvider render() { FieldAndValueCollector collector = model.columnMappings() .map(m -> m.accept(visitor)) - .filter(Optional::isPresent) - .map(Optional::get) + .flatMap(Optional::stream) .collect(FieldAndValueCollector.collect()); Validator.assertFalse(collector.isEmpty(), "ERROR.10"); //$NON-NLS-1$ @@ -53,8 +53,8 @@ public static Builder withInsertModel(InsertModel model) { } public static class Builder { - private InsertModel model; - private RenderingStrategy renderingStrategy; + private @Nullable InsertModel model; + private @Nullable RenderingStrategy renderingStrategy; public Builder withInsertModel(InsertModel model) { this.model = model; diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderingUtilities.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderingUtilities.java index 31bbf02ed..de529a2b4 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderingUtilities.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderingUtilities.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -31,6 +31,6 @@ public static String calculateInsertStatement(SqlTable table, FieldAndValueColle } public static String calculateInsertStatementStart(SqlTable table) { - return "insert into " + table.tableNameAtRuntime(); //$NON-NLS-1$ + return "insert into " + table.tableName(); //$NON-NLS-1$ } } diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertSelectRenderer.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertSelectRenderer.java index 16afe6767..7740309bd 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertSelectRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertSelectRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,20 +15,19 @@ */ package org.mybatis.dynamic.sql.insert.render; -import static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore; +import static org.mybatis.dynamic.sql.util.StringUtilities.spaceAfter; import java.util.Objects; -import java.util.Optional; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlColumn; -import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.insert.InsertColumnListModel; import org.mybatis.dynamic.sql.insert.InsertSelectModel; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.render.RenderingStrategy; -import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; -import org.mybatis.dynamic.sql.util.StringUtilities; +import org.mybatis.dynamic.sql.select.render.SubQueryRenderer; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; public class InsertSelectRenderer { @@ -37,35 +36,35 @@ public class InsertSelectRenderer { private InsertSelectRenderer(Builder builder) { model = Objects.requireNonNull(builder.model); - renderingContext = RenderingContext.withRenderingStrategy(builder.renderingStrategy) - .withStatementConfiguration(builder.statementConfiguration) + renderingContext = RenderingContext.withRenderingStrategy(Objects.requireNonNull(builder.renderingStrategy)) + .withStatementConfiguration(model.statementConfiguration()) .build(); } public InsertSelectStatementProvider render() { - SelectStatementProvider selectStatement = model.selectModel().render(renderingContext); - String statementStart = InsertRenderingUtilities.calculateInsertStatementStart(model.table()); - Optional columnsPhrase = calculateColumnsPhrase(); - String renderedSelectStatement = selectStatement.getSelectStatement(); + String columnsPhrase = calculateColumnsPhrase(); + String prefix = statementStart + spaceAfter(columnsPhrase); - String insertStatement = statementStart - + columnsPhrase.map(StringUtilities::spaceBefore).orElse("") //$NON-NLS-1$ - + spaceBefore(renderedSelectStatement); + FragmentAndParameters fragmentAndParameters = SubQueryRenderer.withSelectModel(model.selectModel()) + .withRenderingContext(renderingContext) + .withPrefix(prefix) + .build() + .render(); - return DefaultGeneralInsertStatementProvider.withInsertStatement(insertStatement) - .withParameters(selectStatement.getParameters()) + return DefaultGeneralInsertStatementProvider.withInsertStatement(fragmentAndParameters.fragment()) + .withParameters(fragmentAndParameters.parameters()) .build(); } - private Optional calculateColumnsPhrase() { - return model.columnList().map(this::calculateColumnsPhrase); + private String calculateColumnsPhrase() { + return model.columnList().map(this::calculateColumnsPhrase).orElse(""); //$NON-NLS-1$ } private String calculateColumnsPhrase(InsertColumnListModel columnList) { return columnList.columns() .map(SqlColumn::name) - .collect(Collectors.joining(", ", "(", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + .collect(Collectors.joining(", ", " (", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } public static Builder withInsertSelectModel(InsertSelectModel model) { @@ -73,9 +72,8 @@ public static Builder withInsertSelectModel(InsertSelectModel model) { } public static class Builder { - private InsertSelectModel model; - private RenderingStrategy renderingStrategy; - private StatementConfiguration statementConfiguration; + private @Nullable InsertSelectModel model; + private @Nullable RenderingStrategy renderingStrategy; public Builder withInsertSelectModel(InsertSelectModel model) { this.model = model; @@ -87,11 +85,6 @@ public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { return this; } - public Builder withStatementConfiguration(StatementConfiguration statementConfiguration) { - this.statementConfiguration = statementConfiguration; - return this; - } - public InsertSelectRenderer build() { return new InsertSelectRenderer(this); } diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertSelectStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertSelectStatementProvider.java index bb25ab835..5545da79a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertSelectStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertSelectStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertStatementProvider.java index 6658320f9..bd40a0e1f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,25 +15,12 @@ */ package org.mybatis.dynamic.sql.insert.render; -import org.jetbrains.annotations.NotNull; - public interface InsertStatementProvider { - /** - * Return the row associated with this insert statement. - * - * @return the row associated with this insert statement. - * - * @deprecated in favor of {@link InsertStatementProvider#getRow()} - */ - @Deprecated - T getRecord(); - /** * Return the row associated with this insert statement. * * @return the row associated with this insert statement. */ - @NotNull T getRow(); /** diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowInsertRenderer.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowInsertRenderer.java index 10479ec4b..5f3146a77 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowInsertRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowInsertRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,6 +19,7 @@ import java.util.Objects; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.insert.MultiRowInsertModel; import org.mybatis.dynamic.sql.render.RenderingStrategy; @@ -30,7 +31,8 @@ public class MultiRowInsertRenderer { private MultiRowInsertRenderer(Builder builder) { model = Objects.requireNonNull(builder.model); // the prefix is a generic format that will be resolved below with String.format(...) - visitor = new MultiRowValuePhraseVisitor(builder.renderingStrategy, "records[%s]"); //$NON-NLS-1$ + visitor = new MultiRowValuePhraseVisitor(Objects.requireNonNull(builder.renderingStrategy), + "records[%s]"); //$NON-NLS-1$ } public MultiRowInsertStatementProvider render() { @@ -58,8 +60,8 @@ public static Builder withMultiRowInsertModel(MultiRowInsertModel mode } public static class Builder { - private MultiRowInsertModel model; - private RenderingStrategy renderingStrategy; + private @Nullable MultiRowInsertModel model; + private @Nullable RenderingStrategy renderingStrategy; public Builder withMultiRowInsertModel(MultiRowInsertModel model) { this.model = model; diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowInsertStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowInsertStatementProvider.java index 6b520d617..de5d87797 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowInsertStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowInsertStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowValuePhraseVisitor.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowValuePhraseVisitor.java index 9a9bbc3ce..ba2d2ffa5 100644 --- a/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowValuePhraseVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowValuePhraseVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,8 +15,6 @@ */ package org.mybatis.dynamic.sql.insert.render; -import java.util.Objects; - import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.util.ConstantMapping; @@ -32,8 +30,8 @@ public class MultiRowValuePhraseVisitor extends MultiRowInsertMappingVisitor aliasForColumn(SqlTable table) { if (alias.isPresent()) { return alias; } else { - return Optional.of(table.tableNameAtRuntime()); + return Optional.of(table.tableName()); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/render/MyBatis3RenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/render/MyBatis3RenderingStrategy.java index d104b1186..1d5563c2a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/MyBatis3RenderingStrategy.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/MyBatis3RenderingStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/render/RenderedParameterInfo.java b/src/main/java/org/mybatis/dynamic/sql/render/RenderedParameterInfo.java index 909f36c3c..5c8187ef2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/RenderedParameterInfo.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/RenderedParameterInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,20 +17,9 @@ import java.util.Objects; -public class RenderedParameterInfo { - private final String parameterMapKey; - private final String renderedPlaceHolder; - +public record RenderedParameterInfo(String parameterMapKey, String renderedPlaceHolder) { public RenderedParameterInfo(String parameterMapKey, String renderedPlaceHolder) { this.parameterMapKey = Objects.requireNonNull(parameterMapKey); this.renderedPlaceHolder = Objects.requireNonNull(renderedPlaceHolder); } - - public String parameterMapKey() { - return parameterMapKey; - } - - public String renderedPlaceHolder() { - return renderedPlaceHolder; - } } diff --git a/src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java b/src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java index a23c8d07a..4e1457067 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/RenderingContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,6 +20,7 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.SqlColumn; import org.mybatis.dynamic.sql.SqlTable; @@ -37,7 +38,7 @@ public class RenderingContext { private final RenderingStrategy renderingStrategy; private final AtomicInteger sequence; private final TableAliasCalculator tableAliasCalculator; - private final String configuredParameterName; + private final @Nullable String configuredParameterName; private final String calculatedParameterName; private final StatementConfiguration statementConfiguration; @@ -53,27 +54,31 @@ private RenderingContext(Builder builder) { : builder.parameterName + "." + RenderingStrategy.DEFAULT_PARAMETER_PREFIX; //$NON-NLS-1$ } - public TableAliasCalculator tableAliasCalculator() { - // this method can be removed when the renderWithTableAlias method is removed from BasicColumn - return tableAliasCalculator; - } - private String nextMapKey() { return renderingStrategy.formatParameterMapKey(sequence); } - private String renderedPlaceHolder(String mapKey) { - return renderingStrategy.getFormattedJdbcPlaceholder(calculatedParameterName, mapKey); - } - private String renderedPlaceHolder(String mapKey, BindableColumn column) { return column.renderingStrategy().orElse(renderingStrategy) .getFormattedJdbcPlaceholder(column, calculatedParameterName, mapKey); } - public RenderedParameterInfo calculateParameterInfo() { - String mapKey = nextMapKey(); - return new RenderedParameterInfo(mapKey, renderedPlaceHolder(mapKey)); + public RenderedParameterInfo calculateFetchFirstRowsParameterInfo() { + String mapKey = renderingStrategy.formatParameterMapKeyForFetchFirstRows(sequence); + return new RenderedParameterInfo(mapKey, + renderingStrategy.getFormattedJdbcPlaceholderForPagingParameters(calculatedParameterName, mapKey)); + } + + public RenderedParameterInfo calculateLimitParameterInfo() { + String mapKey = renderingStrategy.formatParameterMapKeyForLimit(sequence); + return new RenderedParameterInfo(mapKey, + renderingStrategy.getFormattedJdbcPlaceholderForPagingParameters(calculatedParameterName, mapKey)); + } + + public RenderedParameterInfo calculateOffsetParameterInfo() { + String mapKey = renderingStrategy.formatParameterMapKeyForOffset(sequence); + return new RenderedParameterInfo(mapKey, + renderingStrategy.getFormattedJdbcPlaceholderForPagingParameters(calculatedParameterName, mapKey)); } public RenderedParameterInfo calculateParameterInfo(BindableColumn column) { @@ -93,8 +98,8 @@ public String aliasedColumnName(SqlColumn column, String explicitAlias) { public String aliasedTableName(SqlTable table) { return tableAliasCalculator.aliasForTable(table) - .map(a -> table.tableNameAtRuntime() + spaceBefore(a)) - .orElseGet(table::tableNameAtRuntime); + .map(a -> table.tableName() + spaceBefore(a)) + .orElseGet(table::tableName); } public boolean isNonRenderingClauseAllowed() { @@ -130,11 +135,11 @@ public static Builder withRenderingStrategy(RenderingStrategy renderingStrategy) } public static class Builder { - private RenderingStrategy renderingStrategy; - private AtomicInteger sequence; - private TableAliasCalculator tableAliasCalculator = TableAliasCalculator.empty(); - private String parameterName; - private StatementConfiguration statementConfiguration; + private @Nullable RenderingStrategy renderingStrategy; + private @Nullable AtomicInteger sequence; + private @Nullable TableAliasCalculator tableAliasCalculator = TableAliasCalculator.empty(); + private @Nullable String parameterName; + private @Nullable StatementConfiguration statementConfiguration; public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { this.renderingStrategy = renderingStrategy; @@ -151,7 +156,7 @@ public Builder withTableAliasCalculator(TableAliasCalculator tableAliasCalculato return this; } - public Builder withParameterName(String parameterName) { + public Builder withParameterName(@Nullable String parameterName) { this.parameterName = parameterName; return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategies.java b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategies.java index 4b7837a46..d3c17af78 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategies.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategies.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java index 70b369c73..adf66115c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -36,10 +36,55 @@ public abstract class RenderingStrategy { public static final String DEFAULT_PARAMETER_PREFIX = "parameters"; //$NON-NLS-1$ + /** + * Generate a unique key that can be used to place a parameter value in the parameter map. + * + * @param sequence a sequence for calculating a unique value + * @return a key used to place the parameter value in the parameter map + */ public String formatParameterMapKey(AtomicInteger sequence) { return "p" + sequence.getAndIncrement(); //$NON-NLS-1$ } + /** + * Return a parameter map key intended as a parameter for a fetch first query. + * + *

By default, this parameter is treated the same as any other. This method is a hook to support + * MyBatis Spring Batch. + * + * @param sequence a sequence for calculating a unique value + * @return a key used to place the parameter value in the parameter map + */ + public String formatParameterMapKeyForFetchFirstRows(AtomicInteger sequence) { + return formatParameterMapKey(sequence); + } + + /** + * Return a parameter map key intended as a parameter for a limit query. + * + *

By default, this parameter is treated the same as any other. This method is a hook to support + * MyBatis Spring Batch. + * + * @param sequence a sequence for calculating a unique value + * @return a key used to place the parameter value in the parameter map + */ + public String formatParameterMapKeyForLimit(AtomicInteger sequence) { + return formatParameterMapKey(sequence); + } + + /** + * Return a parameter map key intended as a parameter for a query offset. + * + *

By default, this parameter is treated the same as any other. This method is a hook to support + * MyBatis Spring Batch. + * + * @param sequence a sequence for calculating a unique value + * @return a key used to place the parameter value in the parameter map + */ + public String formatParameterMapKeyForOffset(AtomicInteger sequence) { + return formatParameterMapKey(sequence); + } + /** * This method generates a binding for a parameter to a placeholder in a generated SQL statement. * @@ -78,6 +123,23 @@ public String formatParameterMapKey(AtomicInteger sequence) { */ public abstract String getFormattedJdbcPlaceholder(String prefix, String parameterName); + /** + * This method generates a binding for a parameter to a placeholder in a generated SQL statement. + * + *

This method is used to generate bindings for limit, offset, and fetch first parameters. By default, these + * parameters are treated the same as any other. This method supports MyBatis Spring Batch integration where the + * parameter keys have predefined values and need special handling. + * + * @param prefix parameter prefix used for locating the parameters in a SQL provider object. Typically, will be + * {@link RenderingStrategy#DEFAULT_PARAMETER_PREFIX}. This is ignored for Spring. + * @param parameterName name of the parameter. Typically generated by calling + * {@link RenderingStrategy#formatParameterMapKey(AtomicInteger)} + * @return the generated binding + */ + public String getFormattedJdbcPlaceholderForPagingParameters(String prefix, String parameterName) { + return getFormattedJdbcPlaceholder(prefix, parameterName); + } + /** * This method generates a binding for a parameter to a placeholder in a row based insert statement. * diff --git a/src/main/java/org/mybatis/dynamic/sql/render/SpringNamedParameterRenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/render/SpringNamedParameterRenderingStrategy.java index e11ead4e2..ccdbae51f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/SpringNamedParameterRenderingStrategy.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/SpringNamedParameterRenderingStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculator.java b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculator.java index faf7fc9a4..a90d337f2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculator.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java index 3aa75e98b..e843751d3 100644 --- a/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java +++ b/src/main/java/org/mybatis/dynamic/sql/render/TableAliasCalculatorWithParent.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,6 +18,7 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlTable; public class TableAliasCalculatorWithParent implements TableAliasCalculator { @@ -48,8 +49,8 @@ public Optional aliasForTable(SqlTable table) { } public static class Builder { - private TableAliasCalculator parent; - private TableAliasCalculator child; + private @Nullable TableAliasCalculator parent; + private @Nullable TableAliasCalculator child; public Builder withParent(TableAliasCalculator parent) { this.parent = parent; diff --git a/src/main/java/org/mybatis/dynamic/sql/render/package-info.java b/src/main/java/org/mybatis/dynamic/sql/render/package-info.java new file mode 100644 index 000000000..770ff3d47 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/render/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.render; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/AbstractHavingFinisher.java b/src/main/java/org/mybatis/dynamic/sql/select/AbstractHavingFinisher.java index 2a668c72c..872b964eb 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/AbstractHavingFinisher.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/AbstractHavingFinisher.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,6 +17,7 @@ import java.util.List; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AndOrCriteriaGroup; import org.mybatis.dynamic.sql.SqlCriterion; import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionDSL; @@ -27,7 +28,7 @@ void initialize(SqlCriterion sqlCriterion) { setInitialCriterion(sqlCriterion, StatementType.HAVING); } - void initialize(SqlCriterion sqlCriterion, List subCriteria) { + void initialize(@Nullable SqlCriterion sqlCriterion, List subCriteria) { setInitialCriterion(sqlCriterion, StatementType.HAVING); super.subCriteria.addAll(subCriteria); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/AbstractHavingStarter.java b/src/main/java/org/mybatis/dynamic/sql/select/AbstractHavingStarter.java index 4eccfc2f6..1090fb064 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/AbstractHavingStarter.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/AbstractHavingStarter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -22,17 +22,17 @@ import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.ColumnAndConditionCriterion; import org.mybatis.dynamic.sql.CriteriaGroup; +import org.mybatis.dynamic.sql.RenderableCondition; import org.mybatis.dynamic.sql.SqlCriterion; -import org.mybatis.dynamic.sql.VisitableCondition; -public abstract class AbstractHavingStarter> { +public interface AbstractHavingStarter> { - public F having(BindableColumn column, VisitableCondition condition, + default F having(BindableColumn column, RenderableCondition condition, AndOrCriteriaGroup... subCriteria) { return having(column, condition, Arrays.asList(subCriteria)); } - public F having(BindableColumn column, VisitableCondition condition, + default F having(BindableColumn column, RenderableCondition condition, List subCriteria) { SqlCriterion sqlCriterion = ColumnAndConditionCriterion.withColumn(column) .withCondition(condition) @@ -42,11 +42,11 @@ public F having(BindableColumn column, VisitableCondition condition, return initialize(sqlCriterion); } - public F having(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) { + default F having(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) { return having(initialCriterion, Arrays.asList(subCriteria)); } - public F having(SqlCriterion initialCriterion, List subCriteria) { + default F having(SqlCriterion initialCriterion, List subCriteria) { SqlCriterion sqlCriterion = new CriteriaGroup.Builder() .withInitialCriterion(initialCriterion) .withSubCriteria(subCriteria) @@ -55,9 +55,9 @@ public F having(SqlCriterion initialCriterion, List subCrite return initialize(sqlCriterion); } - protected abstract F having(); + F having(); - public F applyHaving(HavingApplier havingApplier) { + default F applyHaving(HavingApplier havingApplier) { F finisher = having(); havingApplier.accept(finisher); return finisher; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java index d800a02ff..f1fd826a8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/AbstractQueryExpressionDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -23,12 +23,14 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; +import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AndOrCriteriaGroup; +import org.mybatis.dynamic.sql.SqlCriterion; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.TableExpression; import org.mybatis.dynamic.sql.exception.DuplicateTableAliasException; -import org.mybatis.dynamic.sql.select.join.JoinCriterion; import org.mybatis.dynamic.sql.select.join.JoinModel; import org.mybatis.dynamic.sql.select.join.JoinSpecification; import org.mybatis.dynamic.sql.select.join.JoinType; @@ -38,9 +40,9 @@ public abstract class AbstractQueryExpressionDSL, T extends AbstractQueryExpressionDSL> - extends AbstractWhereStarter { + implements AbstractWhereStarter { - private final List joinSpecificationBuilders = new ArrayList<>(); + private final List> joinSpecificationSuppliers = new ArrayList<>(); private final Map tableAliases = new HashMap<>(); private final TableExpression table; @@ -52,151 +54,151 @@ public TableExpression table() { return table; } - public T join(SqlTable joinTable, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { - addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.INNER, Arrays.asList(andJoinCriteria)); + public T join(SqlTable joinTable, SqlCriterion onJoinCriterion, + AndOrCriteriaGroup... andJoinCriteria) { + addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.INNER, Arrays.asList(andJoinCriteria)); return getThis(); } - public T join(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { + public T join(SqlTable joinTable, String tableAlias, SqlCriterion onJoinCriterion, + AndOrCriteriaGroup... andJoinCriteria) { addTableAlias(joinTable, tableAlias); return join(joinTable, onJoinCriterion, andJoinCriteria); } - public T join(SqlTable joinTable, JoinCriterion onJoinCriterion, - List> andJoinCriteria) { - addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.INNER, andJoinCriteria); + public T join(SqlTable joinTable, @Nullable SqlCriterion onJoinCriterion, + List andJoinCriteria) { + addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.INNER, andJoinCriteria); return getThis(); } - public T join(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - List> andJoinCriteria) { + public T join(SqlTable joinTable, String tableAlias, @Nullable SqlCriterion onJoinCriterion, + List andJoinCriteria) { addTableAlias(joinTable, tableAlias); return join(joinTable, onJoinCriterion, andJoinCriteria); } - public T join(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, - List> andJoinCriteria) { - addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.INNER, + public T join(Buildable subQuery, @Nullable String tableAlias, @Nullable SqlCriterion onJoinCriterion, + List andJoinCriteria) { + addJoinSpecificationSupplier(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.INNER, andJoinCriteria); return getThis(); } - public T leftJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { - addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.LEFT, Arrays.asList(andJoinCriteria)); + public T leftJoin(SqlTable joinTable, SqlCriterion onJoinCriterion, + AndOrCriteriaGroup... andJoinCriteria) { + addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.LEFT, Arrays.asList(andJoinCriteria)); return getThis(); } - public T leftJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { + public T leftJoin(SqlTable joinTable, String tableAlias, SqlCriterion onJoinCriterion, + AndOrCriteriaGroup... andJoinCriteria) { addTableAlias(joinTable, tableAlias); return leftJoin(joinTable, onJoinCriterion, andJoinCriteria); } - public T leftJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, - List> andJoinCriteria) { - addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.LEFT, andJoinCriteria); + public T leftJoin(SqlTable joinTable, @Nullable SqlCriterion onJoinCriterion, + List andJoinCriteria) { + addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.LEFT, andJoinCriteria); return getThis(); } - public T leftJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - List> andJoinCriteria) { + public T leftJoin(SqlTable joinTable, String tableAlias, @Nullable SqlCriterion onJoinCriterion, + List andJoinCriteria) { addTableAlias(joinTable, tableAlias); return leftJoin(joinTable, onJoinCriterion, andJoinCriteria); } - public T leftJoin(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, - List> andJoinCriteria) { - addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.LEFT, + public T leftJoin(Buildable subQuery, @Nullable String tableAlias, + @Nullable SqlCriterion onJoinCriterion, List andJoinCriteria) { + addJoinSpecificationSupplier(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.LEFT, andJoinCriteria); return getThis(); } - public T rightJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { - addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.RIGHT, Arrays.asList(andJoinCriteria)); + public T rightJoin(SqlTable joinTable, SqlCriterion onJoinCriterion, + AndOrCriteriaGroup... andJoinCriteria) { + addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.RIGHT, Arrays.asList(andJoinCriteria)); return getThis(); } - public T rightJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { + public T rightJoin(SqlTable joinTable, String tableAlias, SqlCriterion onJoinCriterion, + AndOrCriteriaGroup... andJoinCriteria) { addTableAlias(joinTable, tableAlias); return rightJoin(joinTable, onJoinCriterion, andJoinCriteria); } - public T rightJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, - List> andJoinCriteria) { - addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.RIGHT, andJoinCriteria); + public T rightJoin(SqlTable joinTable, @Nullable SqlCriterion onJoinCriterion, + List andJoinCriteria) { + addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.RIGHT, andJoinCriteria); return getThis(); } - public T rightJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - List> andJoinCriteria) { + public T rightJoin(SqlTable joinTable, String tableAlias, @Nullable SqlCriterion onJoinCriterion, + List andJoinCriteria) { addTableAlias(joinTable, tableAlias); return rightJoin(joinTable, onJoinCriterion, andJoinCriteria); } - public T rightJoin(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, - List> andJoinCriteria) { - addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.RIGHT, + public T rightJoin(Buildable subQuery, @Nullable String tableAlias, + @Nullable SqlCriterion onJoinCriterion, List andJoinCriteria) { + addJoinSpecificationSupplier(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.RIGHT, andJoinCriteria); return getThis(); } - public T fullJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { - addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.FULL, Arrays.asList(andJoinCriteria)); + public T fullJoin(SqlTable joinTable, SqlCriterion onJoinCriterion, + AndOrCriteriaGroup... andJoinCriteria) { + addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.FULL, Arrays.asList(andJoinCriteria)); return getThis(); } - public T fullJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - JoinCriterion... andJoinCriteria) { + public T fullJoin(SqlTable joinTable, String tableAlias, SqlCriterion onJoinCriterion, + AndOrCriteriaGroup... andJoinCriteria) { addTableAlias(joinTable, tableAlias); return fullJoin(joinTable, onJoinCriterion, andJoinCriteria); } - public T fullJoin(SqlTable joinTable, JoinCriterion onJoinCriterion, - List> andJoinCriteria) { - addJoinSpecificationBuilder(joinTable, onJoinCriterion, JoinType.FULL, andJoinCriteria); + public T fullJoin(SqlTable joinTable, @Nullable SqlCriterion onJoinCriterion, + List andJoinCriteria) { + addJoinSpecificationSupplier(joinTable, onJoinCriterion, JoinType.FULL, andJoinCriteria); return getThis(); } - public T fullJoin(SqlTable joinTable, String tableAlias, JoinCriterion onJoinCriterion, - List> andJoinCriteria) { + public T fullJoin(SqlTable joinTable, String tableAlias, @Nullable SqlCriterion onJoinCriterion, + List andJoinCriteria) { addTableAlias(joinTable, tableAlias); return fullJoin(joinTable, onJoinCriterion, andJoinCriteria); } - public T fullJoin(Buildable subQuery, String tableAlias, JoinCriterion onJoinCriterion, - List> andJoinCriteria) { - addJoinSpecificationBuilder(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.FULL, + public T fullJoin(Buildable subQuery, @Nullable String tableAlias, + @Nullable SqlCriterion onJoinCriterion, List andJoinCriteria) { + addJoinSpecificationSupplier(buildSubQuery(subQuery, tableAlias), onJoinCriterion, JoinType.FULL, andJoinCriteria); return getThis(); } - private void addJoinSpecificationBuilder(TableExpression joinTable, JoinCriterion onJoinCriterion, - JoinType joinType, List> andJoinCriteria) { - joinSpecificationBuilders.add(new JoinSpecification.Builder() + private void addJoinSpecificationSupplier(TableExpression joinTable, @Nullable SqlCriterion onJoinCriterion, + JoinType joinType, List andJoinCriteria) { + joinSpecificationSuppliers.add(() -> new JoinSpecification.Builder() .withJoinTable(joinTable) .withJoinType(joinType) - .withJoinCriterion(onJoinCriterion) - .withJoinCriteria(andJoinCriteria)); + .withInitialCriterion(onJoinCriterion) + .withSubCriteria(andJoinCriteria).build()); } - protected void addJoinSpecificationBuilder(JoinSpecification.Builder builder) { - joinSpecificationBuilders.add(builder); + protected void addJoinSpecificationSupplier(Supplier joinSpecificationSupplier) { + joinSpecificationSuppliers.add(joinSpecificationSupplier); } protected Optional buildJoinModel() { - if (joinSpecificationBuilders.isEmpty()) { + if (joinSpecificationSuppliers.isEmpty()) { return Optional.empty(); } - return Optional.of(JoinModel.of(joinSpecificationBuilders.stream() - .map(JoinSpecification.Builder::build) - .collect(Collectors.toList()))); + return Optional.of(JoinModel.of(joinSpecificationSuppliers.stream() + .map(Supplier::get) + .toList())); } protected void addTableAlias(SqlTable table, String tableAlias) { @@ -217,7 +219,7 @@ protected static SubQuery buildSubQuery(Buildable selectModel) { .build(); } - protected static SubQuery buildSubQuery(Buildable selectModel, String alias) { + protected static SubQuery buildSubQuery(Buildable selectModel, @Nullable String alias) { return new SubQuery.Builder() .withSelectModel(selectModel.build()) .withAlias(alias) diff --git a/src/main/java/org/mybatis/dynamic/sql/select/AbstractSelectModel.java b/src/main/java/org/mybatis/dynamic/sql/select/AbstractSelectModel.java index 49c20789f..51c3d4fe7 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/AbstractSelectModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/AbstractSelectModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,12 +18,13 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.common.OrderByModel; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; public abstract class AbstractSelectModel { - private final OrderByModel orderByModel; - private final PagingModel pagingModel; + private final @Nullable OrderByModel orderByModel; + private final @Nullable PagingModel pagingModel; protected final StatementConfiguration statementConfiguration; protected AbstractSelectModel(AbstractBuilder builder) { @@ -40,17 +41,21 @@ public Optional pagingModel() { return Optional.ofNullable(pagingModel); } + public StatementConfiguration statementConfiguration() { + return statementConfiguration; + } + public abstract static class AbstractBuilder> { - private OrderByModel orderByModel; - private PagingModel pagingModel; - private StatementConfiguration statementConfiguration; + private @Nullable OrderByModel orderByModel; + private @Nullable PagingModel pagingModel; + private @Nullable StatementConfiguration statementConfiguration; - public T withOrderByModel(OrderByModel orderByModel) { + public T withOrderByModel(@Nullable OrderByModel orderByModel) { this.orderByModel = orderByModel; return getThis(); } - public T withPagingModel(PagingModel pagingModel) { + public T withPagingModel(@Nullable PagingModel pagingModel) { this.pagingModel = pagingModel; return getThis(); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/ColumnSortSpecification.java b/src/main/java/org/mybatis/dynamic/sql/select/ColumnSortSpecification.java index b8503a971..aa74099b5 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/ColumnSortSpecification.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/ColumnSortSpecification.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,34 +19,31 @@ import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.SqlColumn; +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; public class ColumnSortSpecification implements SortSpecification { private final String tableAlias; private final SqlColumn column; - private final boolean isDescending; + private final String descendingPhrase; public ColumnSortSpecification(String tableAlias, SqlColumn column) { - this(tableAlias, column, false); + this(tableAlias, column, ""); //$NON-NLS-1$ } - private ColumnSortSpecification(String tableAlias, SqlColumn column, boolean isDescending) { + private ColumnSortSpecification(String tableAlias, SqlColumn column, String descendingPhrase) { this.tableAlias = Objects.requireNonNull(tableAlias); this.column = Objects.requireNonNull(column); - this.isDescending = isDescending; + this.descendingPhrase = descendingPhrase; } @Override public SortSpecification descending() { - return new ColumnSortSpecification(tableAlias, column, true); + return new ColumnSortSpecification(tableAlias, column, " DESC"); //$NON-NLS-1$ } @Override - public String orderByName() { - return tableAlias + "." + column.name(); //$NON-NLS-1$ - } - - @Override - public boolean isDescending() { - return isDescending; + public FragmentAndParameters renderForOrderBy(RenderingContext renderingContext) { + return FragmentAndParameters.fromFragment(tableAlias + "." + column.name() + descendingPhrase); //$NON-NLS-1$ } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/CountDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/CountDSL.java index a684bae66..48e790a03 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/CountDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/CountDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,13 +19,12 @@ import java.util.function.Consumer; import java.util.function.Function; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.util.Buildable; -import org.mybatis.dynamic.sql.util.Utilities; import org.mybatis.dynamic.sql.where.AbstractWhereFinisher; import org.mybatis.dynamic.sql.where.EmbeddedWhereModel; @@ -42,7 +41,7 @@ public class CountDSL extends AbstractQueryExpressionDSL.CountWhe implements Buildable { private final Function adapterFunction; - private CountWhereBuilder whereBuilder; + private @Nullable CountWhereBuilder whereBuilder; private final BasicColumn countColumn; private final StatementConfiguration statementConfiguration = new StatementConfiguration(); @@ -54,11 +53,10 @@ private CountDSL(BasicColumn countColumn, SqlTable table, Function { +public class HavingDSL implements AbstractHavingStarter { private final StandaloneHavingFinisher havingFinisher = new StandaloneHavingFinisher(); @Override - protected StandaloneHavingFinisher having() { + public StandaloneHavingFinisher having() { return havingFinisher; } @@ -36,7 +35,6 @@ protected StandaloneHavingFinisher getThis() { return this; } - @NotNull @Override public HavingModel build() { return buildModel(); diff --git a/src/main/java/org/mybatis/dynamic/sql/select/HavingModel.java b/src/main/java/org/mybatis/dynamic/sql/select/HavingModel.java index 5f773cf3b..5f8cd38c8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/HavingModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/HavingModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java index ad0f2aba3..124fe095b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -22,7 +22,7 @@ import java.util.Optional; import java.util.function.Consumer; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.common.OrderByModel; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; @@ -32,10 +32,10 @@ public class MultiSelectDSL implements Buildable, ConfigurableStatement { private final List unionQueries = new ArrayList<>(); private final SelectModel initialSelect; - private OrderByModel orderByModel; - private Long limit; - private Long offset; - private Long fetchFirstRows; + private @Nullable OrderByModel orderByModel; + private @Nullable Long limit; + private @Nullable Long offset; + private @Nullable Long fetchFirstRows; private final StatementConfiguration statementConfiguration = new StatementConfiguration(); public MultiSelectDSL(Buildable builder) { @@ -61,22 +61,33 @@ public MultiSelectDSL orderBy(Collection columns) { return this; } - public LimitFinisher limit(long limit) { + public MultiSelectDSL.LimitFinisher limit(long limit) { + return limitWhenPresent(limit); + } + + public MultiSelectDSL.LimitFinisher limitWhenPresent(@Nullable Long limit) { this.limit = limit; return new LimitFinisher(); } - public OffsetFirstFinisher offset(long offset) { + public MultiSelectDSL.OffsetFirstFinisher offset(long offset) { + return offsetWhenPresent(offset); + } + + public MultiSelectDSL.OffsetFirstFinisher offsetWhenPresent(@Nullable Long offset) { this.offset = offset; return new OffsetFirstFinisher(); } - public FetchFirstFinisher fetchFirst(long fetchFirstRows) { + public MultiSelectDSL.FetchFirstFinisher fetchFirst(long fetchFirstRows) { + return fetchFirstWhenPresent(fetchFirstRows); + } + + public MultiSelectDSL.FetchFirstFinisher fetchFirstWhenPresent(@Nullable Long fetchFirstRows) { this.fetchFirstRows = fetchFirstRows; return new FetchFirstFinisher(); } - @NotNull @Override public MultiSelectModel build() { return new MultiSelectModel.Builder() @@ -102,34 +113,32 @@ public MultiSelectDSL configureStatement(Consumer consum return this; } - public class LimitFinisher implements Buildable { - public OffsetFinisher offset(long offset) { - MultiSelectDSL.this.offset(offset); - return new OffsetFinisher(); + public class OffsetFirstFinisher implements Buildable { + public FetchFirstFinisher fetchFirst(long fetchFirstRows) { + return fetchFirstWhenPresent(fetchFirstRows); } - @NotNull - @Override - public MultiSelectModel build() { - return MultiSelectDSL.this.build(); + public FetchFirstFinisher fetchFirstWhenPresent(@Nullable Long fetchFirstRows) { + MultiSelectDSL.this.fetchFirstRows = fetchFirstRows; + return new FetchFirstFinisher(); } - } - public class OffsetFinisher implements Buildable { - @NotNull @Override public MultiSelectModel build() { return MultiSelectDSL.this.build(); } } - public class OffsetFirstFinisher implements Buildable { - public FetchFirstFinisher fetchFirst(long fetchFirstRows) { - MultiSelectDSL.this.fetchFirst(fetchFirstRows); - return new FetchFirstFinisher(); + public class LimitFinisher implements Buildable { + public MultiSelectDSL offset(long offset) { + return offsetWhenPresent(offset); + } + + public MultiSelectDSL offsetWhenPresent(@Nullable Long offset) { + MultiSelectDSL.this.offset = offset; + return MultiSelectDSL.this; } - @NotNull @Override public MultiSelectModel build() { return MultiSelectDSL.this.build(); @@ -137,16 +146,8 @@ public MultiSelectModel build() { } public class FetchFirstFinisher { - public RowsOnlyFinisher rowsOnly() { - return new RowsOnlyFinisher(); - } - } - - public class RowsOnlyFinisher implements Buildable { - @NotNull - @Override - public MultiSelectModel build() { - return MultiSelectDSL.this.build(); + public MultiSelectDSL rowsOnly() { + return MultiSelectDSL.this; } } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java index f9a1a388a..94dbe765b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/MultiSelectModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,7 +20,7 @@ import java.util.Objects; import java.util.stream.Stream; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.select.render.MultiSelectRenderer; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; @@ -45,18 +45,15 @@ public Stream unionQueries() { return unionQueries.stream(); } - @NotNull public SelectStatementProvider render(RenderingStrategy renderingStrategy) { - return new MultiSelectRenderer.Builder() - .withMultiSelectModel(this) + return MultiSelectRenderer.withMultiSelectModel(this) .withRenderingStrategy(renderingStrategy) - .withStatementConfiguration(statementConfiguration) .build() .render(); } public static class Builder extends AbstractBuilder { - private SelectModel initialSelect; + private @Nullable SelectModel initialSelect; private final List unionQueries = new ArrayList<>(); public Builder withInitialSelect(SelectModel initialSelect) { diff --git a/src/main/java/org/mybatis/dynamic/sql/select/PagingModel.java b/src/main/java/org/mybatis/dynamic/sql/select/PagingModel.java index 603f40001..b5da5e01c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/PagingModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/PagingModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,11 +17,13 @@ import java.util.Optional; +import org.jspecify.annotations.Nullable; + public class PagingModel { - private final Long limit; - private final Long offset; - private final Long fetchFirstRows; + private final @Nullable Long limit; + private final @Nullable Long offset; + private final @Nullable Long fetchFirstRows; private PagingModel(Builder builder) { super(); @@ -43,21 +45,21 @@ public Optional fetchFirstRows() { } public static class Builder { - private Long limit; - private Long offset; - private Long fetchFirstRows; + private @Nullable Long limit; + private @Nullable Long offset; + private @Nullable Long fetchFirstRows; - public Builder withLimit(Long limit) { + public Builder withLimit(@Nullable Long limit) { this.limit = limit; return this; } - public Builder withOffset(Long offset) { + public Builder withOffset(@Nullable Long offset) { this.offset = offset; return this; } - public Builder withFetchFirstRows(Long fetchFirstRows) { + public Builder withFetchFirstRows(@Nullable Long fetchFirstRows) { this.fetchFirstRows = fetchFirstRows; return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java index 593252979..70ad2617d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -22,35 +22,36 @@ import java.util.Objects; import java.util.function.Consumer; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AndOrCriteriaGroup; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; +import org.mybatis.dynamic.sql.ColumnAndConditionCriterion; import org.mybatis.dynamic.sql.CriteriaGroup; +import org.mybatis.dynamic.sql.RenderableCondition; import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.TableExpression; +import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionDSL; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; -import org.mybatis.dynamic.sql.select.join.JoinCondition; -import org.mybatis.dynamic.sql.select.join.JoinCriterion; import org.mybatis.dynamic.sql.select.join.JoinSpecification; import org.mybatis.dynamic.sql.select.join.JoinType; import org.mybatis.dynamic.sql.util.Buildable; -import org.mybatis.dynamic.sql.util.Utilities; import org.mybatis.dynamic.sql.where.AbstractWhereFinisher; import org.mybatis.dynamic.sql.where.AbstractWhereStarter; import org.mybatis.dynamic.sql.where.EmbeddedWhereModel; public class QueryExpressionDSL extends AbstractQueryExpressionDSL.QueryExpressionWhereBuilder, QueryExpressionDSL> - implements Buildable { + implements Buildable, SelectDSLOperations { - private final String connector; + private final @Nullable String connector; private final SelectDSL selectDSL; private final boolean isDistinct; private final List selectList; - private QueryExpressionWhereBuilder whereBuilder; - private GroupByModel groupByModel; - private QueryExpressionHavingBuilder havingBuilder; + private @Nullable QueryExpressionWhereBuilder whereBuilder; + private @Nullable GroupByModel groupByModel; + private @Nullable QueryExpressionHavingBuilder havingBuilder; protected QueryExpressionDSL(FromGatherer fromGatherer, TableExpression table) { super(table); @@ -68,7 +69,7 @@ protected QueryExpressionDSL(FromGatherer fromGatherer, SqlTable table, Strin @Override public QueryExpressionWhereBuilder where() { - whereBuilder = Utilities.buildIfNecessary(whereBuilder, QueryExpressionWhereBuilder::new); + whereBuilder = Objects.requireNonNullElseGet(whereBuilder, QueryExpressionWhereBuilder::new); return whereBuilder; } @@ -84,7 +85,7 @@ public QueryExpressionDSL configureStatement(Consumer * @return The having builder */ protected QueryExpressionHavingBuilder having() { - havingBuilder = Utilities.buildIfNecessary(havingBuilder, QueryExpressionHavingBuilder::new); + havingBuilder = Objects.requireNonNullElseGet(havingBuilder, QueryExpressionHavingBuilder::new); return havingBuilder; } @@ -97,7 +98,6 @@ protected void applyHaving(CriteriaGroup criteriaGroup) { having().initialize(criteriaGroup); } - @NotNull @Override public R build() { return selectDSL.build(); @@ -194,25 +194,18 @@ protected QueryExpressionModel buildModel() { .build(); } - public SelectDSL.LimitFinisher limit(long limit) { - return selectDSL.limit(limit); - } - - public SelectDSL.OffsetFirstFinisher offset(long offset) { - return selectDSL.offset(offset); - } - - public SelectDSL.FetchFirstFinisher fetchFirst(long fetchFirstRows) { - return selectDSL.fetchFirst(fetchFirstRows); - } - @Override protected QueryExpressionDSL getThis() { return this; } + @Override + public SelectDSL getSelectDSL() { + return selectDSL; + } + public static class FromGatherer { - private final String connector; + private final @Nullable String connector; private final List selectList; private final SelectDSL selectDSL; private final boolean isDistinct; @@ -241,9 +234,9 @@ public QueryExpressionDSL from(SqlTable table, String tableAlias) { } public static class Builder { - private String connector; + private @Nullable String connector; private final List selectList = new ArrayList<>(); - private SelectDSL selectDSL; + private @Nullable SelectDSL selectDSL; private boolean isDistinct; public Builder withConnector(String connector) { @@ -273,7 +266,7 @@ public FromGatherer build() { } public class QueryExpressionWhereBuilder extends AbstractWhereFinisher - implements Buildable { + implements Buildable, SelectDSLOperations { private QueryExpressionWhereBuilder() { super(QueryExpressionDSL.this); } @@ -302,19 +295,6 @@ public GroupByFinisher groupBy(Collection columns) { return QueryExpressionDSL.this.groupBy(columns); } - public SelectDSL.LimitFinisher limit(long limit) { - return QueryExpressionDSL.this.limit(limit); - } - - public SelectDSL.OffsetFirstFinisher offset(long offset) { - return QueryExpressionDSL.this.offset(offset); - } - - public SelectDSL.FetchFirstFinisher fetchFirst(long fetchFirstRows) { - return QueryExpressionDSL.this.fetchFirst(fetchFirstRows); - } - - @NotNull @Override public R build() { return QueryExpressionDSL.this.build(); @@ -325,6 +305,11 @@ protected QueryExpressionWhereBuilder getThis() { return this; } + @Override + public SelectDSL getSelectDSL() { + return QueryExpressionDSL.this.getSelectDSL(); + } + protected EmbeddedWhereModel buildWhereModel() { return super.buildModel(); } @@ -339,53 +324,60 @@ public JoinSpecificationStarter(TableExpression joinTable, JoinType joinType) { this.joinType = joinType; } - public JoinSpecificationFinisher on(BindableColumn joinColumn, JoinCondition joinCondition) { + public JoinSpecificationFinisher on(BindableColumn joinColumn, RenderableCondition joinCondition) { return new JoinSpecificationFinisher(joinTable, joinColumn, joinCondition, joinType); } - public JoinSpecificationFinisher on(BindableColumn joinColumn, JoinCondition onJoinCondition, - JoinCriterion... andJoinCriteria) { - return new JoinSpecificationFinisher(joinTable, joinColumn, onJoinCondition, joinType, andJoinCriteria); + public JoinSpecificationFinisher on(BindableColumn joinColumn, RenderableCondition onJoinCondition, + AndOrCriteriaGroup... subCriteria) { + return new JoinSpecificationFinisher(joinTable, joinColumn, onJoinCondition, joinType, subCriteria); } } public class JoinSpecificationFinisher - extends AbstractWhereStarter - implements Buildable { - private final JoinSpecification.Builder joinSpecificationBuilder; + extends AbstractBooleanExpressionDSL + implements AbstractWhereStarter, Buildable, + SelectDSLOperations { + + private final TableExpression table; + private final JoinType joinType; public JoinSpecificationFinisher(TableExpression table, BindableColumn joinColumn, - JoinCondition joinCondition, JoinType joinType) { - JoinCriterion joinCriterion = new JoinCriterion.Builder() - .withConnector("on") //$NON-NLS-1$ - .withJoinColumn(joinColumn) - .withJoinCondition(joinCondition) - .build(); + RenderableCondition joinCondition, JoinType joinType) { + this.table = table; + this.joinType = joinType; + addJoinSpecificationSupplier(this::buildJoinSpecification); - joinSpecificationBuilder = JoinSpecification.withJoinTable(table) - .withJoinType(joinType) - .withJoinCriterion(joinCriterion); + ColumnAndConditionCriterion criterion = ColumnAndConditionCriterion.withColumn(joinColumn) + .withCondition(joinCondition) + .build(); - addJoinSpecificationBuilder(joinSpecificationBuilder); + setInitialCriterion(criterion); } public JoinSpecificationFinisher(TableExpression table, BindableColumn joinColumn, - JoinCondition joinCondition, JoinType joinType, JoinCriterion... andJoinCriteria) { - JoinCriterion onJoinCriterion = new JoinCriterion.Builder() - .withConnector("on") //$NON-NLS-1$ - .withJoinColumn(joinColumn) - .withJoinCondition(joinCondition) + RenderableCondition joinCondition, JoinType joinType, + AndOrCriteriaGroup... subCriteria) { + this.table = table; + this.joinType = joinType; + addJoinSpecificationSupplier(this::buildJoinSpecification); + + ColumnAndConditionCriterion criterion = ColumnAndConditionCriterion.withColumn(joinColumn) + .withCondition(joinCondition) + .withSubCriteria(Arrays.asList(subCriteria)) .build(); - joinSpecificationBuilder = JoinSpecification.withJoinTable(table) - .withJoinType(joinType) - .withJoinCriterion(onJoinCriterion) - .withJoinCriteria(Arrays.asList(andJoinCriteria)); + setInitialCriterion(criterion); + } - addJoinSpecificationBuilder(joinSpecificationBuilder); + private JoinSpecification buildJoinSpecification() { + return JoinSpecification.withJoinTable(table) + .withJoinType(joinType) + .withInitialCriterion(getInitialCriterion()) + .withSubCriteria(subCriteria) + .build(); } - @NotNull @Override public R build() { return QueryExpressionDSL.this.build(); @@ -402,16 +394,6 @@ public QueryExpressionWhereBuilder where() { return QueryExpressionDSL.this.where(); } - public JoinSpecificationFinisher and(BindableColumn joinColumn, JoinCondition joinCondition) { - JoinCriterion joinCriterion = new JoinCriterion.Builder() - .withConnector("and") //$NON-NLS-1$ - .withJoinColumn(joinColumn) - .withJoinCondition(joinCondition) - .build(); - joinSpecificationBuilder.withJoinCriterion(joinCriterion); - return this; - } - public JoinSpecificationStarter join(SqlTable joinTable) { return QueryExpressionDSL.this.join(joinTable); } @@ -484,20 +466,19 @@ public SelectDSL orderBy(Collection columns) { return QueryExpressionDSL.this.orderBy(columns); } - public SelectDSL.LimitFinisher limit(long limit) { - return QueryExpressionDSL.this.limit(limit); - } - - public SelectDSL.OffsetFirstFinisher offset(long offset) { - return QueryExpressionDSL.this.offset(offset); + @Override + protected JoinSpecificationFinisher getThis() { + return this; } - public SelectDSL.FetchFirstFinisher fetchFirst(long fetchFirstRows) { - return QueryExpressionDSL.this.fetchFirst(fetchFirstRows); + @Override + public SelectDSL getSelectDSL() { + return QueryExpressionDSL.this.getSelectDSL(); } } - public class GroupByFinisher extends AbstractHavingStarter implements Buildable { + public class GroupByFinisher implements AbstractHavingStarter, + Buildable, SelectDSLOperations { public SelectDSL orderBy(SortSpecification... columns) { return orderBy(Arrays.asList(columns)); } @@ -506,7 +487,6 @@ public SelectDSL orderBy(Collection columns) { return QueryExpressionDSL.this.orderBy(columns); } - @NotNull @Override public R build() { return QueryExpressionDSL.this.build(); @@ -520,22 +500,15 @@ public UnionBuilder unionAll() { return QueryExpressionDSL.this.unionAll(); } - public SelectDSL.LimitFinisher limit(long limit) { - return QueryExpressionDSL.this.limit(limit); - } - - public SelectDSL.OffsetFirstFinisher offset(long offset) { - return QueryExpressionDSL.this.offset(offset); - } - - public SelectDSL.FetchFirstFinisher fetchFirst(long fetchFirstRows) { - return QueryExpressionDSL.this.fetchFirst(fetchFirstRows); - } - @Override public QueryExpressionHavingBuilder having() { return QueryExpressionDSL.this.having(); } + + @Override + public SelectDSL getSelectDSL() { + return QueryExpressionDSL.this.getSelectDSL(); + } } public class UnionBuilder { @@ -572,19 +545,7 @@ public FromGatherer selectDistinct(List selectList) { } public class QueryExpressionHavingBuilder extends AbstractHavingFinisher - implements Buildable { - - public SelectDSL.FetchFirstFinisher fetchFirst(long fetchFirstRows) { - return QueryExpressionDSL.this.fetchFirst(fetchFirstRows); - } - - public SelectDSL.OffsetFirstFinisher offset(long offset) { - return QueryExpressionDSL.this.offset(offset); - } - - public SelectDSL.LimitFinisher limit(long limit) { - return QueryExpressionDSL.this.limit(limit); - } + implements Buildable, SelectDSLOperations { public SelectDSL orderBy(SortSpecification... columns) { return orderBy(Arrays.asList(columns)); @@ -602,7 +563,6 @@ public UnionBuilder unionAll() { return QueryExpressionDSL.this.unionAll(); } - @NotNull @Override public R build() { return QueryExpressionDSL.this.build(); @@ -616,5 +576,10 @@ protected QueryExpressionHavingBuilder getThis() { protected HavingModel buildHavingModel() { return super.buildModel(); } + + @Override + public SelectDSL getSelectDSL() { + return QueryExpressionDSL.this.getSelectDSL(); + } } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java index f67060aad..4561c0781 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/QueryExpressionModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -23,6 +23,7 @@ import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.TableExpression; @@ -31,15 +32,15 @@ import org.mybatis.dynamic.sql.where.EmbeddedWhereModel; public class QueryExpressionModel { - private final String connector; + private final @Nullable String connector; private final boolean isDistinct; private final List selectList; private final TableExpression table; - private final JoinModel joinModel; + private final @Nullable JoinModel joinModel; private final Map tableAliases; - private final EmbeddedWhereModel whereModel; - private final GroupByModel groupByModel; - private final HavingModel havingModel; + private final @Nullable EmbeddedWhereModel whereModel; + private final @Nullable GroupByModel groupByModel; + private final @Nullable HavingModel havingModel; private QueryExpressionModel(Builder builder) { connector = builder.connector; @@ -95,17 +96,17 @@ public static Builder withSelectList(List columnList) { } public static class Builder { - private String connector; + private @Nullable String connector; private boolean isDistinct; private final List selectList = new ArrayList<>(); - private TableExpression table; + private @Nullable TableExpression table; private final Map tableAliases = new HashMap<>(); - private EmbeddedWhereModel whereModel; - private JoinModel joinModel; - private GroupByModel groupByModel; - private HavingModel havingModel; + private @Nullable EmbeddedWhereModel whereModel; + private @Nullable JoinModel joinModel; + private @Nullable GroupByModel groupByModel; + private @Nullable HavingModel havingModel; - public Builder withConnector(String connector) { + public Builder withConnector(@Nullable String connector) { this.connector = connector; return this; } @@ -135,22 +136,22 @@ public Builder withTableAliases(Map tableAliases) { return this; } - public Builder withWhereModel(EmbeddedWhereModel whereModel) { + public Builder withWhereModel(@Nullable EmbeddedWhereModel whereModel) { this.whereModel = whereModel; return this; } - public Builder withJoinModel(JoinModel joinModel) { + public Builder withJoinModel(@Nullable JoinModel joinModel) { this.joinModel = joinModel; return this; } - public Builder withGroupByModel(GroupByModel groupByModel) { + public Builder withGroupByModel(@Nullable GroupByModel groupByModel) { this.groupByModel = groupByModel; return this; } - public Builder withHavingModel(HavingModel havingModel) { + public Builder withHavingModel(@Nullable HavingModel havingModel) { this.havingModel = havingModel; return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java index a509e974b..2c2963772 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -23,9 +23,8 @@ import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.Collectors; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.common.OrderByModel; @@ -33,6 +32,7 @@ import org.mybatis.dynamic.sql.select.QueryExpressionDSL.FromGatherer; import org.mybatis.dynamic.sql.util.Buildable; import org.mybatis.dynamic.sql.util.ConfigurableStatement; +import org.mybatis.dynamic.sql.util.Validator; /** * Implements a SQL DSL for building select statements. @@ -46,11 +46,13 @@ public class SelectDSL implements Buildable, ConfigurableStatement adapterFunction; private final List> queryExpressions = new ArrayList<>(); - private OrderByModel orderByModel; - private Long limit; - private Long offset; - private Long fetchFirstRows; + private @Nullable OrderByModel orderByModel; + private @Nullable Long limit; + private @Nullable Long offset; + private @Nullable Long fetchFirstRows; final StatementConfiguration statementConfiguration = new StatementConfiguration(); + private @Nullable String forClause; + private @Nullable String waitClause; private SelectDSL(Function adapterFunction) { this.adapterFunction = Objects.requireNonNull(adapterFunction); @@ -108,34 +110,83 @@ void orderBy(Collection columns) { orderByModel = OrderByModel.of(columns); } - public LimitFinisher limit(long limit) { + public SelectDSL.LimitFinisher limit(long limit) { + return limitWhenPresent(limit); + } + + public SelectDSL.LimitFinisher limitWhenPresent(@Nullable Long limit) { this.limit = limit; return new LimitFinisher(); } - public OffsetFirstFinisher offset(long offset) { + public SelectDSL.OffsetFirstFinisher offset(long offset) { + return offsetWhenPresent(offset); + } + + public SelectDSL.OffsetFirstFinisher offsetWhenPresent(@Nullable Long offset) { this.offset = offset; return new OffsetFirstFinisher(); } - public FetchFirstFinisher fetchFirst(long fetchFirstRows) { + public SelectDSL.FetchFirstFinisher fetchFirst(long fetchFirstRows) { + return fetchFirstWhenPresent(fetchFirstRows); + } + + public SelectDSL.FetchFirstFinisher fetchFirstWhenPresent(@Nullable Long fetchFirstRows) { this.fetchFirstRows = fetchFirstRows; return new FetchFirstFinisher(); } + public SelectDSL forUpdate() { + Validator.assertNull(forClause, "ERROR.48"); //$NON-NLS-1$ + forClause = "for update"; //$NON-NLS-1$ + return this; + } + + public SelectDSL forNoKeyUpdate() { + Validator.assertNull(forClause, "ERROR.48"); //$NON-NLS-1$ + forClause = "for no key update"; //$NON-NLS-1$ + return this; + } + + public SelectDSL forShare() { + Validator.assertNull(forClause, "ERROR.48"); //$NON-NLS-1$ + forClause = "for share"; //$NON-NLS-1$ + return this; + } + + public SelectDSL forKeyShare() { + Validator.assertNull(forClause, "ERROR.48"); //$NON-NLS-1$ + forClause = "for key share"; //$NON-NLS-1$ + return this; + } + + public SelectDSL skipLocked() { + Validator.assertNull(waitClause, "ERROR.49"); //$NON-NLS-1$ + waitClause = "skip locked"; //$NON-NLS-1$ + return this; + } + + public SelectDSL nowait() { + Validator.assertNull(waitClause, "ERROR.49"); //$NON-NLS-1$ + waitClause = "nowait"; //$NON-NLS-1$ + return this; + } + @Override public SelectDSL configureStatement(Consumer consumer) { consumer.accept(statementConfiguration); return this; } - @NotNull @Override public R build() { SelectModel selectModel = SelectModel.withQueryExpressions(buildModels()) .withOrderByModel(orderByModel) .withPagingModel(buildPagingModel().orElse(null)) .withStatementConfiguration(statementConfiguration) + .withForClause(forClause) + .withWaitClause(waitClause) .build(); return adapterFunction.apply(selectModel); } @@ -143,7 +194,7 @@ public R build() { private List buildModels() { return queryExpressions.stream() .map(QueryExpressionDSL::buildModel) - .collect(Collectors.toList()); + .toList(); } private Optional buildPagingModel() { @@ -154,51 +205,51 @@ private Optional buildPagingModel() { .build(); } - public class LimitFinisher implements Buildable { - public OffsetFinisher offset(long offset) { - SelectDSL.this.offset(offset); - return new OffsetFinisher(); + public class OffsetFirstFinisher implements SelectDSLForAndWaitOperations, Buildable { + public FetchFirstFinisher fetchFirst(long fetchFirstRows) { + return fetchFirstWhenPresent(fetchFirstRows); + } + + public FetchFirstFinisher fetchFirstWhenPresent(@Nullable Long fetchFirstRows) { + SelectDSL.this.fetchFirstRows = fetchFirstRows; + return new FetchFirstFinisher(); } - @NotNull @Override - public R build() { - return SelectDSL.this.build(); + public SelectDSL getSelectDSL() { + return SelectDSL.this; } - } - public class OffsetFinisher implements Buildable { - @NotNull @Override public R build() { return SelectDSL.this.build(); } } - public class OffsetFirstFinisher implements Buildable { - public FetchFirstFinisher fetchFirst(long fetchFirstRows) { - SelectDSL.this.fetchFirst(fetchFirstRows); - return new FetchFirstFinisher(); + public class LimitFinisher implements SelectDSLForAndWaitOperations, Buildable { + public SelectDSL offset(long offset) { + return offsetWhenPresent(offset); } - @NotNull - @Override - public R build() { - return SelectDSL.this.build(); + public SelectDSL offsetWhenPresent(@Nullable Long offset) { + SelectDSL.this.offset = offset; + return SelectDSL.this; } - } - public class FetchFirstFinisher { - public RowsOnlyFinisher rowsOnly() { - return new RowsOnlyFinisher(); + @Override + public SelectDSL getSelectDSL() { + return SelectDSL.this; } - } - public class RowsOnlyFinisher implements Buildable { - @NotNull @Override public R build() { return SelectDSL.this.build(); } } + + public class FetchFirstFinisher { + public SelectDSL rowsOnly() { + return SelectDSL.this; + } + } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLCompleter.java b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLCompleter.java index 4252dfc8d..a38d2d3cf 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLCompleter.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLCompleter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLForAndWaitOperations.java b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLForAndWaitOperations.java new file mode 100644 index 000000000..e4dc45d97 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLForAndWaitOperations.java @@ -0,0 +1,53 @@ +/* + * 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 org.mybatis.dynamic.sql.select; + +public interface SelectDSLForAndWaitOperations { + default SelectDSL forUpdate() { + return getSelectDSL().forUpdate(); + } + + default SelectDSL forNoKeyUpdate() { + return getSelectDSL().forNoKeyUpdate(); + } + + default SelectDSL forShare() { + return getSelectDSL().forShare(); + } + + default SelectDSL forKeyShare() { + return getSelectDSL().forKeyShare(); + } + + default SelectDSL skipLocked() { + return getSelectDSL().skipLocked(); + } + + default SelectDSL nowait() { + return getSelectDSL().nowait(); + } + + /** + * Gain access to the SelectDSL instance. + * + *

This is a leak of an implementation detail into the public API. The tradeoff is that it + * significantly reduces copy/paste code of SelectDSL methods into all the different inner classes of + * QueryExpressionDSL where they would be needed. + * + * @return the SelectDSL instance associated with this interface instance + */ + SelectDSL getSelectDSL(); +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLOperations.java b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLOperations.java new file mode 100644 index 000000000..02f862c3b --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/SelectDSLOperations.java @@ -0,0 +1,44 @@ +/* + * 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 org.mybatis.dynamic.sql.select; + +import org.jspecify.annotations.Nullable; + +public interface SelectDSLOperations extends SelectDSLForAndWaitOperations { + default SelectDSL.LimitFinisher limit(long limit) { + return getSelectDSL().limit(limit); + } + + default SelectDSL.LimitFinisher limitWhenPresent(@Nullable Long limit) { + return getSelectDSL().limitWhenPresent(limit); + } + + default SelectDSL.OffsetFirstFinisher offset(long offset) { + return getSelectDSL().offset(offset); + } + + default SelectDSL.OffsetFirstFinisher offsetWhenPresent(@Nullable Long offset) { + return getSelectDSL().offsetWhenPresent(offset); + } + + default SelectDSL.FetchFirstFinisher fetchFirst(long fetchFirstRows) { + return getSelectDSL().fetchFirst(fetchFirstRows); + } + + default SelectDSL.FetchFirstFinisher fetchFirstWhenPresent(@Nullable Long fetchFirstRows) { + return getSelectDSL().fetchFirstWhenPresent(fetchFirstRows); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SelectModel.java b/src/main/java/org/mybatis/dynamic/sql/select/SelectModel.java index 57fd06a43..ec277760b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/SelectModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/SelectModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,10 +18,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.stream.Stream; -import org.jetbrains.annotations.NotNull; -import org.mybatis.dynamic.sql.render.RenderingContext; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.select.render.SelectRenderer; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; @@ -29,35 +29,32 @@ public class SelectModel extends AbstractSelectModel { private final List queryExpressions; + private final @Nullable String forClause; + private final @Nullable String waitClause; private SelectModel(Builder builder) { super(builder); queryExpressions = Objects.requireNonNull(builder.queryExpressions); Validator.assertNotEmpty(queryExpressions, "ERROR.14"); //$NON-NLS-1$ + forClause = builder.forClause; + waitClause = builder.waitClause; } public Stream queryExpressions() { return queryExpressions.stream(); } - @NotNull - public SelectStatementProvider render(RenderingStrategy renderingStrategy) { - RenderingContext renderingContext = RenderingContext.withRenderingStrategy(renderingStrategy) - .withStatementConfiguration(statementConfiguration) - .build(); - return render(renderingContext); + public Optional forClause() { + return Optional.ofNullable(forClause); + } + + public Optional waitClause() { + return Optional.ofNullable(waitClause); } - /** - * This version is for rendering sub-queries, union queries, etc. - * - * @param renderingContext the rendering context - * @return a rendered select statement and parameters - */ - @NotNull - public SelectStatementProvider render(RenderingContext renderingContext) { + public SelectStatementProvider render(RenderingStrategy renderingStrategy) { return SelectRenderer.withSelectModel(this) - .withRenderingContext(renderingContext) + .withRenderingStrategy(renderingStrategy) .build() .render(); } @@ -68,6 +65,8 @@ public static Builder withQueryExpressions(List queryExpre public static class Builder extends AbstractBuilder { private final List queryExpressions = new ArrayList<>(); + private @Nullable String forClause; + private @Nullable String waitClause; public Builder withQueryExpression(QueryExpressionModel queryExpression) { this.queryExpressions.add(queryExpression); @@ -79,6 +78,16 @@ public Builder withQueryExpressions(List queryExpressions) return this; } + public Builder withForClause(@Nullable String forClause) { + this.forClause = forClause; + return this; + } + + public Builder withWaitClause(@Nullable String waitClause) { + this.waitClause = waitClause; + return this; + } + @Override protected Builder getThis() { return this; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SimpleSortSpecification.java b/src/main/java/org/mybatis/dynamic/sql/select/SimpleSortSpecification.java index 9620020e2..0a2c4918e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/SimpleSortSpecification.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/SimpleSortSpecification.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,6 +18,8 @@ import java.util.Objects; import org.mybatis.dynamic.sql.SortSpecification; +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; /** * This class is used for an order by phrase where there is no suitable column name @@ -28,30 +30,25 @@ public class SimpleSortSpecification implements SortSpecification { private final String name; - private final boolean isDescending; + private final String descendingPhrase; private SimpleSortSpecification(String name) { - this(name, false); + this(name, ""); //$NON-NLS-1$ } - private SimpleSortSpecification(String name, boolean isDescending) { + private SimpleSortSpecification(String name, String descendingPhrase) { this.name = Objects.requireNonNull(name); - this.isDescending = isDescending; + this.descendingPhrase = descendingPhrase; } @Override public SortSpecification descending() { - return new SimpleSortSpecification(name, true); + return new SimpleSortSpecification(name, " DESC"); //$NON-NLS-1$ } @Override - public String orderByName() { - return name; - } - - @Override - public boolean isDescending() { - return isDescending; + public FragmentAndParameters renderForOrderBy(RenderingContext renderingContext) { + return FragmentAndParameters.fromFragment(name + descendingPhrase); } public static SimpleSortSpecification of(String name) { diff --git a/src/main/java/org/mybatis/dynamic/sql/select/SubQuery.java b/src/main/java/org/mybatis/dynamic/sql/select/SubQuery.java index 27bca6fca..c2264c162 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/SubQuery.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/SubQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,12 +18,13 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.TableExpression; import org.mybatis.dynamic.sql.TableExpressionVisitor; public class SubQuery implements TableExpression { private final SelectModel selectModel; - private final String alias; + private final @Nullable String alias; private SubQuery(Builder builder) { selectModel = Objects.requireNonNull(builder.selectModel); @@ -49,15 +50,15 @@ public R accept(TableExpressionVisitor visitor) { } public static class Builder { - private SelectModel selectModel; - private String alias; + private @Nullable SelectModel selectModel; + private @Nullable String alias; public Builder withSelectModel(SelectModel selectModel) { this.selectModel = selectModel; return this; } - public Builder withAlias(String alias) { + public Builder withAlias(@Nullable String alias) { this.alias = alias; return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/UnionQuery.java b/src/main/java/org/mybatis/dynamic/sql/select/UnionQuery.java index 8d8f07be6..6806d4791 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/UnionQuery.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/UnionQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,20 +17,9 @@ import java.util.Objects; -public class UnionQuery { - private final String connector; - private final SelectModel selectModel; - +public record UnionQuery(String connector, SelectModel selectModel) { public UnionQuery(String connector, SelectModel selectModel) { this.connector = Objects.requireNonNull(connector); this.selectModel = Objects.requireNonNull(selectModel); } - - public String connector() { - return connector; - } - - public SelectModel selectModel() { - return selectModel; - } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/AbstractCount.java b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/AbstractCount.java index f0431f026..fe40329ea 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/AbstractCount.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/AbstractCount.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,6 +17,7 @@ import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BindableColumn; /** @@ -25,13 +26,13 @@ * as it is assumed that the count functions always return a number. */ public abstract class AbstractCount implements BindableColumn { - private final String alias; + private final @Nullable String alias; protected AbstractCount() { this(null); } - protected AbstractCount(String alias) { + protected AbstractCount(@Nullable String alias) { this.alias = alias; } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Avg.java b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Avg.java index 5130abf7c..0e04d78ea 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Avg.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Avg.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,6 +15,7 @@ */ package org.mybatis.dynamic.sql.select.aggregate; +import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.select.function.AbstractUniTypeFunction; @@ -22,7 +23,7 @@ public class Avg extends AbstractUniTypeFunction> { - private Avg(BindableColumn column) { + private Avg(BasicColumn column) { super(column); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Count.java b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Count.java index 4c0240d24..9d2a791cf 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Count.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Count.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/CountAll.java b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/CountAll.java index 78fbca6e5..306c14f54 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/CountAll.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/CountAll.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/CountDistinct.java b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/CountDistinct.java index 79d9b874a..4acba9db9 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/CountDistinct.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/CountDistinct.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Max.java b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Max.java index f44225f12..5d20594ea 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Max.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Max.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,6 +15,7 @@ */ package org.mybatis.dynamic.sql.select.aggregate; +import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.select.function.AbstractUniTypeFunction; @@ -22,7 +23,7 @@ public class Max extends AbstractUniTypeFunction> { - private Max(BindableColumn column) { + private Max(BasicColumn column) { super(column); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Min.java b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Min.java index 02830335c..e838c2f3a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Min.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Min.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,6 +15,7 @@ */ package org.mybatis.dynamic.sql.select.aggregate; +import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.select.function.AbstractUniTypeFunction; @@ -22,7 +23,7 @@ public class Min extends AbstractUniTypeFunction> { - private Min(BindableColumn column) { + private Min(BasicColumn column) { super(column); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Sum.java b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Sum.java index 02c8c7a42..7ccfd7854 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Sum.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/Sum.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,8 +17,9 @@ import java.util.function.Function; +import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; -import org.mybatis.dynamic.sql.VisitableCondition; +import org.mybatis.dynamic.sql.RenderableCondition; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.select.function.AbstractUniTypeFunction; import org.mybatis.dynamic.sql.util.FragmentAndParameters; @@ -28,12 +29,12 @@ public class Sum extends AbstractUniTypeFunction> { private final Function renderer; - private Sum(BindableColumn column) { + private Sum(BasicColumn column) { super(column); renderer = rc -> column.render(rc).mapFragment(this::applyAggregate); } - private Sum(BindableColumn column, VisitableCondition condition) { + private Sum(BindableColumn column, RenderableCondition condition) { super(column); renderer = rc -> { Validator.assertTrue(condition.shouldRender(rc), "ERROR.37", "sum"); //$NON-NLS-1$ //$NON-NLS-2$ @@ -48,7 +49,7 @@ private Sum(BindableColumn column, VisitableCondition condition) { }; } - private Sum(BindableColumn column, Function renderer) { + private Sum(BasicColumn column, Function renderer) { super(column); this.renderer = renderer; } @@ -71,7 +72,11 @@ public static Sum of(BindableColumn column) { return new Sum<>(column); } - public static Sum of(BindableColumn column, VisitableCondition condition) { + public static Sum of(BasicColumn column) { + return new Sum<>(column); + } + + public static Sum of(BindableColumn column, RenderableCondition condition) { return new Sum<>(column, condition); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/aggregate/package-info.java b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/package-info.java new file mode 100644 index 000000000..60e9e8918 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/aggregate/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.select.aggregate; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/BasicWhenCondition.java b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/BasicWhenCondition.java index 8fe46e5fd..dcfaddc43 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/BasicWhenCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/BasicWhenCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/ConditionBasedWhenCondition.java b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/ConditionBasedWhenCondition.java index 16af8f891..78f841b1d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/ConditionBasedWhenCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/ConditionBasedWhenCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,17 +20,17 @@ import java.util.stream.Stream; import org.mybatis.dynamic.sql.BasicColumn; -import org.mybatis.dynamic.sql.VisitableCondition; +import org.mybatis.dynamic.sql.RenderableCondition; public class ConditionBasedWhenCondition extends SimpleCaseWhenCondition { - private final List> conditions = new ArrayList<>(); + private final List> conditions = new ArrayList<>(); - public ConditionBasedWhenCondition(List> conditions, BasicColumn thenValue) { + public ConditionBasedWhenCondition(List> conditions, BasicColumn thenValue) { super(thenValue); this.conditions.addAll(conditions); } - public Stream> conditions() { + public Stream> conditions() { return conditions.stream(); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/ElseDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/ElseDSL.java index abe51d763..39d0bc740 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/ElseDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/ElseDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SearchedCaseDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SearchedCaseDSL.java index 674012d3c..c86bf7ff0 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SearchedCaseDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SearchedCaseDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,25 +19,26 @@ import java.util.Arrays; import java.util.List; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AndOrCriteriaGroup; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.ColumnAndConditionCriterion; import org.mybatis.dynamic.sql.CriteriaGroup; +import org.mybatis.dynamic.sql.RenderableCondition; import org.mybatis.dynamic.sql.SqlCriterion; -import org.mybatis.dynamic.sql.VisitableCondition; import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionDSL; public class SearchedCaseDSL implements ElseDSL { private final List whenConditions = new ArrayList<>(); - private BasicColumn elseValue; + private @Nullable BasicColumn elseValue; - public WhenDSL when(BindableColumn column, VisitableCondition condition, + public WhenDSL when(BindableColumn column, RenderableCondition condition, AndOrCriteriaGroup... subCriteria) { return when(column, condition, Arrays.asList(subCriteria)); } - public WhenDSL when(BindableColumn column, VisitableCondition condition, + public WhenDSL when(BindableColumn column, RenderableCondition condition, List subCriteria) { SqlCriterion sqlCriterion = ColumnAndConditionCriterion.withColumn(column) .withCondition(condition) @@ -71,7 +72,7 @@ public SearchedCaseEnder else_(BasicColumn column) { return new SearchedCaseEnder(); } - public BasicColumn end() { + public SearchedCaseModel end() { return new SearchedCaseModel.Builder() .withElseValue(elseValue) .withWhenConditions(whenConditions) @@ -100,7 +101,7 @@ protected WhenDSL getThis() { } public class SearchedCaseEnder { - public BasicColumn end() { + public SearchedCaseModel end() { return SearchedCaseDSL.this.end(); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SearchedCaseModel.java b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SearchedCaseModel.java index 65d3f949f..c42372868 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SearchedCaseModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SearchedCaseModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,21 +20,25 @@ import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.select.render.SearchedCaseRenderer; import org.mybatis.dynamic.sql.util.FragmentAndParameters; import org.mybatis.dynamic.sql.util.Validator; -public class SearchedCaseModel implements BasicColumn { +public class SearchedCaseModel implements BasicColumn, SortSpecification { private final List whenConditions; - private final BasicColumn elseValue; - private final String alias; + private final @Nullable BasicColumn elseValue; + private final @Nullable String alias; + private final String descendingPhrase; private SearchedCaseModel(Builder builder) { whenConditions = builder.whenConditions; alias = builder.alias; elseValue = builder.elseValue; + descendingPhrase = builder.descendingPhrase; Validator.assertNotEmpty(whenConditions, "ERROR.40"); //$NON-NLS-1$ } @@ -56,9 +60,24 @@ public SearchedCaseModel as(String alias) { return new Builder().withWhenConditions(whenConditions) .withElseValue(elseValue) .withAlias(alias) + .withDescendingPhrase(descendingPhrase) .build(); } + @Override + public SearchedCaseModel descending() { + return new Builder().withWhenConditions(whenConditions) + .withElseValue(elseValue) + .withAlias(alias) + .withDescendingPhrase(" DESC") //$NON-NLS-1$ + .build(); + } + + @Override + public FragmentAndParameters renderForOrderBy(RenderingContext renderingContext) { + return render(renderingContext).mapFragment(f -> f + descendingPhrase); + } + @Override public FragmentAndParameters render(RenderingContext renderingContext) { return new SearchedCaseRenderer(this, renderingContext).render(); @@ -66,24 +85,30 @@ public FragmentAndParameters render(RenderingContext renderingContext) { public static class Builder { private final List whenConditions = new ArrayList<>(); - private BasicColumn elseValue; - private String alias; + private @Nullable BasicColumn elseValue; + private @Nullable String alias; + private String descendingPhrase = ""; //$NON-NLS-1$ public Builder withWhenConditions(List whenConditions) { this.whenConditions.addAll(whenConditions); return this; } - public Builder withElseValue(BasicColumn elseValue) { + public Builder withElseValue(@Nullable BasicColumn elseValue) { this.elseValue = elseValue; return this; } - public Builder withAlias(String alias) { + public Builder withAlias(@Nullable String alias) { this.alias = alias; return this; } + public Builder withDescendingPhrase(String descendingPhrase) { + this.descendingPhrase = descendingPhrase; + return this; + } + public SearchedCaseModel build() { return new SearchedCaseModel(this); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SearchedCaseWhenCondition.java b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SearchedCaseWhenCondition.java index e6d57e32c..9b251be9c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SearchedCaseWhenCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SearchedCaseWhenCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,6 +17,7 @@ import java.util.Objects; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionModel; @@ -33,7 +34,7 @@ private SearchedCaseWhenCondition(Builder builder) { } public static class Builder extends AbstractBuilder { - private BasicColumn thenValue; + private @Nullable BasicColumn thenValue; public Builder withThenValue(BasicColumn thenValue) { this.thenValue = thenValue; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseDSL.java index 83e46473a..be2e1e908 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,27 +20,28 @@ import java.util.List; import java.util.Objects; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; -import org.mybatis.dynamic.sql.VisitableCondition; +import org.mybatis.dynamic.sql.RenderableCondition; public class SimpleCaseDSL implements ElseDSL.SimpleCaseEnder> { private final BindableColumn column; private final List> whenConditions = new ArrayList<>(); - private BasicColumn elseValue; + private @Nullable BasicColumn elseValue; private SimpleCaseDSL(BindableColumn column) { this.column = Objects.requireNonNull(column); } @SafeVarargs - public final ConditionBasedWhenFinisher when(VisitableCondition condition, - VisitableCondition... subsequentConditions) { + public final ConditionBasedWhenFinisher when(RenderableCondition condition, + RenderableCondition... subsequentConditions) { return when(condition, Arrays.asList(subsequentConditions)); } - public ConditionBasedWhenFinisher when(VisitableCondition condition, - List> subsequentConditions) { + public ConditionBasedWhenFinisher when(RenderableCondition condition, + List> subsequentConditions) { return new ConditionBasedWhenFinisher(condition, subsequentConditions); } @@ -60,7 +61,7 @@ public SimpleCaseEnder else_(BasicColumn column) { return new SimpleCaseEnder(); } - public BasicColumn end() { + public SimpleCaseModel end() { return new SimpleCaseModel.Builder() .withColumn(column) .withWhenConditions(whenConditions) @@ -69,10 +70,10 @@ public BasicColumn end() { } public class ConditionBasedWhenFinisher implements ThenDSL> { - private final List> conditions = new ArrayList<>(); + private final List> conditions = new ArrayList<>(); - private ConditionBasedWhenFinisher(VisitableCondition condition, - List> subsequentConditions) { + private ConditionBasedWhenFinisher(RenderableCondition condition, + List> subsequentConditions) { conditions.add(condition); conditions.addAll(subsequentConditions); } @@ -100,7 +101,7 @@ public SimpleCaseDSL then(BasicColumn column) { } public class SimpleCaseEnder { - public BasicColumn end() { + public SimpleCaseModel end() { return SimpleCaseDSL.this.end(); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseModel.java b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseModel.java index 4b71407ae..45eb95c01 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,24 +21,28 @@ import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; +import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.select.render.SimpleCaseRenderer; import org.mybatis.dynamic.sql.util.FragmentAndParameters; import org.mybatis.dynamic.sql.util.Validator; -public class SimpleCaseModel implements BasicColumn { +public class SimpleCaseModel implements BasicColumn, SortSpecification { private final BindableColumn column; private final List> whenConditions; - private final BasicColumn elseValue; - private final String alias; + private final @Nullable BasicColumn elseValue; + private final @Nullable String alias; + private final String descendingPhrase; private SimpleCaseModel(Builder builder) { column = Objects.requireNonNull(builder.column); whenConditions = builder.whenConditions; elseValue = builder.elseValue; alias = builder.alias; + descendingPhrase = builder.descendingPhrase; Validator.assertNotEmpty(whenConditions, "ERROR.40"); //$NON-NLS-1$ } @@ -66,19 +70,37 @@ public SimpleCaseModel as(String alias) { .withWhenConditions(whenConditions) .withElseValue(elseValue) .withAlias(alias) + .withDescendingPhrase(descendingPhrase) .build(); } + @Override + public SimpleCaseModel descending() { + return new Builder() + .withColumn(column) + .withWhenConditions(whenConditions) + .withElseValue(elseValue) + .withAlias(alias) + .withDescendingPhrase(" DESC") //$NON-NLS-1$ + .build(); + } + + @Override + public FragmentAndParameters renderForOrderBy(RenderingContext renderingContext) { + return render(renderingContext).mapFragment(f -> f + descendingPhrase); + } + @Override public FragmentAndParameters render(RenderingContext renderingContext) { return new SimpleCaseRenderer<>(this, renderingContext).render(); } public static class Builder { - private BindableColumn column; + private @Nullable BindableColumn column; private final List> whenConditions = new ArrayList<>(); - private BasicColumn elseValue; - private String alias; + private @Nullable BasicColumn elseValue; + private @Nullable String alias; + private String descendingPhrase = ""; //$NON-NLS-1$ public Builder withColumn(BindableColumn column) { this.column = column; @@ -90,16 +112,21 @@ public Builder withWhenConditions(List> whenCondit return this; } - public Builder withElseValue(BasicColumn elseValue) { + public Builder withElseValue(@Nullable BasicColumn elseValue) { this.elseValue = elseValue; return this; } - public Builder withAlias(String alias) { + public Builder withAlias(@Nullable String alias) { this.alias = alias; return this; } + public Builder withDescendingPhrase(String descendingPhrase) { + this.descendingPhrase = descendingPhrase; + return this; + } + public SimpleCaseModel build() { return new SimpleCaseModel<>(this); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseWhenCondition.java b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseWhenCondition.java index 5466f2f3f..7f6351e02 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseWhenCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseWhenCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseWhenConditionVisitor.java b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseWhenConditionVisitor.java index 890343265..dadef7455 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseWhenConditionVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/SimpleCaseWhenConditionVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/ThenDSL.java b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/ThenDSL.java index e88fb13c3..29c914731 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/ThenDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/ThenDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/package-info.java b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/package-info.java new file mode 100644 index 000000000..a383bc305 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/caseexpression/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.select.caseexpression; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractTypeConvertingFunction.java b/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractTypeConvertingFunction.java index cab8aff39..1517d3519 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractTypeConvertingFunction.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractTypeConvertingFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,6 +18,9 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; /** @@ -36,10 +39,10 @@ */ public abstract class AbstractTypeConvertingFunction> implements BindableColumn { - protected final BindableColumn column; - protected String alias; + protected final BasicColumn column; + protected @Nullable String alias; - protected AbstractTypeConvertingFunction(BindableColumn column) { + protected AbstractTypeConvertingFunction(BasicColumn column) { this.column = Objects.requireNonNull(column); } @@ -48,6 +51,7 @@ public Optional alias() { return Optional.ofNullable(alias); } + @NonNull @Override public U as(String alias) { U newThing = copy(); diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractUniTypeFunction.java b/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractUniTypeFunction.java index 7a0690bae..0ad9ee576 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractUniTypeFunction.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractUniTypeFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,7 +18,7 @@ import java.sql.JDBCType; import java.util.Optional; -import org.mybatis.dynamic.sql.BindableColumn; +import org.mybatis.dynamic.sql.BasicColumn; /** * Represents a function that does not change the underlying data type. @@ -33,7 +33,7 @@ public abstract class AbstractUniTypeFunction> extends AbstractTypeConvertingFunction { - protected AbstractUniTypeFunction(BindableColumn column) { + protected AbstractUniTypeFunction(BasicColumn column) { super(column); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/Add.java b/src/main/java/org/mybatis/dynamic/sql/select/function/Add.java index fe0661bea..7521b9fd3 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/Add.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/Add.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -23,7 +23,7 @@ public class Add extends OperatorFunction { - private Add(BindableColumn firstColumn, BasicColumn secondColumn, + private Add(BasicColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { super("+", firstColumn, secondColumn, subsequentColumns); //$NON-NLS-1$ } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/Cast.java b/src/main/java/org/mybatis/dynamic/sql/select/function/Cast.java index 11c31e352..1349d4f51 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/Cast.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/Cast.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,6 +18,7 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.FragmentAndParameters; @@ -25,7 +26,7 @@ public class Cast implements BasicColumn { private final BasicColumn column; private final String targetType; - private final String alias; + private final @Nullable String alias; private Cast(Builder builder) { column = Objects.requireNonNull(builder.column); @@ -56,9 +57,9 @@ private String applyCast(String in) { } public static class Builder { - private BasicColumn column; - private String targetType; - private String alias; + private @Nullable BasicColumn column; + private @Nullable String targetType; + private @Nullable String alias; public Builder withColumn(BasicColumn column) { this.column = column; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/Concat.java b/src/main/java/org/mybatis/dynamic/sql/select/function/Concat.java index e633b2953..2dbf84b7b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/Concat.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/Concat.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -29,7 +29,7 @@ public class Concat extends AbstractUniTypeFunction> { private final List allColumns = new ArrayList<>(); - protected Concat(BindableColumn firstColumn, List subsequentColumns) { + protected Concat(BasicColumn firstColumn, List subsequentColumns) { super(firstColumn); allColumns.add(firstColumn); this.allColumns.addAll(subsequentColumns); diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/Concatenate.java b/src/main/java/org/mybatis/dynamic/sql/select/function/Concatenate.java index 3179f2a1d..16357b21f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/Concatenate.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/Concatenate.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -23,7 +23,7 @@ public class Concatenate extends OperatorFunction { - protected Concatenate(BindableColumn firstColumn, BasicColumn secondColumn, + protected Concatenate(BasicColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { super("||", firstColumn, secondColumn, subsequentColumns); //$NON-NLS-1$ } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/Divide.java b/src/main/java/org/mybatis/dynamic/sql/select/function/Divide.java index 24d2832ca..0463798b5 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/Divide.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/Divide.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -23,7 +23,7 @@ public class Divide extends OperatorFunction { - private Divide(BindableColumn firstColumn, BasicColumn secondColumn, + private Divide(BasicColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { super("/", firstColumn, secondColumn, subsequentColumns); //$NON-NLS-1$ } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/Lower.java b/src/main/java/org/mybatis/dynamic/sql/select/function/Lower.java index e59d04712..80cd292d9 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/Lower.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/Lower.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,13 +15,14 @@ */ package org.mybatis.dynamic.sql.select.function; +import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.FragmentAndParameters; public class Lower extends AbstractUniTypeFunction> { - private Lower(BindableColumn column) { + private Lower(BasicColumn column) { super(column); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/Multiply.java b/src/main/java/org/mybatis/dynamic/sql/select/function/Multiply.java index 50d38b966..239e0564b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/Multiply.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/Multiply.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -23,7 +23,7 @@ public class Multiply extends OperatorFunction { - private Multiply(BindableColumn firstColumn, BasicColumn secondColumn, + private Multiply(BasicColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { super("*", firstColumn, secondColumn, subsequentColumns); //$NON-NLS-1$ } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/OperatorFunction.java b/src/main/java/org/mybatis/dynamic/sql/select/function/OperatorFunction.java index 7ce5c5c25..154a5f564 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/OperatorFunction.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/OperatorFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -35,7 +35,7 @@ public class OperatorFunction extends AbstractUniTypeFunction subsequentColumns = new ArrayList<>(); private final String operator; - protected OperatorFunction(String operator, BindableColumn firstColumn, BasicColumn secondColumn, + protected OperatorFunction(String operator, BasicColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { super(firstColumn); this.secondColumn = Objects.requireNonNull(secondColumn); @@ -52,9 +52,7 @@ protected OperatorFunction copy() { public FragmentAndParameters render(RenderingContext renderingContext) { String paddedOperator = " " + operator + " "; //$NON-NLS-1$ //$NON-NLS-2$ - // note - the cast below is added for type inference issues in some compilers - return Stream.of(Stream.of((BasicColumn) column), - Stream.of(secondColumn), subsequentColumns.stream()) + return Stream.of(Stream.of(column, secondColumn), subsequentColumns.stream()) .flatMap(Function.identity()) .map(column -> column.render(renderingContext)) .collect(FragmentCollector.collect()) diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/Substring.java b/src/main/java/org/mybatis/dynamic/sql/select/function/Substring.java index 81b95ea5c..a987a3a1f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/Substring.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/Substring.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,6 +15,7 @@ */ package org.mybatis.dynamic.sql.select.function; +import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.FragmentAndParameters; @@ -24,7 +25,7 @@ public class Substring extends AbstractUniTypeFunction> { private final int offset; private final int length; - private Substring(BindableColumn column, int offset, int length) { + private Substring(BasicColumn column, int offset, int length) { super(column); this.offset = offset; this.length = length; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/Subtract.java b/src/main/java/org/mybatis/dynamic/sql/select/function/Subtract.java index 94e7e3c81..ae128fc98 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/Subtract.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/Subtract.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -23,7 +23,7 @@ public class Subtract extends OperatorFunction { - private Subtract(BindableColumn firstColumn, BasicColumn secondColumn, + private Subtract(BasicColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { super("-", firstColumn, secondColumn, subsequentColumns); //$NON-NLS-1$ } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/Upper.java b/src/main/java/org/mybatis/dynamic/sql/select/function/Upper.java index 2891032df..d4be9ff61 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/Upper.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/Upper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,13 +15,14 @@ */ package org.mybatis.dynamic.sql.select.function; +import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.FragmentAndParameters; public class Upper extends AbstractUniTypeFunction> { - private Upper(BindableColumn column) { + private Upper(BasicColumn column) { super(column); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/package-info.java b/src/main/java/org/mybatis/dynamic/sql/select/function/package-info.java new file mode 100644 index 000000000..4f8535279 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.select.function; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/ColumnBasedJoinCondition.java b/src/main/java/org/mybatis/dynamic/sql/select/join/ColumnBasedJoinCondition.java deleted file mode 100644 index 712010ca6..000000000 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/ColumnBasedJoinCondition.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.select.join; - -import java.util.Objects; - -import org.mybatis.dynamic.sql.BasicColumn; - -public abstract class ColumnBasedJoinCondition implements JoinCondition { - private final BasicColumn rightColumn; - - protected ColumnBasedJoinCondition(BasicColumn rightColumn) { - this.rightColumn = Objects.requireNonNull(rightColumn); - } - - public BasicColumn rightColumn() { - return rightColumn; - } - - @Override - public R accept(JoinConditionVisitor visitor) { - return visitor.visit(this); - } -} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/EqualTo.java b/src/main/java/org/mybatis/dynamic/sql/select/join/EqualTo.java deleted file mode 100644 index 6f12eb052..000000000 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/EqualTo.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.select.join; - -import org.mybatis.dynamic.sql.BasicColumn; - -public class EqualTo extends ColumnBasedJoinCondition { - - public EqualTo(BasicColumn rightColumn) { - super(rightColumn); - } - - @Override - public String operator() { - return "="; //$NON-NLS-1$ - } -} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCriterion.java b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCriterion.java deleted file mode 100644 index 81925f0e4..000000000 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCriterion.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.select.join; - -import java.util.Objects; - -import org.mybatis.dynamic.sql.BindableColumn; - -public class JoinCriterion { - - private final String connector; - private final BindableColumn leftColumn; - private final JoinCondition joinCondition; - - private JoinCriterion(Builder builder) { - connector = Objects.requireNonNull(builder.connector); - leftColumn = Objects.requireNonNull(builder.joinColumn); - joinCondition = Objects.requireNonNull(builder.joinCondition); - } - - public String connector() { - return connector; - } - - public BindableColumn leftColumn() { - return leftColumn; - } - - public JoinCondition joinCondition() { - return joinCondition; - } - - public static class Builder { - private String connector; - private BindableColumn joinColumn; - private JoinCondition joinCondition; - - public Builder withConnector(String connector) { - this.connector = connector; - return this; - } - - public Builder withJoinColumn(BindableColumn joinColumn) { - this.joinColumn = joinColumn; - return this; - } - - public Builder withJoinCondition(JoinCondition joinCondition) { - this.joinCondition = joinCondition; - return this; - } - - public JoinCriterion build() { - return new JoinCriterion<>(this); - } - } -} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinModel.java b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinModel.java index ef1265467..fe987c1b4 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,13 +20,14 @@ import java.util.Objects; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.TableExpression; import org.mybatis.dynamic.sql.util.Validator; public class JoinModel { private final List joinSpecifications = new ArrayList<>(); - private JoinModel(List joinSpecifications) { + private JoinModel(@Nullable List joinSpecifications) { Objects.requireNonNull(joinSpecifications); Validator.assertNotEmpty(joinSpecifications, "ERROR.15"); //$NON-NLS-1$ this.joinSpecifications.addAll(joinSpecifications); @@ -36,7 +37,7 @@ public Stream joinSpecifications() { return joinSpecifications.stream(); } - public static JoinModel of(List joinSpecifications) { + public static JoinModel of(@Nullable List joinSpecifications) { return new JoinModel(joinSpecifications); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinSpecification.java b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinSpecification.java index ab95dcf5e..8cefbba6b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinSpecification.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinSpecification.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,36 +15,30 @@ */ package org.mybatis.dynamic.sql.select.join; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; -import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.TableExpression; +import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionModel; import org.mybatis.dynamic.sql.util.Validator; -public class JoinSpecification { +public class JoinSpecification extends AbstractBooleanExpressionModel { private final TableExpression table; - private final List> joinCriteria; private final JoinType joinType; private JoinSpecification(Builder builder) { + super(builder); table = Objects.requireNonNull(builder.table); - joinCriteria = Objects.requireNonNull(builder.joinCriteria); joinType = Objects.requireNonNull(builder.joinType); - Validator.assertNotEmpty(joinCriteria, "ERROR.16"); //$NON-NLS-1$ + Validator.assertFalse(initialCriterion().isEmpty() && subCriteria().isEmpty(), + "ERROR.16"); //$NON-NLS-1$ } public TableExpression table() { return table; } - @SuppressWarnings("java:S1452") - public Stream> joinCriteria() { - return joinCriteria.stream(); - } - public JoinType joinType() { return joinType; } @@ -53,26 +47,15 @@ public static Builder withJoinTable(TableExpression table) { return new Builder().withJoinTable(table); } - public static class Builder { - private TableExpression table; - private final List> joinCriteria = new ArrayList<>(); - private JoinType joinType; + public static class Builder extends AbstractBuilder { + private @Nullable TableExpression table; + private @Nullable JoinType joinType; public Builder withJoinTable(TableExpression table) { this.table = table; return this; } - public Builder withJoinCriterion(JoinCriterion joinCriterion) { - this.joinCriteria.add(joinCriterion); - return this; - } - - public Builder withJoinCriteria(List> joinCriteria) { - this.joinCriteria.addAll(joinCriteria); - return this; - } - public Builder withJoinType(JoinType joinType) { this.joinType = joinType; return this; @@ -81,5 +64,10 @@ public Builder withJoinType(JoinType joinType) { public JoinSpecification build() { return new JoinSpecification(this); } + + @Override + protected Builder getThis() { + return this; + } } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinType.java b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinType.java index a6a456a71..e797d1d1d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinType.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/JoinType.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCondition.java b/src/main/java/org/mybatis/dynamic/sql/select/join/package-info.java similarity index 78% rename from src/main/java/org/mybatis/dynamic/sql/select/join/JoinCondition.java rename to src/main/java/org/mybatis/dynamic/sql/select/join/package-info.java index 183bd9a02..03ec51bcc 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/join/JoinCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/join/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -13,10 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@NullMarked package org.mybatis.dynamic.sql.select.join; -public interface JoinCondition { - String operator(); - - R accept(JoinConditionVisitor visitor); -} +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/package-info.java b/src/main/java/org/mybatis/dynamic/sql/select/package-info.java new file mode 100644 index 000000000..7e49fd4ec --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.select; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/DefaultSelectStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/select/render/DefaultSelectStatementProvider.java index 2542f80d0..a79029144 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/DefaultSelectStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/DefaultSelectStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,6 +20,8 @@ import java.util.Map; import java.util.Objects; +import org.jspecify.annotations.Nullable; + public class DefaultSelectStatementProvider implements SelectStatementProvider { private final String selectStatement; private final Map parameters; @@ -44,7 +46,7 @@ public static Builder withSelectStatement(String selectStatement) { } public static class Builder { - private String selectStatement; + private @Nullable String selectStatement; private final Map parameters = new HashMap<>(); public Builder withSelectStatement(String selectStatement) { diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/FetchFirstPagingModelRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/FetchFirstPagingModelRenderer.java index fcd62264b..31bbd9cb8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/FetchFirstPagingModelRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/FetchFirstPagingModelRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -51,30 +51,30 @@ private FragmentAndParameters renderFetchFirstRowsOnly() { } private FragmentAndParameters renderFetchFirstRowsOnly(Long fetchFirstRows) { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(); + RenderedParameterInfo fetchFirstParameterInfo = renderingContext.calculateFetchFirstRowsParameterInfo(); return FragmentAndParameters - .withFragment("fetch first " + parameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + .withFragment("fetch first " + fetchFirstParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + " rows only") //$NON-NLS-1$ - .withParameter(parameterInfo.parameterMapKey(), fetchFirstRows) + .withParameter(fetchFirstParameterInfo.parameterMapKey(), fetchFirstRows) .build(); } private FragmentAndParameters renderOffsetOnly(Long offset) { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(); - return FragmentAndParameters.withFragment("offset " + parameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + RenderedParameterInfo offsetParameterInfo = renderingContext.calculateOffsetParameterInfo(); + return FragmentAndParameters.withFragment("offset " + offsetParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + " rows") //$NON-NLS-1$ - .withParameter(parameterInfo.parameterMapKey(), offset) + .withParameter(offsetParameterInfo.parameterMapKey(), offset) .build(); } private FragmentAndParameters renderOffsetAndFetchFirstRows(Long offset, Long fetchFirstRows) { - RenderedParameterInfo parameterInfo1 = renderingContext.calculateParameterInfo(); - RenderedParameterInfo parameterInfo2 = renderingContext.calculateParameterInfo(); - return FragmentAndParameters.withFragment("offset " + parameterInfo1.renderedPlaceHolder() //$NON-NLS-1$ - + " rows fetch first " + parameterInfo2.renderedPlaceHolder() //$NON-NLS-1$ + RenderedParameterInfo offsetParameterInfo = renderingContext.calculateOffsetParameterInfo(); + RenderedParameterInfo fetchFirstParameterInfo = renderingContext.calculateFetchFirstRowsParameterInfo(); + return FragmentAndParameters.withFragment("offset " + offsetParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + + " rows fetch first " + fetchFirstParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + " rows only") //$NON-NLS-1$ - .withParameter(parameterInfo1.parameterMapKey(), offset) - .withParameter(parameterInfo2.parameterMapKey(), fetchFirstRows) + .withParameter(offsetParameterInfo.parameterMapKey(), offset) + .withParameter(fetchFirstParameterInfo.parameterMapKey(), fetchFirstRows) .build(); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/HavingRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/HavingRenderer.java index 0c8292a9e..f7feb8b3f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/HavingRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/HavingRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/JoinConditionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/JoinConditionRenderer.java deleted file mode 100644 index 2cba1a951..000000000 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/JoinConditionRenderer.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.select.render; - -import static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore; - -import java.util.Objects; - -import org.mybatis.dynamic.sql.BindableColumn; -import org.mybatis.dynamic.sql.render.RenderedParameterInfo; -import org.mybatis.dynamic.sql.render.RenderingContext; -import org.mybatis.dynamic.sql.select.join.ColumnBasedJoinCondition; -import org.mybatis.dynamic.sql.select.join.JoinConditionVisitor; -import org.mybatis.dynamic.sql.select.join.TypedJoinCondition; -import org.mybatis.dynamic.sql.util.FragmentAndParameters; - -public class JoinConditionRenderer implements JoinConditionVisitor { - private final BindableColumn leftColumn; - private final RenderingContext renderingContext; - - private JoinConditionRenderer(Builder builder) { - leftColumn = Objects.requireNonNull(builder.leftColumn); - renderingContext = Objects.requireNonNull(builder.renderingContext); - } - - @Override - public FragmentAndParameters visit(TypedJoinCondition condition) { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(leftColumn); - - return FragmentAndParameters - .withFragment(condition.operator() + spaceBefore(parameterInfo.renderedPlaceHolder())) - .withParameter(parameterInfo.parameterMapKey(), condition.value()) - .build(); - } - - @Override - public FragmentAndParameters visit(ColumnBasedJoinCondition condition) { - return condition.rightColumn().render(renderingContext) - .mapFragment(s -> condition.operator() + spaceBefore(s)); - } - - public static class Builder { - private BindableColumn leftColumn; - private RenderingContext renderingContext; - - public Builder withLeftColumn(BindableColumn leftColumn) { - this.leftColumn = leftColumn; - return this; - } - - public Builder withRenderingContext(RenderingContext renderingContext) { - this.renderingContext = renderingContext; - return this; - } - - public JoinConditionRenderer build() { - return new JoinConditionRenderer<>(this); - } - } -} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/JoinRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/JoinRenderer.java index 667b4e299..c1e8a0387 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/JoinRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/JoinRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,17 +15,17 @@ */ package org.mybatis.dynamic.sql.select.render; -import static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore; - import java.util.Objects; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.exception.InvalidSqlException; import org.mybatis.dynamic.sql.render.RenderingContext; -import org.mybatis.dynamic.sql.select.join.JoinCriterion; import org.mybatis.dynamic.sql.select.join.JoinModel; import org.mybatis.dynamic.sql.select.join.JoinSpecification; import org.mybatis.dynamic.sql.util.FragmentAndParameters; import org.mybatis.dynamic.sql.util.FragmentCollector; +import org.mybatis.dynamic.sql.util.Messages; public class JoinRenderer { private final JoinModel joinModel; @@ -46,43 +46,17 @@ public FragmentAndParameters render() { } private FragmentAndParameters renderJoinSpecification(JoinSpecification joinSpecification) { - FragmentAndParameters renderedTable = joinSpecification.table().accept(tableExpressionRenderer); - FragmentAndParameters renderedJoin = renderConditions(joinSpecification); - - String fragment = joinSpecification.joinType().type() - + spaceBefore(renderedTable.fragment()) - + spaceBefore(renderedJoin.fragment()); - - return FragmentAndParameters.withFragment(fragment) - .withParameters(renderedTable.parameters()) - .withParameters(renderedJoin.parameters()) - .build(); - } - - private FragmentAndParameters renderConditions(JoinSpecification joinSpecification) { - return joinSpecification.joinCriteria() - .map(this::renderCriterion) - .collect(FragmentCollector.collect()) - .toFragmentAndParameters(Collectors.joining(" ")); //$NON-NLS-1$ - } - - private FragmentAndParameters renderCriterion(JoinCriterion joinCriterion) { - FragmentAndParameters renderedColumn = joinCriterion.leftColumn().render(renderingContext); - - String prefix = joinCriterion.connector() - + spaceBefore(renderedColumn.fragment()); - - JoinConditionRenderer joinConditionRenderer = new JoinConditionRenderer.Builder() + FragmentCollector fc = new FragmentCollector(); + fc.add(FragmentAndParameters.fromFragment(joinSpecification.joinType().type())); + fc.add(joinSpecification.table().accept(tableExpressionRenderer)); + fc.add(JoinSpecificationRenderer + .withJoinSpecification(joinSpecification) .withRenderingContext(renderingContext) - .withLeftColumn(joinCriterion.leftColumn()) - .build(); - - FragmentAndParameters suffix = joinCriterion.joinCondition().accept(joinConditionRenderer); + .build() + .render() + .orElseThrow(() -> new InvalidSqlException(Messages.getString("ERROR.46")))); //$NON-NLS-1$ - return FragmentAndParameters.withFragment(prefix + spaceBefore(suffix.fragment())) - .withParameters(suffix.parameters()) - .withParameters(renderedColumn.parameters()) - .build(); + return fc.toFragmentAndParameters(Collectors.joining(" ")); //$NON-NLS-1$ } public static Builder withJoinModel(JoinModel joinModel) { @@ -90,9 +64,9 @@ public static Builder withJoinModel(JoinModel joinModel) { } public static class Builder { - private JoinModel joinModel; - private TableExpressionRenderer tableExpressionRenderer; - private RenderingContext renderingContext; + private @Nullable JoinModel joinModel; + private @Nullable TableExpressionRenderer tableExpressionRenderer; + private @Nullable RenderingContext renderingContext; public Builder withJoinModel(JoinModel joinModel) { this.joinModel = joinModel; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/JoinSpecificationRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/JoinSpecificationRenderer.java new file mode 100644 index 000000000..97766c171 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/JoinSpecificationRenderer.java @@ -0,0 +1,44 @@ +/* + * 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 org.mybatis.dynamic.sql.select.render; + +import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionRenderer; +import org.mybatis.dynamic.sql.select.join.JoinSpecification; + +public class JoinSpecificationRenderer extends AbstractBooleanExpressionRenderer { + private JoinSpecificationRenderer(Builder builder) { + super("on", builder); //$NON-NLS-1$ + } + + public static JoinSpecificationRenderer.Builder withJoinSpecification(JoinSpecification joinSpecification) { + return new Builder(joinSpecification); + } + + public static class Builder extends AbstractBuilder { + public Builder(JoinSpecification joinSpecification) { + super(joinSpecification); + } + + public JoinSpecificationRenderer build() { + return new JoinSpecificationRenderer(this); + } + + @Override + protected Builder getThis() { + return this; + } + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/LimitAndOffsetPagingModelRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/LimitAndOffsetPagingModelRenderer.java index 008c5a1af..ea6452b67 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/LimitAndOffsetPagingModelRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/LimitAndOffsetPagingModelRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -40,19 +40,19 @@ public FragmentAndParameters render() { } private FragmentAndParameters renderLimitOnly() { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(); - return FragmentAndParameters.withFragment("limit " + parameterInfo.renderedPlaceHolder()) //$NON-NLS-1$ - .withParameter(parameterInfo.parameterMapKey(), limit) + RenderedParameterInfo limitParameterInfo = renderingContext.calculateLimitParameterInfo(); + return FragmentAndParameters.withFragment("limit " + limitParameterInfo.renderedPlaceHolder()) //$NON-NLS-1$ + .withParameter(limitParameterInfo.parameterMapKey(), limit) .build(); } private FragmentAndParameters renderLimitAndOffset(Long offset) { - RenderedParameterInfo parameterInfo1 = renderingContext.calculateParameterInfo(); - RenderedParameterInfo parameterInfo2 = renderingContext.calculateParameterInfo(); - return FragmentAndParameters.withFragment("limit " + parameterInfo1.renderedPlaceHolder() //$NON-NLS-1$ - + " offset " + parameterInfo2.renderedPlaceHolder()) //$NON-NLS-1$ - .withParameter(parameterInfo1.parameterMapKey(), limit) - .withParameter(parameterInfo2.parameterMapKey(), offset) + RenderedParameterInfo limitParameterInfo = renderingContext.calculateLimitParameterInfo(); + RenderedParameterInfo offsetParameterInfo = renderingContext.calculateOffsetParameterInfo(); + return FragmentAndParameters.withFragment("limit " + limitParameterInfo.renderedPlaceHolder() //$NON-NLS-1$ + + " offset " + offsetParameterInfo.renderedPlaceHolder()) //$NON-NLS-1$ + .withParameter(limitParameterInfo.parameterMapKey(), limit) + .withParameter(offsetParameterInfo.parameterMapKey(), offset) .build(); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/MultiSelectRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/MultiSelectRenderer.java index 59e926f67..4daf576d4 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/MultiSelectRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/MultiSelectRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,9 +19,9 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.common.OrderByModel; import org.mybatis.dynamic.sql.common.OrderByRenderer; -import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.select.MultiSelectModel; @@ -36,11 +36,11 @@ public class MultiSelectRenderer { private final RenderingContext renderingContext; private MultiSelectRenderer(Builder builder) { + multiSelectModel = Objects.requireNonNull(builder.multiSelectModel); renderingContext = RenderingContext - .withRenderingStrategy(builder.renderingStrategy) - .withStatementConfiguration(builder.statementConfiguration) + .withRenderingStrategy(Objects.requireNonNull(builder.renderingStrategy)) + .withStatementConfiguration(multiSelectModel.statementConfiguration()) .build(); - multiSelectModel = Objects.requireNonNull(builder.multiSelectModel); } public SelectStatementProvider render() { @@ -65,21 +65,21 @@ private SelectStatementProvider toSelectStatementProvider(FragmentCollector frag } private FragmentAndParameters renderSelect(SelectModel selectModel) { - SelectStatementProvider selectStatement = selectModel.render(renderingContext); - - return FragmentAndParameters - .withFragment("(" + selectStatement.getSelectStatement() + ")") //$NON-NLS-1$ //$NON-NLS-2$ - .withParameters(selectStatement.getParameters()) - .build(); + return SubQueryRenderer.withSelectModel(selectModel) + .withRenderingContext(renderingContext) + .withPrefix("(") //$NON-NLS-1$ + .withSuffix(")") //$NON-NLS-1$ + .build() + .render(); } private FragmentAndParameters renderSelect(UnionQuery unionQuery) { - SelectStatementProvider selectStatement = unionQuery.selectModel().render(renderingContext); - - return FragmentAndParameters.withFragment( - unionQuery.connector() + " (" + selectStatement.getSelectStatement() + ")") //$NON-NLS-1$ //$NON-NLS-2$ - .withParameters(selectStatement.getParameters()) - .build(); + return SubQueryRenderer.withSelectModel(unionQuery.selectModel()) + .withRenderingContext(renderingContext) + .withPrefix(unionQuery.connector() + " (") //$NON-NLS-1$ + .withSuffix(")") //$NON-NLS-1$ + .build() + .render(); } private Optional renderOrderBy() { @@ -87,7 +87,7 @@ private Optional renderOrderBy() { } private FragmentAndParameters renderOrderBy(OrderByModel orderByModel) { - return new OrderByRenderer().render(orderByModel); + return new OrderByRenderer(renderingContext).render(orderByModel); } private Optional renderPagingModel() { @@ -102,10 +102,13 @@ private FragmentAndParameters renderPagingModel(PagingModel pagingModel) { .render(); } + public static Builder withMultiSelectModel(MultiSelectModel multiSelectModel) { + return new Builder().withMultiSelectModel(multiSelectModel); + } + public static class Builder { - private RenderingStrategy renderingStrategy; - private MultiSelectModel multiSelectModel; - private StatementConfiguration statementConfiguration; + private @Nullable RenderingStrategy renderingStrategy; + private @Nullable MultiSelectModel multiSelectModel; public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { this.renderingStrategy = renderingStrategy; @@ -117,11 +120,6 @@ public Builder withMultiSelectModel(MultiSelectModel multiSelectModel) { return this; } - public Builder withStatementConfiguration(StatementConfiguration statementConfiguration) { - this.statementConfiguration = statementConfiguration; - return this; - } - public MultiSelectRenderer build() { return new MultiSelectRenderer(this); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/PagingModelRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/PagingModelRenderer.java index 583b48e4b..6b79de960 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/PagingModelRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/PagingModelRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,6 +17,7 @@ import java.util.Objects; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.select.PagingModel; import org.mybatis.dynamic.sql.util.FragmentAndParameters; @@ -44,8 +45,8 @@ private FragmentAndParameters fetchFirstRender() { } public static class Builder { - private PagingModel pagingModel; - private RenderingContext renderingContext; + private @Nullable PagingModel pagingModel; + private @Nullable RenderingContext renderingContext; public Builder withRenderingContext(RenderingContext renderingContext) { this.renderingContext = renderingContext; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java index 0f3ddcb01..610ba6d10 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/QueryExpressionRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,6 +19,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.TableExpression; import org.mybatis.dynamic.sql.render.ExplicitTableAliasCalculator; @@ -43,7 +44,8 @@ private QueryExpressionRenderer(Builder builder) { queryExpression = Objects.requireNonNull(builder.queryExpression); TableAliasCalculator childTableAliasCalculator = calculateChildTableAliasCalculator(queryExpression); - renderingContext = builder.renderingContext.withChildTableAliasCalculator(childTableAliasCalculator); + renderingContext = Objects.requireNonNull(builder.renderingContext) + .withChildTableAliasCalculator(childTableAliasCalculator); tableExpressionRenderer = new TableExpressionRenderer.Builder() .withRenderingContext(renderingContext) @@ -139,12 +141,8 @@ private FragmentAndParameters calculateColumnList() { private FragmentAndParameters renderColumnAndAlias(BasicColumn selectListItem) { FragmentAndParameters renderedColumn = selectListItem.render(renderingContext); - String nameAndTableAlias = selectListItem.alias().map(a -> renderedColumn.fragment() + " as " + a) //$NON-NLS-1$ - .orElse(renderedColumn.fragment()); - - return FragmentAndParameters.withFragment(nameAndTableAlias) - .withParameters(renderedColumn.parameters()) - .build(); + return selectListItem.alias().map(a -> renderedColumn.mapFragment(f -> f + " as " + a)) //$NON-NLS-1$ + .orElse(renderedColumn); } private FragmentAndParameters renderTableExpression(TableExpression table) { @@ -203,8 +201,8 @@ public static Builder withQueryExpression(QueryExpressionModel model) { } public static class Builder { - private QueryExpressionModel queryExpression; - private RenderingContext renderingContext; + private @Nullable QueryExpressionModel queryExpression; + private @Nullable RenderingContext renderingContext; public Builder withRenderingContext(RenderingContext renderingContext) { this.renderingContext = renderingContext; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/SearchedCaseRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/SearchedCaseRenderer.java index d273c4c87..9fd58a591 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/SearchedCaseRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/SearchedCaseRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/SearchedCaseWhenConditionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/SearchedCaseWhenConditionRenderer.java index b7d9bfff3..f73150f5f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/SearchedCaseWhenConditionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/SearchedCaseWhenConditionRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java index 3f65a51e4..719d6aafe 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/SelectRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -16,71 +16,35 @@ package org.mybatis.dynamic.sql.select.render; import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; -import org.mybatis.dynamic.sql.common.OrderByModel; -import org.mybatis.dynamic.sql.common.OrderByRenderer; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.render.RenderingContext; -import org.mybatis.dynamic.sql.select.PagingModel; -import org.mybatis.dynamic.sql.select.QueryExpressionModel; +import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.util.FragmentAndParameters; -import org.mybatis.dynamic.sql.util.FragmentCollector; public class SelectRenderer { private final SelectModel selectModel; - private final RenderingContext renderingContext; + private final RenderingStrategy renderingStrategy; private SelectRenderer(Builder builder) { selectModel = Objects.requireNonNull(builder.selectModel); - renderingContext = Objects.requireNonNull(builder.renderingContext); + renderingStrategy = Objects.requireNonNull(builder.renderingStrategy); } public SelectStatementProvider render() { - FragmentCollector fragmentCollector = selectModel - .queryExpressions() - .map(this::renderQueryExpression) - .collect(FragmentCollector.collect()); - - renderOrderBy().ifPresent(fragmentCollector::add); - renderPagingModel().ifPresent(fragmentCollector::add); - - return toSelectStatementProvider(fragmentCollector); - } - - private SelectStatementProvider toSelectStatementProvider(FragmentCollector fragmentCollector) { - return DefaultSelectStatementProvider - .withSelectStatement(fragmentCollector.collectFragments(Collectors.joining(" "))) //$NON-NLS-1$ - .withParameters(fragmentCollector.parameters()) + RenderingContext renderingContext = RenderingContext.withRenderingStrategy(renderingStrategy) + .withStatementConfiguration(selectModel.statementConfiguration()) .build(); - } - private FragmentAndParameters renderQueryExpression(QueryExpressionModel queryExpressionModel) { - return QueryExpressionRenderer.withQueryExpression(queryExpressionModel) + FragmentAndParameters fragmentAndParameters = SubQueryRenderer.withSelectModel(selectModel) .withRenderingContext(renderingContext) .build() .render(); - } - - private Optional renderOrderBy() { - return selectModel.orderByModel().map(this::renderOrderBy); - } - private FragmentAndParameters renderOrderBy(OrderByModel orderByModel) { - return new OrderByRenderer().render(orderByModel); - } - - private Optional renderPagingModel() { - return selectModel.pagingModel().map(this::renderPagingModel); - } - - private FragmentAndParameters renderPagingModel(PagingModel pagingModel) { - return new PagingModelRenderer.Builder() - .withPagingModel(pagingModel) - .withRenderingContext(renderingContext) - .build() - .render(); + return DefaultSelectStatementProvider.withSelectStatement(fragmentAndParameters.fragment()) + .withParameters(fragmentAndParameters.parameters()) + .build(); } public static Builder withSelectModel(SelectModel selectModel) { @@ -88,11 +52,11 @@ public static Builder withSelectModel(SelectModel selectModel) { } public static class Builder { - private SelectModel selectModel; - private RenderingContext renderingContext; + private @Nullable SelectModel selectModel; + private @Nullable RenderingStrategy renderingStrategy; - public Builder withRenderingContext(RenderingContext renderingContext) { - this.renderingContext = renderingContext; + public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { + this.renderingStrategy = renderingStrategy; return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/SelectStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/select/render/SelectStatementProvider.java index 96a8b621c..42cec3f55 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/SelectStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/SelectStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/SimpleCaseRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/SimpleCaseRenderer.java index 2639d0b53..036a9c909 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/SimpleCaseRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/SimpleCaseRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -48,7 +48,9 @@ public FragmentAndParameters render() { } private FragmentAndParameters renderCase() { - return simpleCaseModel.column().render(renderingContext) + return simpleCaseModel.column().alias() + .map(FragmentAndParameters::fromFragment) + .orElseGet(() -> simpleCaseModel.column().render(renderingContext)) .mapFragment(f -> "case " + f); //$NON-NLS-1$ } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/SimpleCaseWhenConditionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/SimpleCaseWhenConditionRenderer.java index e9108806e..1bb6e2adc 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/SimpleCaseWhenConditionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/SimpleCaseWhenConditionRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,7 +19,7 @@ import java.util.stream.Collectors; import org.mybatis.dynamic.sql.BindableColumn; -import org.mybatis.dynamic.sql.VisitableCondition; +import org.mybatis.dynamic.sql.RenderableCondition; import org.mybatis.dynamic.sql.render.RenderedParameterInfo; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.select.caseexpression.BasicWhenCondition; @@ -28,20 +28,14 @@ import org.mybatis.dynamic.sql.util.FragmentAndParameters; import org.mybatis.dynamic.sql.util.FragmentCollector; import org.mybatis.dynamic.sql.util.Validator; -import org.mybatis.dynamic.sql.where.render.DefaultConditionVisitor; public class SimpleCaseWhenConditionRenderer implements SimpleCaseWhenConditionVisitor { private final RenderingContext renderingContext; private final BindableColumn column; - private final DefaultConditionVisitor conditionVisitor; public SimpleCaseWhenConditionRenderer(RenderingContext renderingContext, BindableColumn column) { this.renderingContext = Objects.requireNonNull(renderingContext); this.column = Objects.requireNonNull(column); - conditionVisitor = new DefaultConditionVisitor.Builder() - .withColumn(column) - .withRenderingContext(renderingContext) - .build(); } @Override @@ -63,12 +57,12 @@ public FragmentAndParameters visit(BasicWhenCondition whenCondition) { .toFragmentAndParameters(Collectors.joining(", ")); //$NON-NLS-1$ } - private boolean shouldRender(VisitableCondition condition) { + private boolean shouldRender(RenderableCondition condition) { return condition.shouldRender(renderingContext); } - private FragmentAndParameters renderCondition(VisitableCondition condition) { - return condition.accept(conditionVisitor); + private FragmentAndParameters renderCondition(RenderableCondition condition) { + return condition.renderCondition(renderingContext, column); } private FragmentAndParameters renderBasicValue(T value) { diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/SubQueryRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/SubQueryRenderer.java new file mode 100644 index 000000000..359e9c1f0 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/SubQueryRenderer.java @@ -0,0 +1,122 @@ +/* + * 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 org.mybatis.dynamic.sql.select.render; + +import java.util.Objects; +import java.util.stream.Collectors; + +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.common.OrderByModel; +import org.mybatis.dynamic.sql.common.OrderByRenderer; +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.select.PagingModel; +import org.mybatis.dynamic.sql.select.QueryExpressionModel; +import org.mybatis.dynamic.sql.select.SelectModel; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; +import org.mybatis.dynamic.sql.util.FragmentCollector; + +public class SubQueryRenderer { + private final SelectModel selectModel; + private final RenderingContext renderingContext; + private final String prefix; + private final String suffix; + + private SubQueryRenderer(Builder builder) { + selectModel = Objects.requireNonNull(builder.selectModel); + renderingContext = Objects.requireNonNull(builder.renderingContext); + prefix = builder.prefix == null ? "" : builder.prefix; //$NON-NLS-1$ + suffix = builder.suffix == null ? "" : builder.suffix; //$NON-NLS-1$ + } + + public FragmentAndParameters render() { + FragmentCollector fragmentCollector = selectModel + .queryExpressions() + .map(this::renderQueryExpression) + .collect(FragmentCollector.collect()); + + selectModel.orderByModel() + .map(this::renderOrderBy) + .ifPresent(fragmentCollector::add); + + selectModel.pagingModel() + .map(this::renderPagingModel) + .ifPresent(fragmentCollector::add); + + selectModel.forClause() + .map(FragmentAndParameters::fromFragment) + .ifPresent(fragmentCollector::add); + + selectModel.waitClause() + .map(FragmentAndParameters::fromFragment) + .ifPresent(fragmentCollector::add); + + return fragmentCollector.toFragmentAndParameters(Collectors.joining(" ", prefix, suffix)); //$NON-NLS-1$ + } + + private FragmentAndParameters renderQueryExpression(QueryExpressionModel queryExpressionModel) { + return QueryExpressionRenderer.withQueryExpression(queryExpressionModel) + .withRenderingContext(renderingContext) + .build() + .render(); + } + + private FragmentAndParameters renderOrderBy(OrderByModel orderByModel) { + return new OrderByRenderer(renderingContext).render(orderByModel); + } + + private FragmentAndParameters renderPagingModel(PagingModel pagingModel) { + return new PagingModelRenderer.Builder() + .withPagingModel(pagingModel) + .withRenderingContext(renderingContext) + .build() + .render(); + } + + public static Builder withSelectModel(SelectModel selectModel) { + return new Builder().withSelectModel(selectModel); + } + + public static class Builder { + private @Nullable SelectModel selectModel; + private @Nullable RenderingContext renderingContext; + private @Nullable String prefix; + private @Nullable String suffix; + + public Builder withRenderingContext(RenderingContext renderingContext) { + this.renderingContext = renderingContext; + return this; + } + + public Builder withSelectModel(SelectModel selectModel) { + this.selectModel = selectModel; + return this; + } + + public Builder withPrefix(String prefix) { + this.prefix = prefix; + return this; + } + + public Builder withSuffix(String suffix) { + this.suffix = suffix; + return this; + } + + public SubQueryRenderer build() { + return new SubQueryRenderer(this); + } + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/TableExpressionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/select/render/TableExpressionRenderer.java index ccb9b1d84..8114e2411 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/render/TableExpressionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/TableExpressionRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,10 +15,9 @@ */ package org.mybatis.dynamic.sql.select.render; -import static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore; - import java.util.Objects; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.TableExpressionVisitor; import org.mybatis.dynamic.sql.render.RenderingContext; @@ -39,25 +38,19 @@ public FragmentAndParameters visit(SqlTable table) { @Override public FragmentAndParameters visit(SubQuery subQuery) { - SelectStatementProvider selectStatement = subQuery.selectModel().render(renderingContext); - - String fragment = "(" + selectStatement.getSelectStatement() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ - - fragment = applyAlias(fragment, subQuery); - - return FragmentAndParameters.withFragment(fragment) - .withParameters(selectStatement.getParameters()) - .build(); - } + String suffix = subQuery.alias().map(a -> ") " + a) //$NON-NLS-1$ + .orElse(")"); //$NON-NLS-1$ - private String applyAlias(String fragment, SubQuery subQuery) { - return subQuery.alias() - .map(a -> fragment + spaceBefore(a)) - .orElse(fragment); + return SubQueryRenderer.withSelectModel(subQuery.selectModel()) + .withRenderingContext(renderingContext) + .withPrefix("(")//$NON-NLS-1$ + .withSuffix(suffix) + .build() + .render(); } public static class Builder { - private RenderingContext renderingContext; + private @Nullable RenderingContext renderingContext; public Builder withRenderingContext(RenderingContext renderingContext) { this.renderingContext = renderingContext; diff --git a/src/main/java/org/mybatis/dynamic/sql/select/render/package-info.java b/src/main/java/org/mybatis/dynamic/sql/select/render/package-info.java new file mode 100644 index 000000000..d2f457254 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/render/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.select.render; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java b/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java index 8cf7259a3..fbac97595 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -24,7 +24,7 @@ import java.util.function.Function; import java.util.function.Supplier; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.SortSpecification; import org.mybatis.dynamic.sql.SqlColumn; @@ -39,7 +39,6 @@ import org.mybatis.dynamic.sql.util.NullMapping; import org.mybatis.dynamic.sql.util.SelectMapping; import org.mybatis.dynamic.sql.util.StringConstantMapping; -import org.mybatis.dynamic.sql.util.Utilities; import org.mybatis.dynamic.sql.util.ValueMapping; import org.mybatis.dynamic.sql.util.ValueOrNullMapping; import org.mybatis.dynamic.sql.util.ValueWhenPresentMapping; @@ -47,19 +46,19 @@ import org.mybatis.dynamic.sql.where.AbstractWhereStarter; import org.mybatis.dynamic.sql.where.EmbeddedWhereModel; -public class UpdateDSL extends AbstractWhereStarter.UpdateWhereBuilder, UpdateDSL> - implements Buildable { +public class UpdateDSL implements AbstractWhereStarter.UpdateWhereBuilder, UpdateDSL>, + Buildable { private final Function adapterFunction; private final List columnMappings = new ArrayList<>(); private final SqlTable table; - private final String tableAlias; - private UpdateWhereBuilder whereBuilder; + private final @Nullable String tableAlias; + private @Nullable UpdateWhereBuilder whereBuilder; private final StatementConfiguration statementConfiguration = new StatementConfiguration(); - private Long limit; - private OrderByModel orderByModel; + private @Nullable Long limit; + private @Nullable OrderByModel orderByModel; - private UpdateDSL(SqlTable table, String tableAlias, Function adapterFunction) { + private UpdateDSL(SqlTable table, @Nullable String tableAlias, Function adapterFunction) { this.table = Objects.requireNonNull(table); this.tableAlias = tableAlias; this.adapterFunction = Objects.requireNonNull(adapterFunction); @@ -71,11 +70,15 @@ public SetClauseFinisher set(SqlColumn column) { @Override public UpdateWhereBuilder where() { - whereBuilder = Utilities.buildIfNecessary(whereBuilder, UpdateWhereBuilder::new); + whereBuilder = Objects.requireNonNullElseGet(whereBuilder, UpdateWhereBuilder::new); return whereBuilder; } public UpdateDSL limit(long limit) { + return limitWhenPresent(limit); + } + + public UpdateDSL limitWhenPresent(@Nullable Long limit) { this.limit = limit; return this; } @@ -95,7 +98,6 @@ public UpdateDSL orderBy(Collection columns) { * * @return the update model */ - @NotNull @Override public R build() { UpdateModel updateModel = UpdateModel.withTable(table) @@ -116,7 +118,8 @@ public UpdateDSL configureStatement(Consumer consumer return this; } - public static UpdateDSL update(Function adapterFunction, SqlTable table, String tableAlias) { + public static UpdateDSL update(Function adapterFunction, SqlTable table, + @Nullable String tableAlias) { return new UpdateDSL<>(table, tableAlias, adapterFunction); } @@ -170,20 +173,20 @@ public UpdateDSL equalTo(BasicColumn rightColumn) { return UpdateDSL.this; } - public UpdateDSL equalToOrNull(T value) { + public UpdateDSL equalToOrNull(@Nullable T value) { return equalToOrNull(() -> value); } - public UpdateDSL equalToOrNull(Supplier valueSupplier) { + public UpdateDSL equalToOrNull(Supplier<@Nullable T> valueSupplier) { columnMappings.add(ValueOrNullMapping.of(column, valueSupplier)); return UpdateDSL.this; } - public UpdateDSL equalToWhenPresent(T value) { + public UpdateDSL equalToWhenPresent(@Nullable T value) { return equalToWhenPresent(() -> value); } - public UpdateDSL equalToWhenPresent(Supplier valueSupplier) { + public UpdateDSL equalToWhenPresent(Supplier<@Nullable T> valueSupplier) { columnMappings.add(ValueWhenPresentMapping.of(column, valueSupplier)); return UpdateDSL.this; } @@ -196,7 +199,11 @@ private UpdateWhereBuilder() { } public UpdateDSL limit(long limit) { - return UpdateDSL.this.limit(limit); + return limitWhenPresent(limit); + } + + public UpdateDSL limitWhenPresent(@Nullable Long limit) { + return UpdateDSL.this.limitWhenPresent(limit); } public UpdateDSL orderBy(SortSpecification... columns) { @@ -208,7 +215,6 @@ public UpdateDSL orderBy(Collection columns) { return UpdateDSL.this; } - @NotNull @Override public R build() { return UpdateDSL.this.build(); diff --git a/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSLCompleter.java b/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSLCompleter.java index 70abc3a5e..30512c46a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSLCompleter.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/UpdateDSLCompleter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/update/UpdateModel.java b/src/main/java/org/mybatis/dynamic/sql/update/UpdateModel.java index 7ff82df26..7fd07f766 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/UpdateModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/UpdateModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,7 +21,7 @@ import java.util.Optional; import java.util.stream.Stream; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.common.CommonBuilder; import org.mybatis.dynamic.sql.common.OrderByModel; @@ -35,11 +35,11 @@ public class UpdateModel { private final SqlTable table; - private final String tableAlias; - private final EmbeddedWhereModel whereModel; + private final @Nullable String tableAlias; + private final @Nullable EmbeddedWhereModel whereModel; private final List columnMappings; - private final Long limit; - private final OrderByModel orderByModel; + private final @Nullable Long limit; + private final @Nullable OrderByModel orderByModel; private final StatementConfiguration statementConfiguration; private UpdateModel(Builder builder) { @@ -77,11 +77,13 @@ public Optional orderByModel() { return Optional.ofNullable(orderByModel); } - @NotNull + public StatementConfiguration statementConfiguration() { + return statementConfiguration; + } + public UpdateStatementProvider render(RenderingStrategy renderingStrategy) { return UpdateRenderer.withUpdateModel(this) .withRenderingStrategy(renderingStrategy) - .withStatementConfiguration(statementConfiguration) .build() .render(); } diff --git a/src/main/java/org/mybatis/dynamic/sql/update/package-info.java b/src/main/java/org/mybatis/dynamic/sql/update/package-info.java new file mode 100644 index 000000000..b1b75a4f4 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/update/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.update; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/update/render/DefaultUpdateStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/update/render/DefaultUpdateStatementProvider.java index a103bd604..eb7f9fba6 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/render/DefaultUpdateStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/render/DefaultUpdateStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,13 +19,15 @@ import java.util.Map; import java.util.Objects; +import org.jspecify.annotations.Nullable; + public class DefaultUpdateStatementProvider implements UpdateStatementProvider { private final String updateStatement; - private final Map parameters = new HashMap<>(); + private final Map parameters; private DefaultUpdateStatementProvider(Builder builder) { updateStatement = Objects.requireNonNull(builder.updateStatement); - parameters.putAll(builder.parameters); + parameters = builder.parameters; } @Override @@ -43,7 +45,7 @@ public static Builder withUpdateStatement(String updateStatement) { } public static class Builder { - private String updateStatement; + private @Nullable String updateStatement; private final Map parameters = new HashMap<>(); public Builder withUpdateStatement(String updateStatement) { diff --git a/src/main/java/org/mybatis/dynamic/sql/update/render/SetPhraseVisitor.java b/src/main/java/org/mybatis/dynamic/sql/update/render/SetPhraseVisitor.java index 07d955c80..5667830e2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/render/SetPhraseVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/render/SetPhraseVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,9 +18,10 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.render.RenderedParameterInfo; import org.mybatis.dynamic.sql.render.RenderingContext; -import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; +import org.mybatis.dynamic.sql.select.render.SubQueryRenderer; import org.mybatis.dynamic.sql.util.AbstractColumnMapping; import org.mybatis.dynamic.sql.util.ColumnToColumnMapping; import org.mybatis.dynamic.sql.util.ConstantMapping; @@ -84,31 +85,26 @@ public Optional visit(ValueWhenPresentMapping mapp @Override public Optional visit(SelectMapping mapping) { - SelectStatementProvider selectStatement = mapping.selectModel().render(renderingContext); - String fragment = renderingContext.aliasedColumnName(mapping.column()) - + " = (" //$NON-NLS-1$ - + selectStatement.getSelectStatement() - + ")"; //$NON-NLS-1$ + String prefix = renderingContext.aliasedColumnName(mapping.column()) + " = ("; //$NON-NLS-1$ - return FragmentAndParameters.withFragment(fragment) - .withParameters(selectStatement.getParameters()) - .buildOptional(); + FragmentAndParameters fragmentAndParameters = SubQueryRenderer.withSelectModel(mapping.selectModel()) + .withRenderingContext(renderingContext) + .withPrefix(prefix) + .withSuffix(")") //$NON-NLS-1$ + .build() + .render(); + + return Optional.of(fragmentAndParameters); } @Override public Optional visit(ColumnToColumnMapping mapping) { - FragmentAndParameters renderedColumn = mapping.rightColumn().render(renderingContext); - - String setPhrase = renderingContext.aliasedColumnName(mapping.column()) - + " = " //$NON-NLS-1$ - + renderedColumn.fragment(); - - return FragmentAndParameters.withFragment(setPhrase) - .withParameters(renderedColumn.parameters()) - .buildOptional(); + FragmentAndParameters fragmentAndParameters = mapping.rightColumn().render(renderingContext) + .mapFragment(f -> renderingContext.aliasedColumnName(mapping.column()) + " = " + f); //$NON-NLS-1$ + return Optional.of(fragmentAndParameters); } - private Optional buildValueFragment(AbstractColumnMapping mapping, T value) { + private Optional buildValueFragment(AbstractColumnMapping mapping, @Nullable T value) { RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(mapping.column()); String setPhrase = renderingContext.aliasedColumnName(mapping.column()) + " = " //$NON-NLS-1$ diff --git a/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java b/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java index 50397ee7f..ee0f74961 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,14 +15,13 @@ */ package org.mybatis.dynamic.sql.update.render; -import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.common.OrderByModel; import org.mybatis.dynamic.sql.common.OrderByRenderer; -import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.render.ExplicitTableAliasCalculator; import org.mybatis.dynamic.sql.render.RenderedParameterInfo; import org.mybatis.dynamic.sql.render.RenderingContext; @@ -47,7 +46,7 @@ private UpdateRenderer(Builder builder) { renderingContext = RenderingContext .withRenderingStrategy(Objects.requireNonNull(builder.renderingStrategy)) .withTableAliasCalculator(tableAliasCalculator) - .withStatementConfiguration(builder.statementConfiguration) + .withStatementConfiguration(updateModel.statementConfiguration()) .build(); visitor = new SetPhraseVisitor(renderingContext); } @@ -77,24 +76,15 @@ private FragmentAndParameters calculateUpdateStatementStart() { } private FragmentAndParameters calculateSetPhrase() { - List> fragmentsAndParameters = updateModel.columnMappings() - .map(m -> m.accept(visitor)) - .collect(Collectors.toList()); - - Validator.assertFalse(fragmentsAndParameters.stream().noneMatch(Optional::isPresent), - "ERROR.18"); //$NON-NLS-1$ - - FragmentCollector fragmentCollector = fragmentsAndParameters.stream() - .filter(Optional::isPresent) - .map(Optional::get) + FragmentCollector fragmentCollector = updateModel.columnMappings() + .map(m -> m.accept(visitor)) + .flatMap(Optional::stream) .collect(FragmentCollector.collect()); - return toSetPhrase(fragmentCollector); - } + Validator.assertFalse(fragmentCollector.isEmpty(), "ERROR.18"); //$NON-NLS-1$ - private FragmentAndParameters toSetPhrase(FragmentCollector fragmentCollector) { return fragmentCollector.toFragmentAndParameters( - Collectors.joining(", ", "set ", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Collectors.joining(", ", "set ", "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } private Optional calculateWhereClause() { @@ -110,7 +100,7 @@ private Optional calculateLimitClause() { } private FragmentAndParameters renderLimitClause(Long limit) { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(); + RenderedParameterInfo parameterInfo = renderingContext.calculateLimitParameterInfo(); return FragmentAndParameters.withFragment("limit " + parameterInfo.renderedPlaceHolder()) //$NON-NLS-1$ .withParameter(parameterInfo.parameterMapKey(), limit) @@ -122,7 +112,7 @@ private Optional calculateOrderByClause() { } private FragmentAndParameters renderOrderByClause(OrderByModel orderByModel) { - return new OrderByRenderer().render(orderByModel); + return new OrderByRenderer(renderingContext).render(orderByModel); } public static Builder withUpdateModel(UpdateModel updateModel) { @@ -130,9 +120,8 @@ public static Builder withUpdateModel(UpdateModel updateModel) { } public static class Builder { - private UpdateModel updateModel; - private RenderingStrategy renderingStrategy; - private StatementConfiguration statementConfiguration; + private @Nullable UpdateModel updateModel; + private @Nullable RenderingStrategy renderingStrategy; public Builder withUpdateModel(UpdateModel updateModel) { this.updateModel = updateModel; @@ -144,11 +133,6 @@ public Builder withRenderingStrategy(RenderingStrategy renderingStrategy) { return this; } - public Builder withStatementConfiguration(StatementConfiguration statementConfiguration) { - this.statementConfiguration = statementConfiguration; - return this; - } - public UpdateRenderer build() { return new UpdateRenderer(this); } diff --git a/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateStatementProvider.java b/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateStatementProvider.java index 539b41b80..087741ae2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateStatementProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/update/render/UpdateStatementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/update/render/package-info.java b/src/main/java/org/mybatis/dynamic/sql/update/render/package-info.java new file mode 100644 index 000000000..625393ac4 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/update/render/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.update.render; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/util/AbstractColumnMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/AbstractColumnMapping.java index e86e69738..4107e9590 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/AbstractColumnMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/AbstractColumnMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/Buildable.java b/src/main/java/org/mybatis/dynamic/sql/util/Buildable.java index eab57548f..1dcbe70ea 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/Buildable.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/Buildable.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,10 +15,7 @@ */ package org.mybatis.dynamic.sql.util; -import org.jetbrains.annotations.NotNull; - @FunctionalInterface public interface Buildable { - @NotNull T build(); -} \ No newline at end of file +} diff --git a/src/main/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitor.java b/src/main/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitor.java index 37c785cd2..19f949f09 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/ColumnToColumnMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/ColumnToColumnMapping.java index d72607cc2..b287ce215 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/ColumnToColumnMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/ColumnToColumnMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/ConfigurableStatement.java b/src/main/java/org/mybatis/dynamic/sql/util/ConfigurableStatement.java index d87edac4b..9fedc85a0 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/ConfigurableStatement.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/ConfigurableStatement.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/ConstantMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/ConstantMapping.java index c813559f3..5396e3499 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/ConstantMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/ConstantMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/FragmentAndParameters.java b/src/main/java/org/mybatis/dynamic/sql/util/FragmentAndParameters.java index c6b65b3b0..3426accf8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/FragmentAndParameters.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/FragmentAndParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,12 +15,15 @@ */ package org.mybatis.dynamic.sql.util; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.UnaryOperator; +import org.jspecify.annotations.Nullable; + public class FragmentAndParameters { private final String fragment; @@ -28,7 +31,7 @@ public class FragmentAndParameters { private FragmentAndParameters(Builder builder) { fragment = Objects.requireNonNull(builder.fragment); - parameters = Objects.requireNonNull(builder.parameters); + parameters = Collections.unmodifiableMap(builder.parameters); } public String fragment() { @@ -46,7 +49,7 @@ public Map parameters() { * @return a new instance with the same parameters and a transformed fragment */ public FragmentAndParameters mapFragment(UnaryOperator mapper) { - return FragmentAndParameters.withFragment(mapper.apply(fragment)) + return withFragment(mapper.apply(fragment)) .withParameters(parameters) .build(); } @@ -60,7 +63,7 @@ public static FragmentAndParameters fromFragment(String fragment) { } public static class Builder { - private String fragment; + private @Nullable String fragment; private final Map parameters = new HashMap<>(); public Builder withFragment(String fragment) { @@ -68,7 +71,10 @@ public Builder withFragment(String fragment) { return this; } - public Builder withParameter(String key, Object value) { + public Builder withParameter(String key, @Nullable Object value) { + // the value can be null because a parameter type converter may return null + + //noinspection DataFlowIssue parameters.put(key, value); return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/FragmentCollector.java b/src/main/java/org/mybatis/dynamic/sql/util/FragmentCollector.java index cc043572f..410d7a035 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/FragmentCollector.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/FragmentCollector.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -16,6 +16,7 @@ package org.mybatis.dynamic.sql.util; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -23,7 +24,8 @@ import java.util.stream.Collector; public class FragmentCollector { - final List fragments = new ArrayList<>(); + final List fragments = new ArrayList<>(); + final Map parameters = new HashMap<>(); public FragmentCollector() { super(); @@ -34,20 +36,22 @@ private FragmentCollector(FragmentAndParameters initialFragment) { } public void add(FragmentAndParameters fragmentAndParameters) { - fragments.add(fragmentAndParameters); + fragments.add(fragmentAndParameters.fragment()); + parameters.putAll(fragmentAndParameters.parameters()); } public FragmentCollector merge(FragmentCollector other) { fragments.addAll(other.fragments); + parameters.putAll(other.parameters); return this; } public Optional firstFragment() { - return fragments.stream().findFirst().map(FragmentAndParameters::fragment); + return fragments.stream().findFirst(); } public String collectFragments(Collector fragmentCollector) { - return fragments.stream().map(FragmentAndParameters::fragment).collect(fragmentCollector); + return fragments.stream().collect(fragmentCollector); } public FragmentAndParameters toFragmentAndParameters(Collector fragmentCollector) { @@ -57,9 +61,7 @@ public FragmentAndParameters toFragmentAndParameters(Collector parameters() { - return fragments.stream() - .map(FragmentAndParameters::parameters) - .collect(HashMap::new, HashMap::putAll, HashMap::putAll); + return Collections.unmodifiableMap(parameters); } public boolean hasMultipleFragments() { diff --git a/src/main/java/org/mybatis/dynamic/sql/util/GeneralInsertMappingVisitor.java b/src/main/java/org/mybatis/dynamic/sql/util/GeneralInsertMappingVisitor.java index ac20a6faf..e55e3d100 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/GeneralInsertMappingVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/GeneralInsertMappingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/InsertMappingVisitor.java b/src/main/java/org/mybatis/dynamic/sql/util/InsertMappingVisitor.java index 39add47a1..60770ec15 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/InsertMappingVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/InsertMappingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/InternalError.java b/src/main/java/org/mybatis/dynamic/sql/util/InternalError.java index e437b8728..ed6b0a3cc 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/InternalError.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/InternalError.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/Messages.java b/src/main/java/org/mybatis/dynamic/sql/util/Messages.java index 1c013bb10..649dd7747 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/Messages.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/Messages.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/MultiRowInsertMappingVisitor.java b/src/main/java/org/mybatis/dynamic/sql/util/MultiRowInsertMappingVisitor.java index 6132ce90e..18a75221d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/MultiRowInsertMappingVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/MultiRowInsertMappingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/NullMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/NullMapping.java index b30430f0f..ccd95d52f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/NullMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/NullMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/Predicates.java b/src/main/java/org/mybatis/dynamic/sql/util/Predicates.java index 0c66c921d..4fb7efa99 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/Predicates.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/Predicates.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,10 +17,12 @@ import java.util.function.BiPredicate; +import org.jspecify.annotations.Nullable; + public class Predicates { private Predicates() {} - public static BiPredicate bothPresent() { + public static BiPredicate<@Nullable T, @Nullable T> bothPresent() { return (v1, v2) -> v1 != null && v2 != null; } } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/PropertyMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/PropertyMapping.java index e0a4f6779..87023c7e9 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/PropertyMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/PropertyMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/PropertyWhenPresentMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/PropertyWhenPresentMapping.java index 3634e6244..e5f6f3e62 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/PropertyWhenPresentMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/PropertyWhenPresentMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/RowMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/RowMapping.java index 20f99940c..9ece580ca 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/RowMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/RowMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/SelectMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/SelectMapping.java index 5a24c9956..b588ece67 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/SelectMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/SelectMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/SqlProviderAdapter.java b/src/main/java/org/mybatis/dynamic/sql/util/SqlProviderAdapter.java index fafcc6bda..102a54a75 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/SqlProviderAdapter.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/SqlProviderAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,7 +17,6 @@ import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider; import org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider; @@ -76,7 +75,7 @@ public String insertMultipleWithGeneratedKeys(Map parameterMap) .map(Map.Entry::getValue) .filter(String.class::isInstance) .map(String.class::cast) - .collect(Collectors.toList()); + .toList(); if (entries.size() == 1) { return entries.get(0); diff --git a/src/main/java/org/mybatis/dynamic/sql/util/StringConstantMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/StringConstantMapping.java index 8c2cf8ccc..c462bd93c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/StringConstantMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/StringConstantMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/StringUtilities.java b/src/main/java/org/mybatis/dynamic/sql/util/StringUtilities.java index 3ef16be5e..aad161f0c 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/StringUtilities.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/StringUtilities.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -25,10 +25,6 @@ static String spaceBefore(String in) { return " " + in; //$NON-NLS-1$ } - static String safelyUpperCase(String s) { - return s == null ? null : s.toUpperCase(); - } - static String toCamelCase(String inputString) { StringBuilder sb = new StringBuilder(); @@ -44,7 +40,7 @@ static String toCamelCase(String inputString) { sb.append(Character.toLowerCase(c)); } } else { - if (sb.length() > 0) { + if (!sb.isEmpty()) { nextUpperCase = true; } } @@ -56,6 +52,15 @@ static String toCamelCase(String inputString) { static String formatConstantForSQL(String in) { String escaped = in.replace("'", "''"); //$NON-NLS-1$ //$NON-NLS-2$ return "'" + escaped + "'"; //$NON-NLS-1$ //$NON-NLS-2$ + } + + static T upperCaseIfPossible(T value) { + if (value instanceof String) { + @SuppressWarnings("unchecked") + T t = (T) ((String) value).toUpperCase(); + return t; + } + return value; } } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/UpdateMappingVisitor.java b/src/main/java/org/mybatis/dynamic/sql/util/UpdateMappingVisitor.java index 9dde23da1..8d06585ab 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/UpdateMappingVisitor.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/UpdateMappingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/Utilities.java b/src/main/java/org/mybatis/dynamic/sql/util/Utilities.java index 780369175..0c3bd188f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/Utilities.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/Utilities.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,14 +15,10 @@ */ package org.mybatis.dynamic.sql.util; -import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; public interface Utilities { - static T buildIfNecessary(T current, Supplier builder) { - return current == null ? builder.get() : current; - } - - static long safelyUnbox(Long l) { + static long safelyUnbox(@Nullable Long l) { return l == null ? 0 : l; } } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/Validator.java b/src/main/java/org/mybatis/dynamic/sql/util/Validator.java index 1d4b3f7b8..7563bfc70 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/Validator.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/Validator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,6 +17,7 @@ import java.util.Collection; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.exception.InvalidSqlException; public class Validator { @@ -26,12 +27,20 @@ public static void assertNotEmpty(Collection collection, String messageNumber assertFalse(collection.isEmpty(), messageNumber); } + public static void assertNotEmpty(Collection collection, String messageNumber, String p1) { + assertFalse(collection.isEmpty(), messageNumber, p1); + } + public static void assertFalse(boolean condition, String messageNumber) { - internalAssertFalse(condition, Messages.getString(messageNumber)); + if (condition) { + throw new InvalidSqlException(Messages.getString(messageNumber)); + } } public static void assertFalse(boolean condition, String messageNumber, String p1) { - internalAssertFalse(condition, Messages.getString(messageNumber, p1)); + if (condition) { + throw new InvalidSqlException(Messages.getString(messageNumber, p1)); + } } public static void assertTrue(boolean condition, String messageNumber) { @@ -42,9 +51,9 @@ public static void assertTrue(boolean condition, String messageNumber, String p1 assertFalse(!condition, messageNumber, p1); } - private static void internalAssertFalse(boolean condition, String message) { - if (condition) { - throw new InvalidSqlException(message); + public static void assertNull(@Nullable Object object, String messageNumber) { + if (object != null) { + throw new InvalidSqlException(Messages.getString(messageNumber)); } } } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/ValueMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/ValueMapping.java index 9028c9c4e..fc8681010 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/ValueMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/ValueMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,6 +18,7 @@ import java.util.Objects; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlColumn; public class ValueMapping extends AbstractColumnMapping { @@ -32,7 +33,7 @@ private ValueMapping(SqlColumn column, Supplier valueSupplier) { localColumn = Objects.requireNonNull(column); } - public Object value() { + public @Nullable Object value() { return localColumn.convertParameterType(valueSupplier.get()); } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/ValueOrNullMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/ValueOrNullMapping.java index 85690db59..f39a706ac 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/ValueOrNullMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/ValueOrNullMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,15 +19,16 @@ import java.util.Optional; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlColumn; public class ValueOrNullMapping extends AbstractColumnMapping { - private final Supplier valueSupplier; + private final Supplier<@Nullable T> valueSupplier; // keep a reference to the column so we don't lose the type private final SqlColumn localColumn; - private ValueOrNullMapping(SqlColumn column, Supplier valueSupplier) { + private ValueOrNullMapping(SqlColumn column, Supplier<@Nullable T> valueSupplier) { super(column); this.valueSupplier = Objects.requireNonNull(valueSupplier); localColumn = Objects.requireNonNull(column); @@ -42,7 +43,7 @@ public R accept(ColumnMappingVisitor visitor) { return visitor.visit(this); } - public static ValueOrNullMapping of(SqlColumn column, Supplier valueSupplier) { + public static ValueOrNullMapping of(SqlColumn column, Supplier<@Nullable T> valueSupplier) { return new ValueOrNullMapping<>(column, valueSupplier); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/ValueWhenPresentMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/ValueWhenPresentMapping.java index 5bee7afd5..5420c644e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/ValueWhenPresentMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/ValueWhenPresentMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,26 +19,27 @@ import java.util.Optional; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.SqlColumn; public class ValueWhenPresentMapping extends AbstractColumnMapping { - private final Supplier valueSupplier; + private final Supplier<@Nullable T> valueSupplier; // keep a reference to the column so we don't lose the type private final SqlColumn localColumn; - private ValueWhenPresentMapping(SqlColumn column, Supplier valueSupplier) { + private ValueWhenPresentMapping(SqlColumn column, Supplier<@Nullable T> valueSupplier) { super(column); this.valueSupplier = Objects.requireNonNull(valueSupplier); localColumn = Objects.requireNonNull(column); } public Optional value() { - return Optional.ofNullable(valueSupplier.get()).map(this::convert); + return Optional.ofNullable(valueSupplier.get()).flatMap(this::convert); } - private Object convert(T value) { - return localColumn.convertParameterType(value); + private Optional convert(T value) { + return Optional.ofNullable(localColumn.convertParameterType(value)); } @Override @@ -46,7 +47,7 @@ public R accept(ColumnMappingVisitor visitor) { return visitor.visit(this); } - public static ValueWhenPresentMapping of(SqlColumn column, Supplier valueSupplier) { + public static ValueWhenPresentMapping of(SqlColumn column, Supplier<@Nullable T> valueSupplier) { return new ValueWhenPresentMapping<>(column, valueSupplier); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonCountMapper.java b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonCountMapper.java index f9319ac3e..d0b6c580d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonCountMapper.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonCountMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonDeleteMapper.java b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonDeleteMapper.java index b8a827fcf..bd7cb06dd 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonDeleteMapper.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonDeleteMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonGeneralInsertMapper.java b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonGeneralInsertMapper.java index 8ec6e889e..4dda5cc11 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonGeneralInsertMapper.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonGeneralInsertMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonInsertMapper.java b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonInsertMapper.java index 0a834f604..bd864b610 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonInsertMapper.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonInsertMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonSelectMapper.java b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonSelectMapper.java index d7d67edc0..8f0f350ee 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonSelectMapper.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonSelectMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -20,9 +20,9 @@ import java.util.Map; import java.util.Optional; import java.util.function.Function; -import java.util.stream.Collectors; import org.apache.ibatis.annotations.SelectProvider; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; import org.mybatis.dynamic.sql.util.SqlProviderAdapter; @@ -50,7 +50,7 @@ public interface CommonSelectMapper { /** * Select a single row as a Map of values. The row may have any number of columns. * The Map key will be the column name as returned from the - * database (may be aliased if an alias is specified in the select statement). Map entries will be + * database (the key will be aliased if an alias is specified in the select statement). Map entries will be * of data types determined by the JDBC driver. MyBatis will call ResultSet.getObject() to retrieve * values from the ResultSet. Reference your JDBC driver documentation to learn about type mappings * for your specific database. @@ -59,7 +59,7 @@ public interface CommonSelectMapper { * @return A Map containing the row values. */ @SelectProvider(type = SqlProviderAdapter.class, method = "select") - Map selectOneMappedRow(SelectStatementProvider selectStatement); + @Nullable Map selectOneMappedRow(SelectStatementProvider selectStatement); /** * Select a single row of values and then convert the values to a custom type. This is similar @@ -75,16 +75,17 @@ public interface CommonSelectMapper { * @param the datatype of the converted object * @return the converted object */ - default R selectOne(SelectStatementProvider selectStatement, + default @Nullable R selectOne(SelectStatementProvider selectStatement, Function, R> rowMapper) { - return rowMapper.apply(selectOneMappedRow(selectStatement)); + var result = selectOneMappedRow(selectStatement); + return result == null ? null : rowMapper.apply(result); } /** * Select any number of rows and return a List of Maps containing row values (one Map for each row returned). * The rows may have any number of columns. * The Map key will be the column name as returned from the - * database (may be aliased if an alias is specified in the select statement). Map entries will be + * database (the key will be aliased if an alias is specified in the select statement). Map entries will be * of data types determined by the JDBC driver. MyBatis will call ResultSet.getObject() to retrieve * values from the ResultSet. Reference your JDBC driver documentation to learn about type mappings * for your specific database. @@ -110,7 +111,7 @@ default List selectMany(SelectStatementProvider selectStatement, Function, R> rowMapper) { return selectManyMappedRows(selectStatement).stream() .map(rowMapper) - .collect(Collectors.toList()); + .toList(); } /** @@ -123,7 +124,7 @@ default List selectMany(SelectStatementProvider selectStatement, * column is null */ @SelectProvider(type = SqlProviderAdapter.class, method = "select") - BigDecimal selectOneBigDecimal(SelectStatementProvider selectStatement); + @Nullable BigDecimal selectOneBigDecimal(SelectStatementProvider selectStatement); /** * Retrieve a single {@link java.math.BigDecimal} from a result set. The result set must have @@ -158,7 +159,7 @@ default List selectMany(SelectStatementProvider selectStatement, * column is null */ @SelectProvider(type = SqlProviderAdapter.class, method = "select") - Double selectOneDouble(SelectStatementProvider selectStatement); + @Nullable Double selectOneDouble(SelectStatementProvider selectStatement); /** * Retrieve a single {@link java.lang.Double} from a result set. The result set must have @@ -193,7 +194,7 @@ default List selectMany(SelectStatementProvider selectStatement, * column is null */ @SelectProvider(type = SqlProviderAdapter.class, method = "select") - Integer selectOneInteger(SelectStatementProvider selectStatement); + @Nullable Integer selectOneInteger(SelectStatementProvider selectStatement); /** * Retrieve a single {@link java.lang.Integer} from a result set. The result set must have @@ -228,7 +229,7 @@ default List selectMany(SelectStatementProvider selectStatement, * column is null */ @SelectProvider(type = SqlProviderAdapter.class, method = "select") - Long selectOneLong(SelectStatementProvider selectStatement); + @Nullable Long selectOneLong(SelectStatementProvider selectStatement); /** * Retrieve a single {@link java.lang.Long} from a result set. The result set must have @@ -263,7 +264,7 @@ default List selectMany(SelectStatementProvider selectStatement, * column is null */ @SelectProvider(type = SqlProviderAdapter.class, method = "select") - String selectOneString(SelectStatementProvider selectStatement); + @Nullable String selectOneString(SelectStatementProvider selectStatement); /** * Retrieve a single {@link java.lang.String} from a result set. The result set must have diff --git a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonUpdateMapper.java b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonUpdateMapper.java index c7e4e40ce..0f25b575d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonUpdateMapper.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/CommonUpdateMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/MyBatis3Utils.java b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/MyBatis3Utils.java index 5b1906429..2e2bb279d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/MyBatis3Utils.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/MyBatis3Utils.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/package-info.java b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/package-info.java new file mode 100644 index 000000000..3eda4b115 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/util/mybatis3/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.util.mybatis3; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/util/package-info.java b/src/main/java/org/mybatis/dynamic/sql/util/package-info.java new file mode 100644 index 000000000..82bcfdd13 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/util/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.util; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/util/spring/BatchInsertUtility.java b/src/main/java/org/mybatis/dynamic/sql/util/spring/BatchInsertUtility.java index 485868ba6..598a6b4a0 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/spring/BatchInsertUtility.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/spring/BatchInsertUtility.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -16,7 +16,6 @@ package org.mybatis.dynamic.sql.util.spring; import java.util.List; -import java.util.stream.Collectors; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils; @@ -35,20 +34,10 @@ private BatchInsertUtility() {} public static SqlParameterSource[] createBatch(List rows) { List> tt = rows.stream() .map(RowHolder::new) - .collect(Collectors.toList()); + .toList(); return SqlParameterSourceUtils.createBatch(tt); } - public static class RowHolder { - private final T row; - - public RowHolder(T row) { - this.row = row; - } - - public T getRow() { - return row; - } - } + public record RowHolder(T row) {} } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/spring/NamedParameterJdbcTemplateExtensions.java b/src/main/java/org/mybatis/dynamic/sql/util/spring/NamedParameterJdbcTemplateExtensions.java index 56fac7d5c..4e630e1d2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/spring/NamedParameterJdbcTemplateExtensions.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/spring/NamedParameterJdbcTemplateExtensions.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/util/spring/package-info.java b/src/main/java/org/mybatis/dynamic/sql/util/spring/package-info.java new file mode 100644 index 000000000..1c53822b8 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/util/spring/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.util.spring; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchCursorReaderSelectModel.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchCursorReaderSelectModel.java deleted file mode 100644 index 5486b31de..000000000 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchCursorReaderSelectModel.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.util.springbatch; - -import org.mybatis.dynamic.sql.select.SelectModel; -import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; - -public class SpringBatchCursorReaderSelectModel { - - private final SelectModel selectModel; - - public SpringBatchCursorReaderSelectModel(SelectModel selectModel) { - this.selectModel = selectModel; - } - - public SelectStatementProvider render() { - return selectModel.render(SpringBatchUtility.SPRING_BATCH_READER_RENDERING_STRATEGY); - } -} diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java new file mode 100644 index 000000000..f56063d95 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingItemReaderRenderingStrategy.java @@ -0,0 +1,50 @@ +/* + * 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 org.mybatis.dynamic.sql.util.springbatch; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.mybatis.dynamic.sql.render.MyBatis3RenderingStrategy; + +/** + * This rendering strategy should be used for MyBatis3 statements using the + * MyBatisPagingItemReader supplied by mybatis-spring integration + * (http://www.mybatis.org/spring/). + */ +public class SpringBatchPagingItemReaderRenderingStrategy extends MyBatis3RenderingStrategy { + + @Override + public String getFormattedJdbcPlaceholderForPagingParameters(String prefix, String parameterName) { + return "#{" //$NON-NLS-1$ + + parameterName + + "}"; //$NON-NLS-1$ + } + + @Override + public String formatParameterMapKeyForFetchFirstRows(AtomicInteger sequence) { + return "_pagesize"; //$NON-NLS-1$ + } + + @Override + public String formatParameterMapKeyForLimit(AtomicInteger sequence) { + return "_pagesize"; //$NON-NLS-1$ + } + + @Override + public String formatParameterMapKeyForOffset(AtomicInteger sequence) { + return "_skiprows"; //$NON-NLS-1$ + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingReaderSelectModel.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingReaderSelectModel.java deleted file mode 100644 index 648d71218..000000000 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchPagingReaderSelectModel.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.util.springbatch; - -import java.util.HashMap; -import java.util.Map; - -import org.mybatis.dynamic.sql.select.SelectModel; -import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; - -public class SpringBatchPagingReaderSelectModel { - - private final SelectModel selectModel; - - public SpringBatchPagingReaderSelectModel(SelectModel selectModel) { - this.selectModel = selectModel; - } - - public SelectStatementProvider render() { - SelectStatementProvider selectStatement = - selectModel.render(SpringBatchUtility.SPRING_BATCH_READER_RENDERING_STRATEGY); - return new LimitAndOffsetDecorator(selectStatement); - } - - public static class LimitAndOffsetDecorator implements SelectStatementProvider { - private final Map parameters = new HashMap<>(); - private final String selectStatement; - - public LimitAndOffsetDecorator(SelectStatementProvider delegate) { - parameters.putAll(delegate.getParameters()); - - selectStatement = delegate.getSelectStatement() - + " LIMIT #{_pagesize} OFFSET #{_skiprows}"; //$NON-NLS-1$ - } - - @Override - public Map getParameters() { - return parameters; - } - - @Override - public String getSelectStatement() { - return selectStatement; - } - } -} diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchProviderAdapter.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchProviderAdapter.java index f212a0d04..2000c02bf 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchProviderAdapter.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchProviderAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,13 +17,9 @@ import java.util.Map; -import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; - public class SpringBatchProviderAdapter { public String select(Map parameterValues) { - SelectStatementProvider selectStatement = - (SelectStatementProvider) parameterValues.get(SpringBatchUtility.PARAMETER_KEY); - return selectStatement.getSelectStatement(); + return (String) parameterValues.get(SpringBatchUtility.PARAMETER_KEY); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchReaderRenderingStrategy.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchReaderRenderingStrategy.java deleted file mode 100644 index 48b1b9b8c..000000000 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchReaderRenderingStrategy.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.util.springbatch; - -import org.mybatis.dynamic.sql.BindableColumn; -import org.mybatis.dynamic.sql.render.MyBatis3RenderingStrategy; - -/** - * This rendering strategy should be used for MyBatis3 statements using one of the - * Spring batch readers supplied by mybatis-spring integration (http://www.mybatis.org/spring/). - * Those readers are MyBatisPagingItemReader and MyBatisCursorItemReader. - * - */ -public class SpringBatchReaderRenderingStrategy extends MyBatis3RenderingStrategy { - - @Override - public String getFormattedJdbcPlaceholder(BindableColumn column, String prefix, String parameterName) { - String newPrefix = SpringBatchUtility.PARAMETER_KEY + "." + prefix; //$NON-NLS-1$ - return super.getFormattedJdbcPlaceholder(column, newPrefix, parameterName); - } -} diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java index 63b64429a..9b84ee499 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,48 +18,45 @@ import java.util.HashMap; import java.util.Map; -import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.render.RenderingStrategy; -import org.mybatis.dynamic.sql.select.QueryExpressionDSL; -import org.mybatis.dynamic.sql.select.SelectDSL; import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; public class SpringBatchUtility { private SpringBatchUtility() {} - public static final String PARAMETER_KEY = "mybatis3_dsql_query"; //$NON-NLS-1$ - - public static final RenderingStrategy SPRING_BATCH_READER_RENDERING_STRATEGY = - new SpringBatchReaderRenderingStrategy(); - - public static Map toParameterValues(SelectStatementProvider selectStatement) { - Map parameterValues = new HashMap<>(); - parameterValues.put(PARAMETER_KEY, selectStatement); - return parameterValues; - } + static final String PARAMETER_KEY = "mybatis3_dsql_query"; //$NON-NLS-1$ /** - * Select builder that renders in a manner appropriate for the MyBatisPagingItemReader. + * Constant for use in a query intended for use with the MyBatisPagingItemReader. + * This value will not be used in the query at runtime because MyBatis Spring integration + * will supply a value for _skiprows. * - *

Important rendered SQL will contain LIMIT and OFFSET clauses in the SELECT statement. If your database - * (Oracle) does not support LIMIT and OFFSET, the queries will fail. + *

This value can be used as a parameter for the "offset" method in a query to make the intention + * clear that the actual runtime value will be supplied by MyBatis Spring integration. * - * @param selectList a column list for the SELECT statement - * @return FromGatherer used to continue a SELECT statement + *

See https://mybatis.org/spring/batch.html for details. */ - public static QueryExpressionDSL.FromGatherer selectForPaging( - BasicColumn... selectList) { - return SelectDSL.select(SpringBatchPagingReaderSelectModel::new, selectList); - } + public static final long MYBATIS_SPRING_BATCH_SKIPROWS = -437L; /** - * Select builder that renders in a manner appropriate for the MyBatisCursorItemReader. + * Constant for use in a query intended for use with the MyBatisPagingItemReader. + * This value will not be used in the query at runtime because MyBatis Spring integration + * will supply a value for _pagesize. * - * @param selectList a column list for the SELECT statement - * @return FromGatherer used to continue a SELECT statement + *

This value can be used as a parameter for the "limit" or "fetchFirst" method in a query to make the intention + * clear that the actual runtime value will be supplied by MyBatis Spring integration. + * + *

See https://mybatis.org/spring/batch.html for details. */ - public static QueryExpressionDSL.FromGatherer selectForCursor( - BasicColumn... selectList) { - return SelectDSL.select(SpringBatchCursorReaderSelectModel::new, selectList); + public static final long MYBATIS_SPRING_BATCH_PAGESIZE = -439L; + + public static final RenderingStrategy SPRING_BATCH_PAGING_ITEM_READER_RENDERING_STRATEGY = + new SpringBatchPagingItemReaderRenderingStrategy(); + + public static Map toParameterValues(SelectStatementProvider selectStatement) { + var parameterValues = new HashMap(); + parameterValues.put(PARAMETER_KEY, selectStatement.getSelectStatement()); + parameterValues.put(RenderingStrategy.DEFAULT_PARAMETER_PREFIX, selectStatement.getParameters()); + return parameterValues; } } diff --git a/src/main/java/org/mybatis/dynamic/sql/util/springbatch/package-info.java b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/package-info.java new file mode 100644 index 000000000..16a66a9fe --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/util/springbatch/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.util.springbatch; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereFinisher.java b/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereFinisher.java index edad1cccc..260fef7ea 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereFinisher.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereFinisher.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,6 +19,8 @@ import java.util.Objects; import java.util.function.Consumer; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AndOrCriteriaGroup; import org.mybatis.dynamic.sql.SqlCriterion; import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionDSL; @@ -37,11 +39,12 @@ void initialize(SqlCriterion sqlCriterion) { setInitialCriterion(sqlCriterion, StatementType.WHERE); } - void initialize(SqlCriterion sqlCriterion, List subCriteria) { + void initialize(@Nullable SqlCriterion sqlCriterion, List subCriteria) { setInitialCriterion(sqlCriterion, StatementType.WHERE); super.subCriteria.addAll(subCriteria); } + @NonNull @Override public T configureStatement(Consumer consumer) { parentStatement.configureStatement(consumer); diff --git a/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereStarter.java b/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereStarter.java index 03da51bd8..17efa091e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereStarter.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/AbstractWhereStarter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,14 +18,15 @@ import java.util.Arrays; import java.util.List; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AndOrCriteriaGroup; import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.ColumnAndConditionCriterion; import org.mybatis.dynamic.sql.CriteriaGroup; import org.mybatis.dynamic.sql.ExistsCriterion; import org.mybatis.dynamic.sql.ExistsPredicate; +import org.mybatis.dynamic.sql.RenderableCondition; import org.mybatis.dynamic.sql.SqlCriterion; -import org.mybatis.dynamic.sql.VisitableCondition; import org.mybatis.dynamic.sql.util.ConfigurableStatement; /** @@ -35,14 +36,14 @@ * * @param the implementation of the Where DSL customized for a particular SQL statement. */ -public abstract class AbstractWhereStarter, D extends AbstractWhereStarter> - implements ConfigurableStatement { +public interface AbstractWhereStarter, D extends AbstractWhereStarter> + extends ConfigurableStatement { - public F where(BindableColumn column, VisitableCondition condition, AndOrCriteriaGroup... subCriteria) { + default F where(BindableColumn column, RenderableCondition condition, AndOrCriteriaGroup... subCriteria) { return where(column, condition, Arrays.asList(subCriteria)); } - public F where(BindableColumn column, VisitableCondition condition, + default F where(BindableColumn column, RenderableCondition condition, List subCriteria) { SqlCriterion sqlCriterion = ColumnAndConditionCriterion.withColumn(column) .withCondition(condition) @@ -52,11 +53,11 @@ public F where(BindableColumn column, VisitableCondition condition, return initialize(sqlCriterion); } - public F where(ExistsPredicate existsPredicate, AndOrCriteriaGroup... subCriteria) { + default F where(ExistsPredicate existsPredicate, AndOrCriteriaGroup... subCriteria) { return where(existsPredicate, Arrays.asList(subCriteria)); } - public F where(ExistsPredicate existsPredicate, List subCriteria) { + default F where(ExistsPredicate existsPredicate, List subCriteria) { ExistsCriterion sqlCriterion = new ExistsCriterion.Builder() .withExistsPredicate(existsPredicate) .withSubCriteria(subCriteria) @@ -65,11 +66,11 @@ public F where(ExistsPredicate existsPredicate, List subCrit return initialize(sqlCriterion); } - public F where(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) { + default F where(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) { return where(initialCriterion, Arrays.asList(subCriteria)); } - public F where(SqlCriterion initialCriterion, List subCriteria) { + default F where(@Nullable SqlCriterion initialCriterion, List subCriteria) { SqlCriterion sqlCriterion = new CriteriaGroup.Builder() .withInitialCriterion(initialCriterion) .withSubCriteria(subCriteria) @@ -78,7 +79,7 @@ public F where(SqlCriterion initialCriterion, List subCriter return initialize(sqlCriterion); } - public F where(List subCriteria) { + default F where(List subCriteria) { SqlCriterion sqlCriterion = new CriteriaGroup.Builder() .withSubCriteria(subCriteria) .build(); @@ -86,9 +87,9 @@ public F where(List subCriteria) { return initialize(sqlCriterion); } - public abstract F where(); + F where(); - public F applyWhere(WhereApplier whereApplier) { + default F applyWhere(WhereApplier whereApplier) { F finisher = where(); whereApplier.accept(finisher); return finisher; diff --git a/src/main/java/org/mybatis/dynamic/sql/where/EmbeddedWhereModel.java b/src/main/java/org/mybatis/dynamic/sql/where/EmbeddedWhereModel.java index acede2590..7cd42e73e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/EmbeddedWhereModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/EmbeddedWhereModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/where/WhereApplier.java b/src/main/java/org/mybatis/dynamic/sql/where/WhereApplier.java index ce2289695..d708aa065 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/WhereApplier.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/WhereApplier.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/where/WhereDSL.java b/src/main/java/org/mybatis/dynamic/sql/where/WhereDSL.java index 0eb23f638..6a20b57e8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/WhereDSL.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/WhereDSL.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,7 +17,6 @@ import java.util.function.Consumer; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.util.Buildable; @@ -26,7 +25,7 @@ * *

This can also be used to create reusable where clauses for different statements. */ -public class WhereDSL extends AbstractWhereStarter { +public class WhereDSL implements AbstractWhereStarter { private final StatementConfiguration statementConfiguration = new StatementConfiguration(); private final StandaloneWhereFinisher whereBuilder = new StandaloneWhereFinisher(); @@ -52,7 +51,6 @@ protected StandaloneWhereFinisher getThis() { return this; } - @NotNull @Override public WhereModel build() { return new WhereModel.Builder() diff --git a/src/main/java/org/mybatis/dynamic/sql/where/WhereModel.java b/src/main/java/org/mybatis/dynamic/sql/where/WhereModel.java index 8db761223..27fed4eed 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/WhereModel.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/WhereModel.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,12 +18,14 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionModel; import org.mybatis.dynamic.sql.configuration.StatementConfiguration; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.render.RenderingStrategy; import org.mybatis.dynamic.sql.render.TableAliasCalculator; import org.mybatis.dynamic.sql.util.FragmentAndParameters; +import org.mybatis.dynamic.sql.where.render.DefaultWhereClauseProvider; import org.mybatis.dynamic.sql.where.render.WhereClauseProvider; import org.mybatis.dynamic.sql.where.render.WhereRenderer; @@ -92,13 +94,13 @@ private Optional render(RenderingContext renderingContext) } private WhereClauseProvider toWhereClauseProvider(FragmentAndParameters fragmentAndParameters) { - return WhereClauseProvider.withWhereClause(fragmentAndParameters.fragment()) + return DefaultWhereClauseProvider.withWhereClause(fragmentAndParameters.fragment()) .withParameters(fragmentAndParameters.parameters()) .build(); } public static class Builder extends AbstractBuilder { - private StatementConfiguration statementConfiguration; + private @Nullable StatementConfiguration statementConfiguration; public Builder withStatementConfiguration(StatementConfiguration statementConfiguration) { this.statementConfiguration = statementConfiguration; diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/AndGatherer.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/AndGatherer.java index ddd272f40..c9514f3fa 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/AndGatherer.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/AndGatherer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,6 +17,8 @@ import java.util.function.Supplier; +import org.jspecify.annotations.NonNull; + /** * Utility class supporting the "and" part of a between condition. This class supports builders, so it is mutable. * @@ -29,20 +31,18 @@ */ public abstract class AndGatherer { protected final T value1; - protected T value2; protected AndGatherer(T value1) { this.value1 = value1; } public R and(T value2) { - this.value2 = value2; - return build(); + return build(value2); } - public R and(Supplier valueSupplier2) { + public R and(Supplier<@NonNull T> valueSupplier2) { return and(valueSupplier2.get()); } - protected abstract R build(); + protected abstract R build(T value2); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/AndWhenPresentGatherer.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/AndWhenPresentGatherer.java new file mode 100644 index 000000000..d4d8f3d7c --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/AndWhenPresentGatherer.java @@ -0,0 +1,49 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.function.Supplier; + +import org.jspecify.annotations.Nullable; + +/** + * Utility class supporting the "and" part of a between when present condition. This class supports builders, + * so it is mutable. + * + * @author Jeff Butler + * + * @param + * the type of field for the between condition + * @param + * the type of condition being built + */ +public abstract class AndWhenPresentGatherer { + protected final @Nullable T value1; + + protected AndWhenPresentGatherer(@Nullable T value1) { + this.value1 = value1; + } + + public R and(@Nullable T value2) { + return build(value2); + } + + public R and(Supplier<@Nullable T> valueSupplier2) { + return and(valueSupplier2.get()); + } + + protected abstract R build(@Nullable T value2); +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveRenderableCondition.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveRenderableCondition.java new file mode 100644 index 000000000..977a0090b --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveRenderableCondition.java @@ -0,0 +1,31 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import org.mybatis.dynamic.sql.BindableColumn; +import org.mybatis.dynamic.sql.RenderableCondition; +import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.util.FragmentAndParameters; + +public interface CaseInsensitiveRenderableCondition extends RenderableCondition { + + @Override + default FragmentAndParameters renderLeftColumn(RenderingContext renderingContext, + BindableColumn leftColumn) { + return RenderableCondition.super.renderLeftColumn(renderingContext, leftColumn) + .mapFragment(s -> "upper(" + s + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java index ff21433d0..0f7fcd66a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,15 +15,27 @@ */ package org.mybatis.dynamic.sql.where.condition; -import java.util.Objects; +import java.util.NoSuchElementException; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractTwoValueCondition; -public class IsBetween extends AbstractTwoValueCondition { - private static final IsBetween EMPTY = new IsBetween(null, null) { +public class IsBetween extends AbstractTwoValueCondition<@NonNull T> + implements AbstractTwoValueCondition.Filterable, AbstractTwoValueCondition.Mappable { + private static final IsBetween EMPTY = new IsBetween(-1, -1) { + @Override + public Object value1() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public Object value2() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + @Override public boolean isEmpty() { return true; @@ -51,39 +63,23 @@ public String operator2() { } @Override - public IsBetween filter(BiPredicate predicate) { + public IsBetween filter(BiPredicate predicate) { return filterSupport(predicate, IsBetween::empty, this); } @Override - public IsBetween filter(Predicate predicate) { + public IsBetween filter(Predicate predicate) { return filterSupport(predicate, IsBetween::empty, this); } - /** - * If renderable, apply the mappings to the values and return a new condition with the new values. Else return a - * condition that will not render (this). - * - * @param mapper1 a mapping function to apply to the first value, if renderable - * @param mapper2 a mapping function to apply to the second value, if renderable - * @param type of the new condition - * @return a new condition with the result of applying the mappers to the values of this condition, - * if renderable, otherwise a condition that will not render. - */ - public IsBetween map(Function mapper1, Function mapper2) { + @Override + public IsBetween map(Function mapper1, + Function mapper2) { return mapSupport(mapper1, mapper2, IsBetween::new, IsBetween::empty); } - /** - * If renderable, apply the mapping to both values and return a new condition with the new values. Else return a - * condition that will not render (this). - * - * @param mapper a mapping function to apply to both values, if renderable - * @param type of the new condition - * @return a new condition with the result of applying the mappers to the values of this condition, - * if renderable, otherwise a condition that will not render. - */ - public IsBetween map(Function mapper) { + @Override + public IsBetween map(Function mapper) { return map(mapper, mapper); } @@ -91,29 +87,14 @@ public static Builder isBetween(T value1) { return new Builder<>(value1); } - public static WhenPresentBuilder isBetweenWhenPresent(T value1) { - return new WhenPresentBuilder<>(value1); - } - public static class Builder extends AndGatherer> { private Builder(T value1) { super(value1); } @Override - protected IsBetween build() { + protected IsBetween build(T value2) { return new IsBetween<>(value1, value2); } } - - public static class WhenPresentBuilder extends AndGatherer> { - private WhenPresentBuilder(T value1) { - super(value1); - } - - @Override - protected IsBetween build() { - return new IsBetween<>(value1, value2).filter(Objects::nonNull); - } - } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetweenWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetweenWhenPresent.java new file mode 100644 index 000000000..bc9c12d37 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetweenWhenPresent.java @@ -0,0 +1,109 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.NoSuchElementException; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AbstractTwoValueCondition; + +public class IsBetweenWhenPresent extends AbstractTwoValueCondition + implements AbstractTwoValueCondition.Filterable, AbstractTwoValueCondition.Mappable { + private static final IsBetweenWhenPresent EMPTY = new IsBetweenWhenPresent(-1, -1) { + @Override + public Object value1() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public Object value2() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + public static IsBetweenWhenPresent empty() { + @SuppressWarnings("unchecked") + IsBetweenWhenPresent t = (IsBetweenWhenPresent) EMPTY; + return t; + } + + protected IsBetweenWhenPresent(T value1, T value2) { + super(value1, value2); + } + + @Override + public String operator1() { + return "between"; //$NON-NLS-1$ + } + + @Override + public String operator2() { + return "and"; //$NON-NLS-1$ + } + + @Override + public IsBetweenWhenPresent filter(BiPredicate predicate) { + return filterSupport(predicate, IsBetweenWhenPresent::empty, this); + } + + @Override + public IsBetweenWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsBetweenWhenPresent::empty, this); + } + + @Override + public IsBetweenWhenPresent map(Function mapper1, + Function mapper2) { + return mapSupport(mapper1, mapper2, IsBetweenWhenPresent::of, IsBetweenWhenPresent::empty); + } + + @Override + public IsBetweenWhenPresent map(Function mapper) { + return map(mapper, mapper); + } + + public static IsBetweenWhenPresent of(@Nullable T value1, @Nullable T value2) { + if (value1 == null || value2 == null) { + return empty(); + } else { + return new IsBetweenWhenPresent<>(value1, value2); + } + } + + public static Builder isBetweenWhenPresent(@Nullable T value1) { + return new Builder<>(value1); + } + + public static class Builder extends AndWhenPresentGatherer> { + private Builder(@Nullable T value1) { + super(value1); + } + + @Override + protected IsBetweenWhenPresent build(@Nullable T value2) { + return of(value1, value2); + } + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualTo.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualTo.java index 2682dd602..db8548f61 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualTo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,14 +15,22 @@ */ package org.mybatis.dynamic.sql.where.condition; +import java.util.NoSuchElementException; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsEqualTo extends AbstractSingleValueCondition { +public class IsEqualTo extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + + private static final IsEqualTo EMPTY = new IsEqualTo(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } - private static final IsEqualTo EMPTY = new IsEqualTo(null) { @Override public boolean isEmpty() { return true; @@ -49,20 +57,12 @@ public static IsEqualTo of(T value) { } @Override - public IsEqualTo filter(Predicate predicate) { + public IsEqualTo filter(Predicate predicate) { return filterSupport(predicate, IsEqualTo::empty, this); } - /** - * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a - * condition that will not render (this). - * - * @param mapper a mapping function to apply to the value, if renderable - * @param type of the new condition - * @return a new condition with the result of applying the mapper to the value of this condition, - * if renderable, otherwise a condition that will not render. - */ - public IsEqualTo map(Function mapper) { + @Override + public IsEqualTo map(Function mapper) { return mapSupport(mapper, IsEqualTo::new, IsEqualTo::empty); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualToColumn.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualToColumn.java index 80fa86094..6f12ea99f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualToColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualToColumn.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualToWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualToWhenPresent.java new file mode 100644 index 000000000..2dd8c746d --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualToWhenPresent.java @@ -0,0 +1,73 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AbstractSingleValueCondition; + +public class IsEqualToWhenPresent extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + + private static final IsEqualToWhenPresent EMPTY = new IsEqualToWhenPresent(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + public static IsEqualToWhenPresent empty() { + @SuppressWarnings("unchecked") + IsEqualToWhenPresent t = (IsEqualToWhenPresent) EMPTY; + return t; + } + + protected IsEqualToWhenPresent(T value) { + super(value); + } + + @Override + public String operator() { + return "="; //$NON-NLS-1$ + } + + public static IsEqualToWhenPresent of(@Nullable T value) { + if (value == null) { + return empty(); + } else { + return new IsEqualToWhenPresent<>(value); + } + } + + @Override + public IsEqualToWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsEqualToWhenPresent::empty, this); + } + + @Override + public IsEqualToWhenPresent map(Function mapper) { + return mapSupport(mapper, IsEqualToWhenPresent::of, IsEqualToWhenPresent::empty); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualToWithSubselect.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualToWithSubselect.java index 6b4def2ce..10bc2c285 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualToWithSubselect.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsEqualToWithSubselect.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,7 +15,6 @@ */ package org.mybatis.dynamic.sql.where.condition; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.AbstractSubselectCondition; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.util.Buildable; @@ -26,7 +25,6 @@ protected IsEqualToWithSubselect(Buildable selectModelBuilder) { super(selectModelBuilder); } - @NotNull public static IsEqualToWithSubselect of(Buildable selectModelBuilder) { return new IsEqualToWithSubselect<>(selectModelBuilder); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThan.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThan.java index 3e45574ad..577a400f2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThan.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThan.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,13 +15,21 @@ */ package org.mybatis.dynamic.sql.where.condition; +import java.util.NoSuchElementException; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsGreaterThan extends AbstractSingleValueCondition { - private static final IsGreaterThan EMPTY = new IsGreaterThan(null) { +public class IsGreaterThan extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + private static final IsGreaterThan EMPTY = new IsGreaterThan(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + @Override public boolean isEmpty() { return true; @@ -48,20 +56,12 @@ public static IsGreaterThan of(T value) { } @Override - public IsGreaterThan filter(Predicate predicate) { + public IsGreaterThan filter(Predicate predicate) { return filterSupport(predicate, IsGreaterThan::empty, this); } - /** - * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a - * condition that will not render (this). - * - * @param mapper a mapping function to apply to the value, if renderable - * @param type of the new condition - * @return a new condition with the result of applying the mapper to the value of this condition, - * if renderable, otherwise a condition that will not render. - */ - public IsGreaterThan map(Function mapper) { + @Override + public IsGreaterThan map(Function mapper) { return mapSupport(mapper, IsGreaterThan::new, IsGreaterThan::empty); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanColumn.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanColumn.java index dc0bb513e..446ab6377 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanColumn.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualTo.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualTo.java index 8212a3b5b..5fb4bd0d4 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualTo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,13 +15,21 @@ */ package org.mybatis.dynamic.sql.where.condition; +import java.util.NoSuchElementException; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsGreaterThanOrEqualTo extends AbstractSingleValueCondition { - private static final IsGreaterThanOrEqualTo EMPTY = new IsGreaterThanOrEqualTo(null) { +public class IsGreaterThanOrEqualTo extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + private static final IsGreaterThanOrEqualTo EMPTY = new IsGreaterThanOrEqualTo(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + @Override public boolean isEmpty() { return true; @@ -48,20 +56,12 @@ public static IsGreaterThanOrEqualTo of(T value) { } @Override - public IsGreaterThanOrEqualTo filter(Predicate predicate) { + public IsGreaterThanOrEqualTo filter(Predicate predicate) { return filterSupport(predicate, IsGreaterThanOrEqualTo::empty, this); } - /** - * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a - * condition that will not render (this). - * - * @param mapper a mapping function to apply to the value, if renderable - * @param type of the new condition - * @return a new condition with the result of applying the mapper to the value of this condition, - * if renderable, otherwise a condition that will not render. - */ - public IsGreaterThanOrEqualTo map(Function mapper) { + @Override + public IsGreaterThanOrEqualTo map(Function mapper) { return mapSupport(mapper, IsGreaterThanOrEqualTo::new, IsGreaterThanOrEqualTo::empty); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToColumn.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToColumn.java index 413d0b206..3b8d4a05e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToColumn.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. 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 new file mode 100644 index 000000000..01f895dc9 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToWhenPresent.java @@ -0,0 +1,73 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AbstractSingleValueCondition; + +public class IsGreaterThanOrEqualToWhenPresent extends AbstractSingleValueCondition + 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 boolean isEmpty() { + return true; + } + }; + + public static IsGreaterThanOrEqualToWhenPresent empty() { + @SuppressWarnings("unchecked") + IsGreaterThanOrEqualToWhenPresent t = (IsGreaterThanOrEqualToWhenPresent) EMPTY; + return t; + } + + protected IsGreaterThanOrEqualToWhenPresent(T value) { + super(value); + } + + @Override + public String operator() { + return ">="; //$NON-NLS-1$ + } + + public static IsGreaterThanOrEqualToWhenPresent of(@Nullable T value) { + if (value == null) { + return empty(); + } else { + return new IsGreaterThanOrEqualToWhenPresent<>(value); + } + } + + @Override + public IsGreaterThanOrEqualToWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsGreaterThanOrEqualToWhenPresent::empty, this); + } + + @Override + public IsGreaterThanOrEqualToWhenPresent map(Function mapper) { + return mapSupport(mapper, IsGreaterThanOrEqualToWhenPresent::of, IsGreaterThanOrEqualToWhenPresent::empty); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToWithSubselect.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToWithSubselect.java index 9a7e9385f..05aea23f2 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToWithSubselect.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanOrEqualToWithSubselect.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,7 +15,6 @@ */ package org.mybatis.dynamic.sql.where.condition; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.AbstractSubselectCondition; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.util.Buildable; @@ -26,7 +25,6 @@ protected IsGreaterThanOrEqualToWithSubselect(Buildable selectModel super(selectModelBuilder); } - @NotNull public static IsGreaterThanOrEqualToWithSubselect of(Buildable selectModelBuilder) { return new IsGreaterThanOrEqualToWithSubselect<>(selectModelBuilder); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanWhenPresent.java new file mode 100644 index 000000000..779a15596 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanWhenPresent.java @@ -0,0 +1,72 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AbstractSingleValueCondition; + +public class IsGreaterThanWhenPresent extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + private static final IsGreaterThanWhenPresent EMPTY = new IsGreaterThanWhenPresent(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + public static IsGreaterThanWhenPresent empty() { + @SuppressWarnings("unchecked") + IsGreaterThanWhenPresent t = (IsGreaterThanWhenPresent) EMPTY; + return t; + } + + protected IsGreaterThanWhenPresent(T value) { + super(value); + } + + @Override + public String operator() { + return ">"; //$NON-NLS-1$ + } + + public static IsGreaterThanWhenPresent of(@Nullable T value) { + if (value == null) { + return empty(); + } else { + return new IsGreaterThanWhenPresent<>(value); + } + } + + @Override + public IsGreaterThanWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsGreaterThanWhenPresent::empty, this); + } + + @Override + public IsGreaterThanWhenPresent map(Function mapper) { + return mapSupport(mapper, IsGreaterThanWhenPresent::of, IsGreaterThanWhenPresent::empty); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanWithSubselect.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanWithSubselect.java index 9f5ff92dc..225423cbd 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanWithSubselect.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsGreaterThanWithSubselect.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,7 +15,6 @@ */ package org.mybatis.dynamic.sql.where.condition; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.AbstractSubselectCondition; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.util.Buildable; @@ -26,7 +25,6 @@ protected IsGreaterThanWithSubselect(Buildable selectModelBuilder) super(selectModelBuilder); } - @NotNull public static IsGreaterThanWithSubselect of(Buildable selectModelBuilder) { return new IsGreaterThanWithSubselect<>(selectModelBuilder); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsIn.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsIn.java index ce00047b5..67072dc8a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsIn.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsIn.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,10 +21,13 @@ import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractListValueCondition; import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.util.Validator; -public class IsIn extends AbstractListValueCondition { +public class IsIn extends AbstractListValueCondition + implements AbstractListValueCondition.Filterable, AbstractListValueCondition.Mappable { private static final IsIn EMPTY = new IsIn<>(Collections.emptyList()); public static IsIn empty() { @@ -39,6 +42,7 @@ protected IsIn(Collection values) { @Override public boolean shouldRender(RenderingContext renderingContext) { + Validator.assertNotEmpty(values, "ERROR.44", "IsIn"); //$NON-NLS-1$ //$NON-NLS-2$ return true; } @@ -48,21 +52,13 @@ public String operator() { } @Override - public IsIn filter(Predicate predicate) { + public IsIn filter(Predicate predicate) { return filterSupport(predicate, IsIn::new, this, IsIn::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @param type of the new condition - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public IsIn map(Function mapper) { - Function, IsIn> constructor = IsIn::new; - return mapSupport(mapper, constructor, IsIn::empty); + @Override + public IsIn map(Function mapper) { + return mapSupport(mapper, IsIn::new, IsIn::empty); } @SafeVarargs diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java index 3fe30c1ef..d26a9c30f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitive.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,27 +18,33 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractListValueCondition; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.StringUtilities; +import org.mybatis.dynamic.sql.util.Validator; -public class IsInCaseInsensitive extends AbstractListValueCondition - implements CaseInsensitiveVisitableCondition { - private static final IsInCaseInsensitive EMPTY = new IsInCaseInsensitive(Collections.emptyList()); +public class IsInCaseInsensitive extends AbstractListValueCondition + implements CaseInsensitiveRenderableCondition, AbstractListValueCondition.Filterable, + AbstractListValueCondition.Mappable { + private static final IsInCaseInsensitive EMPTY = new IsInCaseInsensitive<>(Collections.emptyList()); - public static IsInCaseInsensitive empty() { - return EMPTY; + public static IsInCaseInsensitive empty() { + @SuppressWarnings("unchecked") + IsInCaseInsensitive t = (IsInCaseInsensitive) EMPTY; + return t; } - protected IsInCaseInsensitive(Collection values) { - super(values); + protected IsInCaseInsensitive(Collection values) { + super(values.stream().map(StringUtilities::upperCaseIfPossible).toList()); } @Override public boolean shouldRender(RenderingContext renderingContext) { + Validator.assertNotEmpty(values, "ERROR.44", "IsInCaseInsensitive"); //$NON-NLS-1$ //$NON-NLS-2$ return true; } @@ -48,26 +54,21 @@ public String operator() { } @Override - public IsInCaseInsensitive filter(Predicate predicate) { + public IsInCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsInCaseInsensitive::new, this, IsInCaseInsensitive::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public IsInCaseInsensitive map(UnaryOperator mapper) { + @Override + public IsInCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsInCaseInsensitive::new, IsInCaseInsensitive::empty); } - public static IsInCaseInsensitive of(String... values) { + @SafeVarargs + public static IsInCaseInsensitive of(T... values) { return of(Arrays.asList(values)); } - public static IsInCaseInsensitive of(Collection values) { - return new IsInCaseInsensitive(values).map(StringUtilities::safelyUpperCase); + public static IsInCaseInsensitive of(Collection values) { + return new IsInCaseInsensitive<>(values); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitiveWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitiveWhenPresent.java index b123d30c5..6b4c1e1cd 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitiveWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInCaseInsensitiveWhenPresent.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,23 +19,28 @@ import java.util.Collection; import java.util.Collections; import java.util.Objects; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; -import java.util.stream.Collectors; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AbstractListValueCondition; import org.mybatis.dynamic.sql.util.StringUtilities; -public class IsInCaseInsensitiveWhenPresent extends AbstractListValueCondition - implements CaseInsensitiveVisitableCondition { - private static final IsInCaseInsensitiveWhenPresent EMPTY = new IsInCaseInsensitiveWhenPresent(Collections.emptyList()); +public class IsInCaseInsensitiveWhenPresent extends AbstractListValueCondition + implements CaseInsensitiveRenderableCondition, AbstractListValueCondition.Filterable, + AbstractListValueCondition.Mappable { + private static final IsInCaseInsensitiveWhenPresent EMPTY = + new IsInCaseInsensitiveWhenPresent<>(Collections.emptyList()); - public static IsInCaseInsensitiveWhenPresent empty() { - return EMPTY; + public static IsInCaseInsensitiveWhenPresent empty() { + @SuppressWarnings("unchecked") + IsInCaseInsensitiveWhenPresent t = (IsInCaseInsensitiveWhenPresent) EMPTY; + return t; } - protected IsInCaseInsensitiveWhenPresent(Collection values) { - super(values.stream().filter(Objects::nonNull).collect(Collectors.toList())); + protected IsInCaseInsensitiveWhenPresent(Collection values) { + super(values.stream().filter(Objects::nonNull).map(StringUtilities::upperCaseIfPossible).toList()); } @Override @@ -44,26 +49,26 @@ public String operator() { } @Override - public IsInCaseInsensitiveWhenPresent filter(Predicate predicate) { - return filterSupport(predicate, IsInCaseInsensitiveWhenPresent::new, this, IsInCaseInsensitiveWhenPresent::empty); + public IsInCaseInsensitiveWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsInCaseInsensitiveWhenPresent::new, this, + IsInCaseInsensitiveWhenPresent::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public IsInCaseInsensitiveWhenPresent map(UnaryOperator mapper) { + @Override + public IsInCaseInsensitiveWhenPresent map(Function mapper) { return mapSupport(mapper, IsInCaseInsensitiveWhenPresent::new, IsInCaseInsensitiveWhenPresent::empty); } - public static IsInCaseInsensitiveWhenPresent of(String... values) { + @SafeVarargs + public static IsInCaseInsensitiveWhenPresent of(@Nullable T... values) { return of(Arrays.asList(values)); } - public static IsInCaseInsensitiveWhenPresent of(Collection values) { - return new IsInCaseInsensitiveWhenPresent(values).map(StringUtilities::safelyUpperCase); + public static IsInCaseInsensitiveWhenPresent of(@Nullable Collection<@Nullable T> values) { + if (values == null) { + return empty(); + } else { + return new IsInCaseInsensitiveWhenPresent<>(values); + } } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInWhenPresent.java index fc2994ea7..abacd2690 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInWhenPresent.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,11 +21,13 @@ import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; -import java.util.stream.Collectors; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AbstractListValueCondition; -public class IsInWhenPresent extends AbstractListValueCondition { +public class IsInWhenPresent extends AbstractListValueCondition + implements AbstractListValueCondition.Filterable, AbstractListValueCondition.Mappable { private static final IsInWhenPresent EMPTY = new IsInWhenPresent<>(Collections.emptyList()); public static IsInWhenPresent empty() { @@ -35,7 +37,7 @@ public static IsInWhenPresent empty() { } protected IsInWhenPresent(Collection values) { - super(values.stream().filter(Objects::nonNull).collect(Collectors.toList())); + super(values.stream().filter(Objects::nonNull).toList()); } @Override @@ -44,29 +46,25 @@ public String operator() { } @Override - public IsInWhenPresent filter(Predicate predicate) { + public IsInWhenPresent filter(Predicate predicate) { return filterSupport(predicate, IsInWhenPresent::new, this, IsInWhenPresent::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @param type of the new condition - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public IsInWhenPresent map(Function mapper) { - Function, IsInWhenPresent> constructor = IsInWhenPresent::new; - return mapSupport(mapper, constructor, IsInWhenPresent::empty); + @Override + public IsInWhenPresent map(Function mapper) { + return mapSupport(mapper, IsInWhenPresent::of, IsInWhenPresent::empty); } @SafeVarargs - public static IsInWhenPresent of(T... values) { + public static IsInWhenPresent of(@Nullable T... values) { return of(Arrays.asList(values)); } - public static IsInWhenPresent of(Collection values) { - return new IsInWhenPresent<>(values); + public static IsInWhenPresent of(@Nullable Collection<@Nullable T> values) { + if (values == null) { + return empty(); + } else { + return new IsInWhenPresent<>(values); + } } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInWithSubselect.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInWithSubselect.java index 51012cfa2..771e2637b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInWithSubselect.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsInWithSubselect.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,7 +15,6 @@ */ package org.mybatis.dynamic.sql.where.condition; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.AbstractSubselectCondition; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.util.Buildable; @@ -26,7 +25,6 @@ protected IsInWithSubselect(Buildable selectModelBuilder) { super(selectModelBuilder); } - @NotNull public static IsInWithSubselect of(Buildable selectModelBuilder) { return new IsInWithSubselect<>(selectModelBuilder); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThan.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThan.java index 516331e0b..ffe66bd97 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThan.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThan.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,13 +15,22 @@ */ package org.mybatis.dynamic.sql.where.condition; +import java.util.NoSuchElementException; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsLessThan extends AbstractSingleValueCondition { - private static final IsLessThan EMPTY = new IsLessThan(null) { +public class IsLessThan extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + + private static final IsLessThan EMPTY = new IsLessThan(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + @Override public boolean isEmpty() { return true; @@ -48,20 +57,12 @@ public static IsLessThan of(T value) { } @Override - public IsLessThan filter(Predicate predicate) { + public IsLessThan filter(Predicate predicate) { return filterSupport(predicate, IsLessThan::empty, this); } - /** - * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a - * condition that will not render (this). - * - * @param mapper a mapping function to apply to the value, if renderable - * @param type of the new condition - * @return a new condition with the result of applying the mapper to the value of this condition, - * if renderable, otherwise a condition that will not render. - */ - public IsLessThan map(Function mapper) { + @Override + public IsLessThan map(Function mapper) { return mapSupport(mapper, IsLessThan::new, IsLessThan::empty); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanColumn.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanColumn.java index 8d1c200dd..d8fc2b0e7 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanColumn.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualTo.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualTo.java index 83c45cca2..a73707e3e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualTo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,13 +15,21 @@ */ package org.mybatis.dynamic.sql.where.condition; +import java.util.NoSuchElementException; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsLessThanOrEqualTo extends AbstractSingleValueCondition { - private static final IsLessThanOrEqualTo EMPTY = new IsLessThanOrEqualTo(null) { +public class IsLessThanOrEqualTo extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + private static final IsLessThanOrEqualTo EMPTY = new IsLessThanOrEqualTo(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + @Override public boolean isEmpty() { return true; @@ -48,20 +56,12 @@ public static IsLessThanOrEqualTo of(T value) { } @Override - public IsLessThanOrEqualTo filter(Predicate predicate) { + public IsLessThanOrEqualTo filter(Predicate predicate) { return filterSupport(predicate, IsLessThanOrEqualTo::empty, this); } - /** - * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a - * condition that will not render (this). - * - * @param mapper a mapping function to apply to the value, if renderable - * @param type of the new condition - * @return a new condition with the result of applying the mapper to the value of this condition, - * if renderable, otherwise a condition that will not render. - */ - public IsLessThanOrEqualTo map(Function mapper) { + @Override + public IsLessThanOrEqualTo map(Function mapper) { return mapSupport(mapper, IsLessThanOrEqualTo::new, IsLessThanOrEqualTo::empty); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualToColumn.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualToColumn.java index 6c3bd8b65..858f86c1f 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualToColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualToColumn.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualToWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualToWhenPresent.java new file mode 100644 index 000000000..d944b7a45 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualToWhenPresent.java @@ -0,0 +1,72 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AbstractSingleValueCondition; + +public class IsLessThanOrEqualToWhenPresent extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + private static final IsLessThanOrEqualToWhenPresent EMPTY = new IsLessThanOrEqualToWhenPresent(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + public static IsLessThanOrEqualToWhenPresent empty() { + @SuppressWarnings("unchecked") + IsLessThanOrEqualToWhenPresent t = (IsLessThanOrEqualToWhenPresent) EMPTY; + return t; + } + + protected IsLessThanOrEqualToWhenPresent(T value) { + super(value); + } + + @Override + public String operator() { + return "<="; //$NON-NLS-1$ + } + + public static IsLessThanOrEqualToWhenPresent of(@Nullable T value) { + if (value == null) { + return empty(); + } else { + return new IsLessThanOrEqualToWhenPresent<>(value); + } + } + + @Override + public IsLessThanOrEqualToWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsLessThanOrEqualToWhenPresent::empty, this); + } + + @Override + public IsLessThanOrEqualToWhenPresent map(Function mapper) { + return mapSupport(mapper, IsLessThanOrEqualToWhenPresent::of, IsLessThanOrEqualToWhenPresent::empty); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualToWithSubselect.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualToWithSubselect.java index 07d2b67d7..7a7769016 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualToWithSubselect.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanOrEqualToWithSubselect.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,7 +15,6 @@ */ package org.mybatis.dynamic.sql.where.condition; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.AbstractSubselectCondition; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.util.Buildable; @@ -26,7 +25,6 @@ protected IsLessThanOrEqualToWithSubselect(Buildable selectModelBui super(selectModelBuilder); } - @NotNull public static IsLessThanOrEqualToWithSubselect of(Buildable selectModelBuilder) { return new IsLessThanOrEqualToWithSubselect<>(selectModelBuilder); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanWhenPresent.java new file mode 100644 index 000000000..830bf788c --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanWhenPresent.java @@ -0,0 +1,73 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AbstractSingleValueCondition; + +public class IsLessThanWhenPresent extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + + private static final IsLessThanWhenPresent EMPTY = new IsLessThanWhenPresent(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + public static IsLessThanWhenPresent empty() { + @SuppressWarnings("unchecked") + IsLessThanWhenPresent t = (IsLessThanWhenPresent) EMPTY; + return t; + } + + protected IsLessThanWhenPresent(T value) { + super(value); + } + + @Override + public String operator() { + return "<"; //$NON-NLS-1$ + } + + public static IsLessThanWhenPresent of(@Nullable T value) { + if (value == null) { + return empty(); + } else { + return new IsLessThanWhenPresent<>(value); + } + } + + @Override + public IsLessThanWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsLessThanWhenPresent::empty, this); + } + + @Override + public IsLessThanWhenPresent map(Function mapper) { + return mapSupport(mapper, IsLessThanWhenPresent::of, IsLessThanWhenPresent::empty); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanWithSubselect.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanWithSubselect.java index 21a96e6d1..91a2765a4 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanWithSubselect.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLessThanWithSubselect.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,7 +15,6 @@ */ package org.mybatis.dynamic.sql.where.condition; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.AbstractSubselectCondition; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.util.Buildable; @@ -26,7 +25,6 @@ protected IsLessThanWithSubselect(Buildable selectModelBuilder) { super(selectModelBuilder); } - @NotNull public static IsLessThanWithSubselect of(Buildable selectModelBuilder) { return new IsLessThanWithSubselect<>(selectModelBuilder); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLike.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLike.java index 864ddb432..f2d2a419a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLike.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLike.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,13 +15,22 @@ */ package org.mybatis.dynamic.sql.where.condition; +import java.util.NoSuchElementException; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsLike extends AbstractSingleValueCondition { - private static final IsLike EMPTY = new IsLike(null) { +public class IsLike extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + + private static final IsLike EMPTY = new IsLike(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + @Override public boolean isEmpty() { return true; @@ -48,20 +57,12 @@ public static IsLike of(T value) { } @Override - public IsLike filter(Predicate predicate) { + public IsLike filter(Predicate predicate) { return filterSupport(predicate, IsLike::empty, this); } - /** - * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a - * condition that will not render (this). - * - * @param mapper a mapping function to apply to the value, if renderable - * @param type of the new condition - * @return a new condition with the result of applying the mapper to the value of this condition, - * if renderable, otherwise a condition that will not render. - */ - public IsLike map(Function mapper) { + @Override + public IsLike map(Function mapper) { return mapSupport(mapper, IsLike::new, IsLike::empty); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java index 3b13d1748..43525e287 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitive.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,27 +15,37 @@ */ package org.mybatis.dynamic.sql.where.condition; +import java.util.NoSuchElementException; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractSingleValueCondition; import org.mybatis.dynamic.sql.util.StringUtilities; -public class IsLikeCaseInsensitive extends AbstractSingleValueCondition - implements CaseInsensitiveVisitableCondition { - private static final IsLikeCaseInsensitive EMPTY = new IsLikeCaseInsensitive(null) { +public class IsLikeCaseInsensitive extends AbstractSingleValueCondition + implements CaseInsensitiveRenderableCondition, AbstractSingleValueCondition.Filterable, + AbstractSingleValueCondition.Mappable { + private static final IsLikeCaseInsensitive EMPTY = new IsLikeCaseInsensitive<>("") { //$NON-NLS-1$ + @Override + public String value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + @Override public boolean isEmpty() { return true; } }; - public static IsLikeCaseInsensitive empty() { - return EMPTY; + public static IsLikeCaseInsensitive empty() { + @SuppressWarnings("unchecked") + IsLikeCaseInsensitive t = (IsLikeCaseInsensitive) EMPTY; + return t; } - protected IsLikeCaseInsensitive(String value) { - super(value); + protected IsLikeCaseInsensitive(T value) { + super(StringUtilities.upperCaseIfPossible(value)); } @Override @@ -44,23 +54,16 @@ public String operator() { } @Override - public IsLikeCaseInsensitive filter(Predicate predicate) { + public IsLikeCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsLikeCaseInsensitive::empty, this); } - /** - * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a - * condition that will not render (this). - * - * @param mapper a mapping function to apply to the value, if renderable - * @return a new condition with the result of applying the mapper to the value of this condition, - * if renderable, otherwise a condition that will not render. - */ - public IsLikeCaseInsensitive map(UnaryOperator mapper) { + @Override + public IsLikeCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsLikeCaseInsensitive::new, IsLikeCaseInsensitive::empty); } - public static IsLikeCaseInsensitive of(String value) { - return new IsLikeCaseInsensitive(value).map(StringUtilities::safelyUpperCase); + public static IsLikeCaseInsensitive of(T value) { + return new IsLikeCaseInsensitive<>(value); } } 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 new file mode 100644 index 000000000..60307625f --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeCaseInsensitiveWhenPresent.java @@ -0,0 +1,75 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AbstractSingleValueCondition; +import org.mybatis.dynamic.sql.util.StringUtilities; + +public class IsLikeCaseInsensitiveWhenPresent extends AbstractSingleValueCondition + implements CaseInsensitiveRenderableCondition, AbstractSingleValueCondition.Filterable, + 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 boolean isEmpty() { + return true; + } + }; + + public static IsLikeCaseInsensitiveWhenPresent empty() { + @SuppressWarnings("unchecked") + IsLikeCaseInsensitiveWhenPresent t = (IsLikeCaseInsensitiveWhenPresent) EMPTY; + return t; + } + + protected IsLikeCaseInsensitiveWhenPresent(T value) { + super(StringUtilities.upperCaseIfPossible(value)); + } + + @Override + public String operator() { + return "like"; //$NON-NLS-1$ + } + + @Override + public IsLikeCaseInsensitiveWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsLikeCaseInsensitiveWhenPresent::empty, this); + } + + @Override + public IsLikeCaseInsensitiveWhenPresent map(Function mapper) { + return mapSupport(mapper, IsLikeCaseInsensitiveWhenPresent::of, IsLikeCaseInsensitiveWhenPresent::empty); + } + + public static IsLikeCaseInsensitiveWhenPresent of(@Nullable T value) { + if (value == null) { + return empty(); + } else { + return new IsLikeCaseInsensitiveWhenPresent<>(value); + } + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeWhenPresent.java new file mode 100644 index 000000000..fc20722a2 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsLikeWhenPresent.java @@ -0,0 +1,73 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AbstractSingleValueCondition; + +public class IsLikeWhenPresent extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + + private static final IsLikeWhenPresent EMPTY = new IsLikeWhenPresent(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + public static IsLikeWhenPresent empty() { + @SuppressWarnings("unchecked") + IsLikeWhenPresent t = (IsLikeWhenPresent) EMPTY; + return t; + } + + protected IsLikeWhenPresent(T value) { + super(value); + } + + @Override + public String operator() { + return "like"; //$NON-NLS-1$ + } + + public static IsLikeWhenPresent of(@Nullable T value) { + if (value == null) { + return empty(); + } else { + return new IsLikeWhenPresent<>(value); + } + } + + @Override + public IsLikeWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsLikeWhenPresent::empty, this); + } + + @Override + public IsLikeWhenPresent map(Function mapper) { + return mapSupport(mapper, IsLikeWhenPresent::of, IsLikeWhenPresent::empty); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetween.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetween.java index f380449fd..b3d0d59ff 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetween.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetween.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,15 +15,27 @@ */ package org.mybatis.dynamic.sql.where.condition; -import java.util.Objects; +import java.util.NoSuchElementException; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractTwoValueCondition; -public class IsNotBetween extends AbstractTwoValueCondition { - private static final IsNotBetween EMPTY = new IsNotBetween(null, null) { +public class IsNotBetween extends AbstractTwoValueCondition + implements AbstractTwoValueCondition.Filterable, AbstractTwoValueCondition.Mappable { + private static final IsNotBetween EMPTY = new IsNotBetween(-1, -1) { + @Override + public Object value1() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public Object value2() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + @Override public boolean isEmpty() { return true; @@ -51,40 +63,23 @@ public String operator2() { } @Override - public IsNotBetween filter(BiPredicate predicate) { + public IsNotBetween filter(BiPredicate predicate) { return filterSupport(predicate, IsNotBetween::empty, this); } @Override - public IsNotBetween filter(Predicate predicate) { + public IsNotBetween filter(Predicate predicate) { return filterSupport(predicate, IsNotBetween::empty, this); } - /** - * If renderable, apply the mappings to the values and return a new condition with the new values. Else return a - * condition that will not render (this). - * - * @param mapper1 a mapping function to apply to the first value, if renderable - * @param mapper2 a mapping function to apply to the second value, if renderable - * @param type of the new condition - * @return a new condition with the result of applying the mappers to the values of this condition, - * if renderable, otherwise a condition that will not render. - */ - public IsNotBetween map(Function mapper1, - Function mapper2) { + @Override + public IsNotBetween map(Function mapper1, + Function mapper2) { return mapSupport(mapper1, mapper2, IsNotBetween::new, IsNotBetween::empty); } - /** - * If renderable, apply the mapping to both values and return a new condition with the new values. Else return a - * condition that will not render (this). - * - * @param mapper a mapping function to apply to both values, if renderable - * @param type of the new condition - * @return a new condition with the result of applying the mappers to the values of this condition, - * if renderable, otherwise a condition that will not render. - */ - public IsNotBetween map(Function mapper) { + @Override + public IsNotBetween map(Function mapper) { return map(mapper, mapper); } @@ -92,10 +87,6 @@ public static Builder isNotBetween(T value1) { return new Builder<>(value1); } - public static WhenPresentBuilder isNotBetweenWhenPresent(T value1) { - return new WhenPresentBuilder<>(value1); - } - public static class Builder extends AndGatherer> { private Builder(T value1) { @@ -103,20 +94,8 @@ private Builder(T value1) { } @Override - protected IsNotBetween build() { + protected IsNotBetween build(T value2) { return new IsNotBetween<>(value1, value2); } } - - public static class WhenPresentBuilder extends AndGatherer> { - - private WhenPresentBuilder(T value1) { - super(value1); - } - - @Override - protected IsNotBetween build() { - return new IsNotBetween<>(value1, value2).filter(Objects::nonNull); - } - } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetweenWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetweenWhenPresent.java new file mode 100644 index 000000000..3c9c8fc9f --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotBetweenWhenPresent.java @@ -0,0 +1,110 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.NoSuchElementException; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AbstractTwoValueCondition; + +public class IsNotBetweenWhenPresent extends AbstractTwoValueCondition + implements AbstractTwoValueCondition.Filterable, AbstractTwoValueCondition.Mappable { + private static final IsNotBetweenWhenPresent EMPTY = new IsNotBetweenWhenPresent(-1, -1) { + @Override + public Object value1() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public Object value2() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + public static IsNotBetweenWhenPresent empty() { + @SuppressWarnings("unchecked") + IsNotBetweenWhenPresent t = (IsNotBetweenWhenPresent) EMPTY; + return t; + } + + protected IsNotBetweenWhenPresent(T value1, T value2) { + super(value1, value2); + } + + @Override + public String operator1() { + return "not between"; //$NON-NLS-1$ + } + + @Override + public String operator2() { + return "and"; //$NON-NLS-1$ + } + + @Override + public IsNotBetweenWhenPresent filter(BiPredicate predicate) { + return filterSupport(predicate, IsNotBetweenWhenPresent::empty, this); + } + + @Override + public IsNotBetweenWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsNotBetweenWhenPresent::empty, this); + } + + @Override + public IsNotBetweenWhenPresent map(Function mapper1, + Function mapper2) { + return mapSupport(mapper1, mapper2, IsNotBetweenWhenPresent::of, IsNotBetweenWhenPresent::empty); + } + + @Override + public IsNotBetweenWhenPresent map(Function mapper) { + return map(mapper, mapper); + } + + public static IsNotBetweenWhenPresent of(@Nullable T value1, @Nullable T value2) { + if (value1 == null || value2 == null) { + return empty(); + } else { + return new IsNotBetweenWhenPresent<>(value1, value2); + } + } + + public static Builder isNotBetweenWhenPresent(@Nullable T value1) { + return new Builder<>(value1); + } + + public static class Builder extends AndWhenPresentGatherer> { + + private Builder(@Nullable T value1) { + super(value1); + } + + @Override + protected IsNotBetweenWhenPresent build(@Nullable T value2) { + return of(value1, value2); + } + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualTo.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualTo.java index f35516e56..fc0292070 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualTo.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualTo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,13 +15,21 @@ */ package org.mybatis.dynamic.sql.where.condition; +import java.util.NoSuchElementException; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsNotEqualTo extends AbstractSingleValueCondition { - private static final IsNotEqualTo EMPTY = new IsNotEqualTo(null) { +public class IsNotEqualTo extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + private static final IsNotEqualTo EMPTY = new IsNotEqualTo(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + @Override public boolean isEmpty() { return true; @@ -48,20 +56,12 @@ public static IsNotEqualTo of(T value) { } @Override - public IsNotEqualTo filter(Predicate predicate) { + public IsNotEqualTo filter(Predicate predicate) { return filterSupport(predicate, IsNotEqualTo::empty, this); } - /** - * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a - * condition that will not render (this). - * - * @param mapper a mapping function to apply to the value, if renderable - * @param type of the new condition - * @return a new condition with the result of applying the mapper to the value of this condition, - * if renderable, otherwise a condition that will not render. - */ - public IsNotEqualTo map(Function mapper) { + @Override + public IsNotEqualTo map(Function mapper) { return mapSupport(mapper, IsNotEqualTo::new, IsNotEqualTo::empty); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualToColumn.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualToColumn.java index 7dc52aa26..c8721b035 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualToColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualToColumn.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualToWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualToWhenPresent.java new file mode 100644 index 000000000..1fff2d9ba --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualToWhenPresent.java @@ -0,0 +1,72 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AbstractSingleValueCondition; + +public class IsNotEqualToWhenPresent extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + private static final IsNotEqualToWhenPresent EMPTY = new IsNotEqualToWhenPresent(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + public static IsNotEqualToWhenPresent empty() { + @SuppressWarnings("unchecked") + IsNotEqualToWhenPresent t = (IsNotEqualToWhenPresent) EMPTY; + return t; + } + + protected IsNotEqualToWhenPresent(T value) { + super(value); + } + + @Override + public String operator() { + return "<>"; //$NON-NLS-1$ + } + + public static IsNotEqualToWhenPresent of(@Nullable T value) { + if (value == null) { + return empty(); + } else { + return new IsNotEqualToWhenPresent<>(value); + } + } + + @Override + public IsNotEqualToWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsNotEqualToWhenPresent::empty, this); + } + + @Override + public IsNotEqualToWhenPresent map(Function mapper) { + return mapSupport(mapper, IsNotEqualToWhenPresent::of, IsNotEqualToWhenPresent::empty); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualToWithSubselect.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualToWithSubselect.java index a9095e0c0..2e19d9a19 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualToWithSubselect.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotEqualToWithSubselect.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,7 +15,6 @@ */ package org.mybatis.dynamic.sql.where.condition; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.AbstractSubselectCondition; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.util.Buildable; @@ -26,7 +25,6 @@ protected IsNotEqualToWithSubselect(Buildable selectModelBuilder) { super(selectModelBuilder); } - @NotNull public static IsNotEqualToWithSubselect of(Buildable selectModelBuilder) { return new IsNotEqualToWithSubselect<>(selectModelBuilder); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotIn.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotIn.java index dc7358b2a..af6c248f4 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotIn.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotIn.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,10 +21,13 @@ import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractListValueCondition; import org.mybatis.dynamic.sql.render.RenderingContext; +import org.mybatis.dynamic.sql.util.Validator; -public class IsNotIn extends AbstractListValueCondition { +public class IsNotIn extends AbstractListValueCondition + implements AbstractListValueCondition.Filterable, AbstractListValueCondition.Mappable { private static final IsNotIn EMPTY = new IsNotIn<>(Collections.emptyList()); public static IsNotIn empty() { @@ -39,6 +42,7 @@ protected IsNotIn(Collection values) { @Override public boolean shouldRender(RenderingContext renderingContext) { + Validator.assertNotEmpty(values, "ERROR.44", "IsNotIn"); //$NON-NLS-1$ //$NON-NLS-2$ return true; } @@ -48,21 +52,13 @@ public String operator() { } @Override - public IsNotIn filter(Predicate predicate) { + public IsNotIn filter(Predicate predicate) { return filterSupport(predicate, IsNotIn::new, this, IsNotIn::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @param type of the new condition - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public IsNotIn map(Function mapper) { - Function, IsNotIn> constructor = IsNotIn::new; - return mapSupport(mapper, constructor, IsNotIn::empty); + @Override + public IsNotIn map(Function mapper) { + return mapSupport(mapper, IsNotIn::new, IsNotIn::empty); } @SafeVarargs diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitive.java index fa43af0b6..98a9ca9db 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitive.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -18,27 +18,33 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractListValueCondition; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.StringUtilities; +import org.mybatis.dynamic.sql.util.Validator; -public class IsNotInCaseInsensitive extends AbstractListValueCondition - implements CaseInsensitiveVisitableCondition { - private static final IsNotInCaseInsensitive EMPTY = new IsNotInCaseInsensitive(Collections.emptyList()); +public class IsNotInCaseInsensitive extends AbstractListValueCondition + implements CaseInsensitiveRenderableCondition, AbstractListValueCondition.Filterable, + AbstractListValueCondition.Mappable { + private static final IsNotInCaseInsensitive EMPTY = new IsNotInCaseInsensitive<>(Collections.emptyList()); - public static IsNotInCaseInsensitive empty() { - return EMPTY; + public static IsNotInCaseInsensitive empty() { + @SuppressWarnings("unchecked") + IsNotInCaseInsensitive t = (IsNotInCaseInsensitive) EMPTY; + return t; } - protected IsNotInCaseInsensitive(Collection values) { - super(values); + protected IsNotInCaseInsensitive(Collection values) { + super(values.stream().map(StringUtilities::upperCaseIfPossible).toList()); } @Override public boolean shouldRender(RenderingContext renderingContext) { + Validator.assertNotEmpty(values, "ERROR.44", "IsNotInCaseInsensitive"); //$NON-NLS-1$ //$NON-NLS-2$ return true; } @@ -48,26 +54,21 @@ public String operator() { } @Override - public IsNotInCaseInsensitive filter(Predicate predicate) { + public IsNotInCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsNotInCaseInsensitive::new, this, IsNotInCaseInsensitive::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public IsNotInCaseInsensitive map(UnaryOperator mapper) { + @Override + public IsNotInCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsNotInCaseInsensitive::new, IsNotInCaseInsensitive::empty); } - public static IsNotInCaseInsensitive of(String... values) { + @SafeVarargs + public static IsNotInCaseInsensitive of(T... values) { return of(Arrays.asList(values)); } - public static IsNotInCaseInsensitive of(Collection values) { - return new IsNotInCaseInsensitive(values).map(StringUtilities::safelyUpperCase); + public static IsNotInCaseInsensitive of(Collection values) { + return new IsNotInCaseInsensitive<>(values); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java index 58f0b347e..6852c67fd 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInCaseInsensitiveWhenPresent.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,23 +19,28 @@ import java.util.Collection; import java.util.Collections; import java.util.Objects; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; -import java.util.stream.Collectors; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AbstractListValueCondition; import org.mybatis.dynamic.sql.util.StringUtilities; -public class IsNotInCaseInsensitiveWhenPresent extends AbstractListValueCondition - implements CaseInsensitiveVisitableCondition { - private static final IsNotInCaseInsensitiveWhenPresent EMPTY = new IsNotInCaseInsensitiveWhenPresent(Collections.emptyList()); +public class IsNotInCaseInsensitiveWhenPresent extends AbstractListValueCondition + implements CaseInsensitiveRenderableCondition, AbstractListValueCondition.Filterable, + AbstractListValueCondition.Mappable { + private static final IsNotInCaseInsensitiveWhenPresent EMPTY = + new IsNotInCaseInsensitiveWhenPresent<>(Collections.emptyList()); - public static IsNotInCaseInsensitiveWhenPresent empty() { - return EMPTY; + public static IsNotInCaseInsensitiveWhenPresent empty() { + @SuppressWarnings("unchecked") + IsNotInCaseInsensitiveWhenPresent t = (IsNotInCaseInsensitiveWhenPresent) EMPTY; + return t; } - protected IsNotInCaseInsensitiveWhenPresent(Collection values) { - super(values.stream().filter(Objects::nonNull).collect(Collectors.toList())); + protected IsNotInCaseInsensitiveWhenPresent(Collection values) { + super(values.stream().filter(Objects::nonNull).map(StringUtilities::upperCaseIfPossible).toList()); } @Override @@ -44,26 +49,26 @@ public String operator() { } @Override - public IsNotInCaseInsensitiveWhenPresent filter(Predicate predicate) { - return filterSupport(predicate, IsNotInCaseInsensitiveWhenPresent::new, this, IsNotInCaseInsensitiveWhenPresent::empty); + public IsNotInCaseInsensitiveWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsNotInCaseInsensitiveWhenPresent::new, + this, IsNotInCaseInsensitiveWhenPresent::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public IsNotInCaseInsensitiveWhenPresent map(UnaryOperator mapper) { + @Override + public IsNotInCaseInsensitiveWhenPresent map(Function mapper) { return mapSupport(mapper, IsNotInCaseInsensitiveWhenPresent::new, IsNotInCaseInsensitiveWhenPresent::empty); } - public static IsNotInCaseInsensitiveWhenPresent of(String... values) { + @SafeVarargs + public static IsNotInCaseInsensitiveWhenPresent of(@Nullable T... values) { return of(Arrays.asList(values)); } - public static IsNotInCaseInsensitiveWhenPresent of(Collection values) { - return new IsNotInCaseInsensitiveWhenPresent(values).map(StringUtilities::safelyUpperCase); + public static IsNotInCaseInsensitiveWhenPresent of(@Nullable Collection<@Nullable T> values) { + if (values == null) { + return empty(); + } else { + return new IsNotInCaseInsensitiveWhenPresent<>(values); + } } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInWhenPresent.java index 8115413ef..33efb1782 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInWhenPresent.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInWhenPresent.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,11 +21,13 @@ import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; -import java.util.stream.Collectors; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.AbstractListValueCondition; -public class IsNotInWhenPresent extends AbstractListValueCondition { +public class IsNotInWhenPresent extends AbstractListValueCondition + implements AbstractListValueCondition.Filterable, AbstractListValueCondition.Mappable { private static final IsNotInWhenPresent EMPTY = new IsNotInWhenPresent<>(Collections.emptyList()); public static IsNotInWhenPresent empty() { @@ -35,7 +37,7 @@ public static IsNotInWhenPresent empty() { } protected IsNotInWhenPresent(Collection values) { - super(values.stream().filter(Objects::nonNull).collect(Collectors.toList())); + super(values.stream().filter(Objects::nonNull).toList()); } @Override @@ -44,29 +46,25 @@ public String operator() { } @Override - public IsNotInWhenPresent filter(Predicate predicate) { + public IsNotInWhenPresent filter(Predicate predicate) { return filterSupport(predicate, IsNotInWhenPresent::new, this, IsNotInWhenPresent::empty); } - /** - * If not empty, apply the mapping to each value in the list return a new condition with the mapped values. - * Else return an empty condition (this). - * - * @param mapper a mapping function to apply to the values, if not empty - * @param type of the new condition - * @return a new condition with mapped values if renderable, otherwise an empty condition - */ - public IsNotInWhenPresent map(Function mapper) { - Function, IsNotInWhenPresent> constructor = IsNotInWhenPresent::new; - return mapSupport(mapper, constructor, IsNotInWhenPresent::empty); + @Override + public IsNotInWhenPresent map(Function mapper) { + return mapSupport(mapper, IsNotInWhenPresent::new, IsNotInWhenPresent::empty); } @SafeVarargs - public static IsNotInWhenPresent of(T... values) { + public static IsNotInWhenPresent of(@Nullable T... values) { return of(Arrays.asList(values)); } - public static IsNotInWhenPresent of(Collection values) { - return new IsNotInWhenPresent<>(values); + public static IsNotInWhenPresent of(@Nullable Collection<@Nullable T> values) { + if (values == null) { + return empty(); + } else { + return new IsNotInWhenPresent<>(values); + } } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInWithSubselect.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInWithSubselect.java index 587994bf8..f6f3764f1 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInWithSubselect.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotInWithSubselect.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,7 +15,6 @@ */ package org.mybatis.dynamic.sql.where.condition; -import org.jetbrains.annotations.NotNull; import org.mybatis.dynamic.sql.AbstractSubselectCondition; import org.mybatis.dynamic.sql.select.SelectModel; import org.mybatis.dynamic.sql.util.Buildable; @@ -26,7 +25,6 @@ protected IsNotInWithSubselect(Buildable selectModelBuilder) { super(selectModelBuilder); } - @NotNull public static IsNotInWithSubselect of(Buildable selectModelBuilder) { return new IsNotInWithSubselect<>(selectModelBuilder); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLike.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLike.java index 79d97ca15..b5b82d675 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLike.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLike.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,13 +15,21 @@ */ package org.mybatis.dynamic.sql.where.condition; +import java.util.NoSuchElementException; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -public class IsNotLike extends AbstractSingleValueCondition { - private static final IsNotLike EMPTY = new IsNotLike(null) { +public class IsNotLike extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + private static final IsNotLike EMPTY = new IsNotLike(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + @Override public boolean isEmpty() { return true; @@ -48,23 +56,12 @@ public static IsNotLike of(T value) { } @Override - public IsNotLike filter(Predicate predicate) { + public IsNotLike filter(Predicate predicate) { return filterSupport(predicate, IsNotLike::empty, this); } - /** - * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a - * condition that will not render (this). - * - * @param mapper - * a mapping function to apply to the value, if renderable - * @param - * type of the new condition - * - * @return a new condition with the result of applying the mapper to the value of this condition, if renderable, - * otherwise a condition that will not render. - */ - public IsNotLike map(Function mapper) { + @Override + public IsNotLike map(Function mapper) { return mapSupport(mapper, IsNotLike::new, IsNotLike::empty); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java index eb0450135..1604deb3b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitive.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,27 +15,37 @@ */ package org.mybatis.dynamic.sql.where.condition; +import java.util.NoSuchElementException; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.UnaryOperator; +import org.jspecify.annotations.NonNull; import org.mybatis.dynamic.sql.AbstractSingleValueCondition; import org.mybatis.dynamic.sql.util.StringUtilities; -public class IsNotLikeCaseInsensitive extends AbstractSingleValueCondition - implements CaseInsensitiveVisitableCondition { - private static final IsNotLikeCaseInsensitive EMPTY = new IsNotLikeCaseInsensitive(null) { +public class IsNotLikeCaseInsensitive extends AbstractSingleValueCondition + implements CaseInsensitiveRenderableCondition, AbstractSingleValueCondition.Filterable, + AbstractSingleValueCondition.Mappable { + private static final IsNotLikeCaseInsensitive EMPTY = new IsNotLikeCaseInsensitive<>("") { //$NON-NLS-1$ + @Override + public String value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + @Override public boolean isEmpty() { return true; } }; - public static IsNotLikeCaseInsensitive empty() { - return EMPTY; + public static IsNotLikeCaseInsensitive empty() { + @SuppressWarnings("unchecked") + IsNotLikeCaseInsensitive t = (IsNotLikeCaseInsensitive) EMPTY; + return t; } - protected IsNotLikeCaseInsensitive(String value) { - super(value); + protected IsNotLikeCaseInsensitive(T value) { + super(StringUtilities.upperCaseIfPossible(value)); } @Override @@ -44,25 +54,16 @@ public String operator() { } @Override - public IsNotLikeCaseInsensitive filter(Predicate predicate) { + public IsNotLikeCaseInsensitive filter(Predicate predicate) { return filterSupport(predicate, IsNotLikeCaseInsensitive::empty, this); } - /** - * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a - * condition that will not render (this). - * - * @param mapper - * a mapping function to apply to the value, if renderable - * - * @return a new condition with the result of applying the mapper to the value of this condition, if renderable, - * otherwise a condition that will not render. - */ - public IsNotLikeCaseInsensitive map(UnaryOperator mapper) { + @Override + public IsNotLikeCaseInsensitive map(Function mapper) { return mapSupport(mapper, IsNotLikeCaseInsensitive::new, IsNotLikeCaseInsensitive::empty); } - public static IsNotLikeCaseInsensitive of(String value) { - return new IsNotLikeCaseInsensitive(value).map(StringUtilities::safelyUpperCase); + public static IsNotLikeCaseInsensitive of(T value) { + return new IsNotLikeCaseInsensitive<>(value); } } 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 new file mode 100644 index 000000000..cc0b04549 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeCaseInsensitiveWhenPresent.java @@ -0,0 +1,75 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AbstractSingleValueCondition; +import org.mybatis.dynamic.sql.util.StringUtilities; + +public class IsNotLikeCaseInsensitiveWhenPresent extends AbstractSingleValueCondition + implements CaseInsensitiveRenderableCondition, AbstractSingleValueCondition.Filterable, + 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 boolean isEmpty() { + return true; + } + }; + + public static IsNotLikeCaseInsensitiveWhenPresent empty() { + @SuppressWarnings("unchecked") + IsNotLikeCaseInsensitiveWhenPresent t = (IsNotLikeCaseInsensitiveWhenPresent) EMPTY; + return t; + } + + protected IsNotLikeCaseInsensitiveWhenPresent(T value) { + super(StringUtilities.upperCaseIfPossible(value)); + } + + @Override + public String operator() { + return "not like"; //$NON-NLS-1$ + } + + @Override + public IsNotLikeCaseInsensitiveWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsNotLikeCaseInsensitiveWhenPresent::empty, this); + } + + @Override + public IsNotLikeCaseInsensitiveWhenPresent map(Function mapper) { + return mapSupport(mapper, IsNotLikeCaseInsensitiveWhenPresent::of, IsNotLikeCaseInsensitiveWhenPresent::empty); + } + + public static IsNotLikeCaseInsensitiveWhenPresent of(@Nullable T value) { + if (value == null) { + return empty(); + } else { + return new IsNotLikeCaseInsensitiveWhenPresent<>(value); + } + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeWhenPresent.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeWhenPresent.java new file mode 100644 index 000000000..a3571b0ee --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotLikeWhenPresent.java @@ -0,0 +1,72 @@ +/* + * 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 org.mybatis.dynamic.sql.where.condition; + +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.mybatis.dynamic.sql.AbstractSingleValueCondition; + +public class IsNotLikeWhenPresent extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + private static final IsNotLikeWhenPresent EMPTY = new IsNotLikeWhenPresent(-1) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + public static IsNotLikeWhenPresent empty() { + @SuppressWarnings("unchecked") + IsNotLikeWhenPresent t = (IsNotLikeWhenPresent) EMPTY; + return t; + } + + protected IsNotLikeWhenPresent(T value) { + super(value); + } + + @Override + public String operator() { + return "not like"; //$NON-NLS-1$ + } + + public static IsNotLikeWhenPresent of(@Nullable T value) { + if (value == null) { + return empty(); + } else { + return new IsNotLikeWhenPresent<>(value); + } + } + + @Override + public IsNotLikeWhenPresent filter(Predicate predicate) { + return filterSupport(predicate, IsNotLikeWhenPresent::empty, this); + } + + @Override + public IsNotLikeWhenPresent map(Function mapper) { + return mapSupport(mapper, IsNotLikeWhenPresent::of, IsNotLikeWhenPresent::empty); + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotNull.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotNull.java index 59b4052f8..1c1f3139d 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotNull.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNotNull.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,8 +19,8 @@ import org.mybatis.dynamic.sql.AbstractNoValueCondition; -public class IsNotNull extends AbstractNoValueCondition { - private static final IsNotNull EMPTY = new IsNotNull() { +public class IsNotNull extends AbstractNoValueCondition implements AbstractNoValueCondition.Filterable { + private static final IsNotNull EMPTY = new IsNotNull<>() { @Override public boolean isEmpty() { return true; @@ -42,17 +42,7 @@ public String operator() { return "is not null"; //$NON-NLS-1$ } - /** - * If renderable and the supplier returns true, returns this condition. Else returns a condition that will not - * render. - * - * @param booleanSupplier - * function that specifies whether the condition should render - * @param - * condition type - not used except for compilation compliance - * - * @return this condition if renderable and the supplier returns true, otherwise a condition that will not render. - */ + @Override public IsNotNull filter(BooleanSupplier booleanSupplier) { @SuppressWarnings("unchecked") IsNotNull self = (IsNotNull) this; diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNull.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNull.java index 61fe53da2..a27b7dc2a 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNull.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsNull.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,8 +19,8 @@ import org.mybatis.dynamic.sql.AbstractNoValueCondition; -public class IsNull extends AbstractNoValueCondition { - private static final IsNull EMPTY = new IsNull() { +public class IsNull extends AbstractNoValueCondition implements AbstractNoValueCondition.Filterable { + private static final IsNull EMPTY = new IsNull<>() { @Override public boolean isEmpty() { return true; @@ -42,17 +42,7 @@ public String operator() { return "is null"; //$NON-NLS-1$ } - /** - * If renderable and the supplier returns true, returns this condition. Else returns a condition that will not - * render. - * - * @param booleanSupplier - * function that specifies whether the condition should render - * @param - * condition type - not used except for compilation compliance - * - * @return this condition if renderable and the supplier returns true, otherwise a condition that will not render. - */ + @Override public IsNull filter(BooleanSupplier booleanSupplier) { @SuppressWarnings("unchecked") IsNull self = (IsNull) this; diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveVisitableCondition.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/package-info.java similarity index 63% rename from src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveVisitableCondition.java rename to src/main/java/org/mybatis/dynamic/sql/where/condition/package-info.java index 18b21a136..3457063de 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/condition/CaseInsensitiveVisitableCondition.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -13,14 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@NullMarked package org.mybatis.dynamic.sql.where.condition; -import org.mybatis.dynamic.sql.VisitableCondition; - -public interface CaseInsensitiveVisitableCondition extends VisitableCondition { - - @Override - default String overrideRenderedLeftColumn(String renderedLeftColumn) { - return "upper(" + renderedLeftColumn + ")"; //$NON-NLS-1$ //$NON-NLS-2$ - } -} +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/where/package-info.java b/src/main/java/org/mybatis/dynamic/sql/where/package-info.java new file mode 100644 index 000000000..194b40e86 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.where; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/mybatis/dynamic/sql/where/render/ColumnAndConditionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/where/render/ColumnAndConditionRenderer.java index 2a78ccbf8..c094bca1b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/render/ColumnAndConditionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/render/ColumnAndConditionRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,18 +15,19 @@ */ package org.mybatis.dynamic.sql.where.render; -import static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore; - import java.util.Objects; +import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.BindableColumn; -import org.mybatis.dynamic.sql.VisitableCondition; +import org.mybatis.dynamic.sql.RenderableCondition; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.util.FragmentAndParameters; +import org.mybatis.dynamic.sql.util.FragmentCollector; public class ColumnAndConditionRenderer { private final BindableColumn column; - private final VisitableCondition condition; + private final RenderableCondition condition; private final RenderingContext renderingContext; private ColumnAndConditionRenderer(Builder builder) { @@ -36,34 +37,23 @@ private ColumnAndConditionRenderer(Builder builder) { } public FragmentAndParameters render() { - FragmentAndParameters renderedLeftColumn = column.render(renderingContext); - - DefaultConditionVisitor visitor = DefaultConditionVisitor.withColumn(column) - .withRenderingContext(renderingContext) - .build(); - - FragmentAndParameters renderedCondition = condition.accept(visitor); - - String finalFragment = condition.overrideRenderedLeftColumn(renderedLeftColumn.fragment()) - + spaceBefore(renderedCondition.fragment()); - - return FragmentAndParameters.withFragment(finalFragment) - .withParameters(renderedLeftColumn.parameters()) - .withParameters(renderedCondition.parameters()) - .build(); + FragmentCollector fc = new FragmentCollector(); + fc.add(condition.renderLeftColumn(renderingContext, column)); + fc.add(condition.renderCondition(renderingContext, column)); + return fc.toFragmentAndParameters(Collectors.joining(" ")); //$NON-NLS-1$ } public static class Builder { - private BindableColumn column; - private VisitableCondition condition; - private RenderingContext renderingContext; + private @Nullable BindableColumn column; + private @Nullable RenderableCondition condition; + private @Nullable RenderingContext renderingContext; public Builder withColumn(BindableColumn column) { this.column = column; return this; } - public Builder withCondition(VisitableCondition condition) { + public Builder withCondition(RenderableCondition condition) { this.condition = condition; return this; } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/render/CriterionRenderer.java b/src/main/java/org/mybatis/dynamic/sql/where/render/CriterionRenderer.java index 6e3e13254..09e8091c8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/render/CriterionRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/render/CriterionRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -30,7 +30,7 @@ import org.mybatis.dynamic.sql.SqlCriterion; import org.mybatis.dynamic.sql.SqlCriterionVisitor; import org.mybatis.dynamic.sql.render.RenderingContext; -import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; +import org.mybatis.dynamic.sql.select.render.SubQueryRenderer; import org.mybatis.dynamic.sql.util.FragmentAndParameters; import org.mybatis.dynamic.sql.util.FragmentCollector; @@ -119,25 +119,18 @@ private Optional renderColumnAndCondition(ColumnAndCo private FragmentAndParameters renderExists(ExistsCriterion criterion) { ExistsPredicate existsPredicate = criterion.existsPredicate(); - - SelectStatementProvider selectStatement = existsPredicate.selectModelBuilder().build().render(renderingContext); - - String fragment = existsPredicate.operator() - + " (" //$NON-NLS-1$ - + selectStatement.getSelectStatement() - + ")"; //$NON-NLS-1$ - - return FragmentAndParameters - .withFragment(fragment) - .withParameters(selectStatement.getParameters()) - .build(); + return SubQueryRenderer.withSelectModel(existsPredicate.selectModelBuilder().build()) + .withRenderingContext(renderingContext) + .withPrefix(existsPredicate.operator() + " (") //$NON-NLS-1$ + .withSuffix(")") //$NON-NLS-1$ + .build() + .render(); } private List renderSubCriteria(List subCriteria) { return subCriteria.stream().map(this::renderAndOrCriteriaGroup) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toList()); + .flatMap(Optional::stream) + .toList(); } private Optional renderAndOrCriteriaGroup(AndOrCriteriaGroup criterion) { diff --git a/src/main/java/org/mybatis/dynamic/sql/where/render/DefaultConditionVisitor.java b/src/main/java/org/mybatis/dynamic/sql/where/render/DefaultConditionVisitor.java deleted file mode 100644 index 9adda9f64..000000000 --- a/src/main/java/org/mybatis/dynamic/sql/where/render/DefaultConditionVisitor.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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.where.render; - -import static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore; - -import java.util.Objects; -import java.util.stream.Collectors; - -import org.mybatis.dynamic.sql.AbstractColumnComparisonCondition; -import org.mybatis.dynamic.sql.AbstractListValueCondition; -import org.mybatis.dynamic.sql.AbstractNoValueCondition; -import org.mybatis.dynamic.sql.AbstractSingleValueCondition; -import org.mybatis.dynamic.sql.AbstractSubselectCondition; -import org.mybatis.dynamic.sql.AbstractTwoValueCondition; -import org.mybatis.dynamic.sql.BindableColumn; -import org.mybatis.dynamic.sql.ConditionVisitor; -import org.mybatis.dynamic.sql.render.RenderedParameterInfo; -import org.mybatis.dynamic.sql.render.RenderingContext; -import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; -import org.mybatis.dynamic.sql.util.FragmentAndParameters; -import org.mybatis.dynamic.sql.util.FragmentCollector; - -public class DefaultConditionVisitor implements ConditionVisitor { - - private final BindableColumn column; - private final RenderingContext renderingContext; - - private DefaultConditionVisitor(Builder builder) { - column = Objects.requireNonNull(builder.column); - renderingContext = Objects.requireNonNull(builder.renderingContext); - } - - @Override - public FragmentAndParameters visit(AbstractListValueCondition condition) { - FragmentCollector fc = condition.values() - .map(this::toFragmentAndParameters) - .collect(FragmentCollector.collect()); - - String joinedFragments = - fc.collectFragments(Collectors.joining(",", "(", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - String finalFragment = condition.operator() - + spaceBefore(joinedFragments); - - return FragmentAndParameters - .withFragment(finalFragment) - .withParameters(fc.parameters()) - .build(); - } - - @Override - public FragmentAndParameters visit(AbstractNoValueCondition condition) { - return FragmentAndParameters.fromFragment(condition.operator()); - } - - @Override - public FragmentAndParameters visit(AbstractSingleValueCondition condition) { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(column); - String finalFragment = condition.operator() - + spaceBefore(parameterInfo.renderedPlaceHolder()); - - return FragmentAndParameters.withFragment(finalFragment) - .withParameter(parameterInfo.parameterMapKey(), convertValue(condition.value())) - .build(); - } - - @Override - public FragmentAndParameters visit(AbstractTwoValueCondition condition) { - RenderedParameterInfo parameterInfo1 = renderingContext.calculateParameterInfo(column); - RenderedParameterInfo parameterInfo2 = renderingContext.calculateParameterInfo(column); - - String finalFragment = condition.operator1() - + spaceBefore(parameterInfo1.renderedPlaceHolder()) - + spaceBefore(condition.operator2()) - + spaceBefore(parameterInfo2.renderedPlaceHolder()); - - return FragmentAndParameters.withFragment(finalFragment) - .withParameter(parameterInfo1.parameterMapKey(), convertValue(condition.value1())) - .withParameter(parameterInfo2.parameterMapKey(), convertValue(condition.value2())) - .build(); - } - - @Override - public FragmentAndParameters visit(AbstractSubselectCondition condition) { - SelectStatementProvider selectStatement = condition.selectModel().render(renderingContext); - - String finalFragment = condition.operator() - + " (" //$NON-NLS-1$ - + selectStatement.getSelectStatement() - + ")"; //$NON-NLS-1$ - - return FragmentAndParameters.withFragment(finalFragment) - .withParameters(selectStatement.getParameters()) - .build(); - } - - @Override - public FragmentAndParameters visit(AbstractColumnComparisonCondition condition) { - FragmentAndParameters renderedRightColumn = condition.rightColumn().render(renderingContext); - String finalFragment = condition.operator() - + spaceBefore(renderedRightColumn.fragment()); - return FragmentAndParameters.withFragment(finalFragment) - .withParameters(renderedRightColumn.parameters()) - .build(); - } - - private Object convertValue(T value) { - return column.convertParameterType(value); - } - - private FragmentAndParameters toFragmentAndParameters(T value) { - RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(column); - return FragmentAndParameters.withFragment(parameterInfo.renderedPlaceHolder()) - .withParameter(parameterInfo.parameterMapKey(), convertValue(value)) - .build(); - } - - public static Builder withColumn(BindableColumn column) { - return new Builder().withColumn(column); - } - - public static class Builder { - private BindableColumn column; - private RenderingContext renderingContext; - - public Builder withColumn(BindableColumn column) { - this.column = column; - return this; - } - - public Builder withRenderingContext(RenderingContext renderingContext) { - this.renderingContext = renderingContext; - return this; - } - - public DefaultConditionVisitor build() { - return new DefaultConditionVisitor<>(this); - } - } -} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/render/DefaultWhereClauseProvider.java b/src/main/java/org/mybatis/dynamic/sql/where/render/DefaultWhereClauseProvider.java new file mode 100644 index 000000000..5a10f77d7 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/render/DefaultWhereClauseProvider.java @@ -0,0 +1,65 @@ +/* + * 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 org.mybatis.dynamic.sql.where.render; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.jspecify.annotations.Nullable; + +public class DefaultWhereClauseProvider implements WhereClauseProvider { + private final String whereClause; + private final Map parameters; + + private DefaultWhereClauseProvider(Builder builder) { + whereClause = Objects.requireNonNull(builder.whereClause); + parameters = builder.parameters; + } + + @Override + public Map getParameters() { + return parameters; + } + + @Override + public String getWhereClause() { + return whereClause; + } + + public static Builder withWhereClause(String whereClause) { + return new Builder().withWhereClause(whereClause); + } + + public static class Builder { + private @Nullable String whereClause; + private final Map parameters = new HashMap<>(); + + public Builder withWhereClause(String whereClause) { + this.whereClause = whereClause; + return this; + } + + public Builder withParameters(Map parameters) { + this.parameters.putAll(parameters); + return this; + } + + public DefaultWhereClauseProvider build() { + return new DefaultWhereClauseProvider(this); + } + } +} diff --git a/src/main/java/org/mybatis/dynamic/sql/where/render/RenderedCriterion.java b/src/main/java/org/mybatis/dynamic/sql/where/render/RenderedCriterion.java index 6d1a2de25..1c6b54f27 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/render/RenderedCriterion.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/render/RenderedCriterion.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -19,10 +19,11 @@ import java.util.Objects; +import org.jspecify.annotations.Nullable; import org.mybatis.dynamic.sql.util.FragmentAndParameters; public class RenderedCriterion { - private final String connector; + private final @Nullable String connector; private final FragmentAndParameters fragmentAndParameters; private RenderedCriterion(Builder builder) { @@ -54,8 +55,8 @@ private FragmentAndParameters prependFragment(FragmentAndParameters fragmentAndP } public static class Builder { - private String connector; - private FragmentAndParameters fragmentAndParameters; + private @Nullable String connector; + private @Nullable FragmentAndParameters fragmentAndParameters; public Builder withConnector(String connector) { this.connector = connector; diff --git a/src/main/java/org/mybatis/dynamic/sql/where/render/WhereClauseProvider.java b/src/main/java/org/mybatis/dynamic/sql/where/render/WhereClauseProvider.java index 37d051e81..6fab1f340 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/render/WhereClauseProvider.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/render/WhereClauseProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -15,48 +15,10 @@ */ package org.mybatis.dynamic.sql.where.render; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; -import java.util.Objects; -public class WhereClauseProvider { - private final String whereClause; - private final Map parameters; +public interface WhereClauseProvider { + Map getParameters(); - private WhereClauseProvider(Builder builder) { - whereClause = Objects.requireNonNull(builder.whereClause); - parameters = Objects.requireNonNull(builder.parameters); - } - - public Map getParameters() { - return Collections.unmodifiableMap(parameters); - } - - public String getWhereClause() { - return whereClause; - } - - public static Builder withWhereClause(String whereClause) { - return new Builder().withWhereClause(whereClause); - } - - public static class Builder { - private String whereClause; - private final Map parameters = new HashMap<>(); - - public Builder withWhereClause(String whereClause) { - this.whereClause = whereClause; - return this; - } - - public Builder withParameters(Map parameters) { - this.parameters.putAll(parameters); - return this; - } - - public WhereClauseProvider build() { - return new WhereClauseProvider(this); - } - } + String getWhereClause(); } diff --git a/src/main/java/org/mybatis/dynamic/sql/where/render/WhereRenderer.java b/src/main/java/org/mybatis/dynamic/sql/where/render/WhereRenderer.java index 244dcfa1b..0db5f2dc0 100644 --- a/src/main/java/org/mybatis/dynamic/sql/where/render/WhereRenderer.java +++ b/src/main/java/org/mybatis/dynamic/sql/where/render/WhereRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/java/org/mybatis/dynamic/sql/where/render/package-info.java b/src/main/java/org/mybatis/dynamic/sql/where/render/package-info.java new file mode 100644 index 000000000..cde1387a3 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/where/render/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.mybatis.dynamic.sql.where.render; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt index 4fe8d3091..be4ba72af 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/GroupingCriteriaCollector.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -22,9 +22,9 @@ import org.mybatis.dynamic.sql.ColumnAndConditionCriterion import org.mybatis.dynamic.sql.CriteriaGroup import org.mybatis.dynamic.sql.ExistsCriterion import org.mybatis.dynamic.sql.NotCriterion +import org.mybatis.dynamic.sql.RenderableCondition import org.mybatis.dynamic.sql.SqlBuilder import org.mybatis.dynamic.sql.SqlCriterion -import org.mybatis.dynamic.sql.VisitableCondition typealias GroupingCriteriaReceiver = GroupingCriteriaCollector.() -> Unit @@ -229,21 +229,21 @@ open class GroupingCriteriaCollector : SubCriteriaCollector() { * * @param condition the condition to be applied to this column, in this scope */ - operator fun BindableColumn.invoke(condition: VisitableCondition) { + operator fun BindableColumn.invoke(condition: RenderableCondition) { initialCriterion = ColumnAndConditionCriterion.withColumn(this) .withCondition(condition) .build() } - // infix functions...we may be able to rewrite these as extension functions once Kotlin solves the multiple - // receivers problem (https://youtrack.jetbrains.com/issue/KT-42435) + // infix functions...we may be able to rewrite these as extension functions once Kotlin implements the context + // parameters proposal (https://github.com/Kotlin/KEEP/issues/367) // conditions for all data types fun BindableColumn<*>.isNull() = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNull()) fun BindableColumn<*>.isNotNull() = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotNull()) - infix fun BindableColumn.isEqualTo(value: T & Any) = + infix fun BindableColumn.isEqualTo(value: T) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isEqualTo(value)) infix fun BindableColumn<*>.isEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit) = @@ -252,10 +252,10 @@ open class GroupingCriteriaCollector : SubCriteriaCollector() { infix fun BindableColumn<*>.isEqualTo(column: BasicColumn) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isEqualTo(column)) - infix fun BindableColumn.isEqualToWhenPresent(value: T?) = + infix fun BindableColumn.isEqualToWhenPresent(value: T?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isEqualToWhenPresent(value)) - infix fun BindableColumn.isNotEqualTo(value: T & Any) = + infix fun BindableColumn.isNotEqualTo(value: T) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotEqualTo(value)) infix fun BindableColumn<*>.isNotEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit) = @@ -264,10 +264,10 @@ open class GroupingCriteriaCollector : SubCriteriaCollector() { infix fun BindableColumn<*>.isNotEqualTo(column: BasicColumn) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotEqualTo(column)) - infix fun BindableColumn.isNotEqualToWhenPresent(value: T?) = + infix fun BindableColumn.isNotEqualToWhenPresent(value: T?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotEqualToWhenPresent(value)) - infix fun BindableColumn.isGreaterThan(value: T & Any) = + infix fun BindableColumn.isGreaterThan(value: T) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isGreaterThan(value)) infix fun BindableColumn<*>.isGreaterThan(subQuery: KotlinSubQueryBuilder.() -> Unit) = @@ -276,10 +276,10 @@ open class GroupingCriteriaCollector : SubCriteriaCollector() { infix fun BindableColumn<*>.isGreaterThan(column: BasicColumn) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isGreaterThan(column)) - infix fun BindableColumn.isGreaterThanWhenPresent(value: T?) = + infix fun BindableColumn.isGreaterThanWhenPresent(value: T?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isGreaterThanWhenPresent(value)) - infix fun BindableColumn.isGreaterThanOrEqualTo(value: T & Any) = + infix fun BindableColumn.isGreaterThanOrEqualTo(value: T) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isGreaterThanOrEqualTo(value)) infix fun BindableColumn<*>.isGreaterThanOrEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit) = @@ -288,10 +288,10 @@ open class GroupingCriteriaCollector : SubCriteriaCollector() { infix fun BindableColumn<*>.isGreaterThanOrEqualTo(column: BasicColumn) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isGreaterThanOrEqualTo(column)) - infix fun BindableColumn.isGreaterThanOrEqualToWhenPresent(value: T?) = + infix fun BindableColumn.isGreaterThanOrEqualToWhenPresent(value: T?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isGreaterThanOrEqualToWhenPresent(value)) - infix fun BindableColumn.isLessThan(value: T & Any) = + infix fun BindableColumn.isLessThan(value: T) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isLessThan(value)) infix fun BindableColumn<*>.isLessThan(subQuery: KotlinSubQueryBuilder.() -> Unit) = @@ -300,10 +300,10 @@ open class GroupingCriteriaCollector : SubCriteriaCollector() { infix fun BindableColumn<*>.isLessThan(column: BasicColumn) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isLessThan(column)) - infix fun BindableColumn.isLessThanWhenPresent(value: T?) = + infix fun BindableColumn.isLessThanWhenPresent(value: T?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isLessThanWhenPresent(value)) - infix fun BindableColumn.isLessThanOrEqualTo(value: T & Any) = + infix fun BindableColumn.isLessThanOrEqualTo(value: T) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isLessThanOrEqualTo(value)) infix fun BindableColumn<*>.isLessThanOrEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit) = @@ -312,82 +312,82 @@ open class GroupingCriteriaCollector : SubCriteriaCollector() { infix fun BindableColumn<*>.isLessThanOrEqualTo(column: BasicColumn) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isLessThanOrEqualTo(column)) - infix fun BindableColumn.isLessThanOrEqualToWhenPresent(value: T?) = + infix fun BindableColumn.isLessThanOrEqualToWhenPresent(value: T?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isLessThanOrEqualToWhenPresent(value)) - fun BindableColumn.isIn(vararg values: T & Any) = isIn(values.asList()) + fun BindableColumn.isIn(vararg values: T) = isIn(values.asList()) @JvmName("isInArray") - infix fun BindableColumn.isIn(values: Array) = + infix fun BindableColumn.isIn(values: Array) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isIn(values)) - infix fun BindableColumn.isIn(values: Collection) = + infix fun BindableColumn.isIn(values: Collection) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isIn(values)) infix fun BindableColumn<*>.isIn(subQuery: KotlinSubQueryBuilder.() -> Unit) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isIn(subQuery)) - fun BindableColumn.isInWhenPresent(vararg values: T?) = isInWhenPresent(values.asList()) + fun BindableColumn.isInWhenPresent(vararg values: T?) = isInWhenPresent(values.asList()) @JvmName("isInArrayWhenPresent") - infix fun BindableColumn.isInWhenPresent(values: Array?) = + infix fun BindableColumn.isInWhenPresent(values: Array?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isInWhenPresent(values)) - infix fun BindableColumn.isInWhenPresent(values: Collection?) = + infix fun BindableColumn.isInWhenPresent(values: Collection?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isInWhenPresent(values)) - fun BindableColumn.isNotIn(vararg values: T & Any) = isNotIn(values.asList()) + fun BindableColumn.isNotIn(vararg values: T) = isNotIn(values.asList()) @JvmName("isNotInArray") - infix fun BindableColumn.isNotIn(values: Array) = + infix fun BindableColumn.isNotIn(values: Array) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotIn(values)) - infix fun BindableColumn.isNotIn(values: Collection) = + infix fun BindableColumn.isNotIn(values: Collection) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotIn(values)) infix fun BindableColumn<*>.isNotIn(subQuery: KotlinSubQueryBuilder.() -> Unit) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotIn(subQuery)) - fun BindableColumn.isNotInWhenPresent(vararg values: T?) = isNotInWhenPresent(values.asList()) + fun BindableColumn.isNotInWhenPresent(vararg values: T?) = isNotInWhenPresent(values.asList()) @JvmName("isNotInArrayWhenPresent") - infix fun BindableColumn.isNotInWhenPresent(values: Array?) = + infix fun BindableColumn.isNotInWhenPresent(values: Array?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotInWhenPresent(values)) - infix fun BindableColumn.isNotInWhenPresent(values: Collection?) = + infix fun BindableColumn.isNotInWhenPresent(values: Collection?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotInWhenPresent(values)) - infix fun BindableColumn.isBetween(value1: T & Any) = - SecondValueCollector { + infix fun BindableColumn.isBetween(value1: T) = + SecondValueCollector { invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isBetween(value1).and(it)) } - infix fun BindableColumn.isBetweenWhenPresent(value1: T?) = + infix fun BindableColumn.isBetweenWhenPresent(value1: T?) = NullableSecondValueCollector { invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isBetweenWhenPresent(value1).and(it)) } - infix fun BindableColumn.isNotBetween(value1: T & Any) = - SecondValueCollector { + infix fun BindableColumn.isNotBetween(value1: T) = + SecondValueCollector { invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotBetween(value1).and(it)) } - infix fun BindableColumn.isNotBetweenWhenPresent(value1: T?) = + infix fun BindableColumn.isNotBetweenWhenPresent(value1: T?) = NullableSecondValueCollector { invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotBetweenWhenPresent(value1).and(it)) } // for string columns, but generic for columns with type handlers - infix fun BindableColumn.isLike(value: T & Any) = + infix fun BindableColumn.isLike(value: T) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isLike(value)) - infix fun BindableColumn.isLikeWhenPresent(value: T?) = + infix fun BindableColumn.isLikeWhenPresent(value: T?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isLikeWhenPresent(value)) - infix fun BindableColumn.isNotLike(value: T & Any) = + infix fun BindableColumn.isNotLike(value: T) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotLike(value)) - infix fun BindableColumn.isNotLikeWhenPresent(value: T?) = + infix fun BindableColumn.isNotLikeWhenPresent(value: T?) = invoke(org.mybatis.dynamic.sql.util.kotlin.elements.isNotLikeWhenPresent(value)) // shortcuts for booleans diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt index 3ba5c2acb..c65b37a7a 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/JoinCollector.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -16,40 +16,30 @@ package org.mybatis.dynamic.sql.util.kotlin import org.mybatis.dynamic.sql.BindableColumn +import org.mybatis.dynamic.sql.RenderableCondition import org.mybatis.dynamic.sql.SqlBuilder -import org.mybatis.dynamic.sql.select.join.JoinCondition -import org.mybatis.dynamic.sql.select.join.JoinCriterion typealias JoinReceiver = JoinCollector.() -> Unit @MyBatisDslMarker class JoinCollector { - private var onJoinCriterion: JoinCriterion<*>? = null - internal val andJoinCriteria = mutableListOf>() + private val criteriaCollector = GroupingCriteriaCollector() - internal fun onJoinCriterion() : JoinCriterion<*> = invalidIfNull(onJoinCriterion, "ERROR.22") //$NON-NLS-1$ + internal fun initialCriterion() = invalidIfNull(criteriaCollector.initialCriterion, "ERROR.22") //$NON-NLS-1$ + internal fun subCriteria() = criteriaCollector.subCriteria - fun on(leftColumn: BindableColumn): RightColumnCollector = RightColumnCollector { - onJoinCriterion = JoinCriterion.Builder() - .withConnector("on") //$NON-NLS-1$ - .withJoinColumn(leftColumn) - .withJoinCondition(it) - .build() + fun on(leftColumn: BindableColumn): RightColumnCollector = RightColumnCollector { + assertNull(criteriaCollector.initialCriterion, "ERROR.45") //$NON-NLS-1$ + criteriaCollector.apply { leftColumn.invoke(it) } } - fun and(leftColumn: BindableColumn): RightColumnCollector = RightColumnCollector { - andJoinCriteria.add( - JoinCriterion.Builder() - .withConnector("and") //$NON-NLS-1$ - .withJoinColumn(leftColumn) - .withJoinCondition(it) - .build() - ) + fun and(leftColumn: BindableColumn): RightColumnCollector = RightColumnCollector { + criteriaCollector.and { leftColumn.invoke(it) } } } -class RightColumnCollector(private val joinConditionConsumer: (JoinCondition) -> Unit) { - infix fun equalTo(rightColumn: BindableColumn) = joinConditionConsumer.invoke(SqlBuilder.equalTo(rightColumn)) +class RightColumnCollector(private val joinConditionConsumer: (RenderableCondition) -> Unit) { + infix fun equalTo(rightColumn: BindableColumn) = joinConditionConsumer.invoke(SqlBuilder.isEqualTo(rightColumn)) - infix fun equalTo(value: T) = joinConditionConsumer.invoke(SqlBuilder.equalTo(value)) + infix fun equalTo(value: T) = joinConditionConsumer.invoke(SqlBuilder.isEqualTo(value)) } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KInvalidSQLException.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KInvalidSQLException.kt index fee58915a..1203b555a 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KInvalidSQLException.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KInvalidSQLException.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KValidator.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KValidator.kt index c50b856e1..f9b6aa323 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KValidator.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KValidator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBaseBuilders.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBaseBuilders.kt index 2f6bc175b..ea0d29f36 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBaseBuilders.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBaseBuilders.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -25,15 +25,6 @@ import org.mybatis.dynamic.sql.where.AbstractWhereStarter @DslMarker annotation class MyBatisDslMarker -@Deprecated("Please use GroupingCriteriaCollector.where") -typealias WhereApplier = KotlinBaseBuilder<*>.() -> Unit - -@Deprecated("Please use GroupingCriteriaCollector.where") -fun WhereApplier.andThen(after: WhereApplier): WhereApplier = { - invoke(this) - after(this) -} - @MyBatisDslMarker @Suppress("TooManyFunctions") abstract class KotlinBaseBuilder> { @@ -51,31 +42,6 @@ abstract class KotlinBaseBuilder> { getDsl().where(criteria) } - @Deprecated("Please move the \"and\" function into the where lambda. If the where lambda has more than one condition, you may need to surround the existing conditions with \"group\" first.") - fun and(criteria: GroupingCriteriaReceiver): Unit = - GroupingCriteriaCollector().apply(criteria).let { - getDsl().where().and(it.initialCriterion, it.subCriteria) - } - - @Deprecated("Please move the \"and\" function into the where lambda. If the where lambda has more than one condition, you may need to surround the existing conditions with \"group\" first.") - fun and(criteria: List) { - getDsl().where().and(criteria) - } - - @Deprecated("Please move the \"or\" function into the where lambda. If the where lambda has more than one condition, you may need to surround the existing conditions with \"group\" first.") - fun or(criteria: GroupingCriteriaReceiver): Unit = - GroupingCriteriaCollector().apply(criteria).let { - getDsl().where().or(it.initialCriterion, it.subCriteria) - } - - @Deprecated("Please move the \"or\" function into the where lambda. If the where lambda has more than one condition, you may need to surround the existing conditions with \"group\" first.") - fun or(criteria: List) { - getDsl().where().or(criteria) - } - - @Deprecated("Please use GroupingCriteriaCollector.where, then pass it to the \"where\" method") - fun applyWhere(whereApplier: WhereApplier) = whereApplier.invoke(this) - /** * This function does nothing, but it can be used to make some code snippets more understandable. * @@ -98,76 +64,156 @@ abstract class KotlinBaseBuilder> { @Suppress("TooManyFunctions") abstract class KotlinBaseJoiningBuilder> : KotlinBaseBuilder() { + @Deprecated("Please use the new form with the \"on\" keyword outside the lambda") fun join(table: SqlTable, joinCriteria: JoinReceiver): Unit = applyToDsl(joinCriteria) { jc -> - join(table, jc.onJoinCriterion(), jc.andJoinCriteria) + join(table, jc.initialCriterion(), jc.subCriteria()) } + @Deprecated("Please use the new form with the \"on\" keyword outside the lambda") fun join(table: SqlTable, alias: String, joinCriteria: JoinReceiver): Unit = applyToDsl(joinCriteria) { jc -> - join(table, alias, jc.onJoinCriterion(), jc.andJoinCriteria) + join(table, alias, jc.initialCriterion(), jc.subCriteria()) } + @Deprecated("Please use the new form with the \"on\" keyword outside the lambda") fun join( subQuery: KotlinQualifiedSubQueryBuilder.() -> Unit, joinCriteria: JoinReceiver ): Unit = applyToDsl(subQuery, joinCriteria) { sq, jc -> - join(sq, sq.correlationName, jc.onJoinCriterion(), jc.andJoinCriteria) + join(sq, sq.correlationName, jc.initialCriterion(), jc.subCriteria()) + } + + fun join(table: SqlTable): JoinCriteriaGatherer = + JoinCriteriaGatherer { + getDsl().join(table, it.initialCriterion, it.subCriteria) + } + + fun join(table: SqlTable, alias: String): JoinCriteriaGatherer = + JoinCriteriaGatherer { + getDsl().join(table, alias, it.initialCriterion, it.subCriteria) } + fun join( + subQuery: KotlinQualifiedSubQueryBuilder.() -> Unit): JoinCriteriaGatherer = + JoinCriteriaGatherer { + val sq = KotlinQualifiedSubQueryBuilder().apply(subQuery) + getDsl().join(sq, sq.correlationName, it.initialCriterion, it.subCriteria) + } + + @Deprecated("Please use the new form with the \"on\" keyword outside the lambda") fun fullJoin(table: SqlTable, joinCriteria: JoinReceiver): Unit = applyToDsl(joinCriteria) { jc -> - fullJoin(table, jc.onJoinCriterion(), jc.andJoinCriteria) + fullJoin(table, jc.initialCriterion(), jc.subCriteria()) } + @Deprecated("Please use the new form with the \"on\" keyword outside the lambda") fun fullJoin(table: SqlTable, alias: String, joinCriteria: JoinReceiver): Unit = applyToDsl(joinCriteria) { jc -> - fullJoin(table, alias, jc.onJoinCriterion(), jc.andJoinCriteria) + fullJoin(table, alias, jc.initialCriterion(), jc.subCriteria()) } + @Deprecated("Please use the new form with the \"on\" keyword outside the lambda") fun fullJoin( subQuery: KotlinQualifiedSubQueryBuilder.() -> Unit, joinCriteria: JoinReceiver ): Unit = applyToDsl(subQuery, joinCriteria) { sq, jc -> - fullJoin(sq, sq.correlationName, jc.onJoinCriterion(), jc.andJoinCriteria) + fullJoin(sq, sq.correlationName, jc.initialCriterion(), jc.subCriteria()) + } + + fun fullJoin(table: SqlTable): JoinCriteriaGatherer = + JoinCriteriaGatherer { + getDsl().fullJoin(table, it.initialCriterion, it.subCriteria) } + fun fullJoin(table: SqlTable, alias: String): JoinCriteriaGatherer = + JoinCriteriaGatherer { + getDsl().fullJoin(table, alias, it.initialCriterion, it.subCriteria) + } + + fun fullJoin( + subQuery: KotlinQualifiedSubQueryBuilder.() -> Unit): JoinCriteriaGatherer = + JoinCriteriaGatherer { + val sq = KotlinQualifiedSubQueryBuilder().apply(subQuery) + getDsl().fullJoin(sq, sq.correlationName, it.initialCriterion, it.subCriteria) + } + + @Deprecated("Please use the new form with the \"on\" keyword outside the lambda") fun leftJoin(table: SqlTable, joinCriteria: JoinReceiver): Unit = applyToDsl(joinCriteria) { jc -> - leftJoin(table, jc.onJoinCriterion(), jc.andJoinCriteria) + leftJoin(table, jc.initialCriterion(), jc.subCriteria()) } + @Deprecated("Please use the new form with the \"on\" keyword outside the lambda") fun leftJoin(table: SqlTable, alias: String, joinCriteria: JoinReceiver): Unit = applyToDsl(joinCriteria) { jc -> - leftJoin(table, alias, jc.onJoinCriterion(), jc.andJoinCriteria) + leftJoin(table, alias, jc.initialCriterion(), jc.subCriteria()) } + @Deprecated("Please use the new form with the \"on\" keyword outside the lambda") fun leftJoin( subQuery: KotlinQualifiedSubQueryBuilder.() -> Unit, joinCriteria: JoinReceiver ): Unit = applyToDsl(subQuery, joinCriteria) { sq, jc -> - leftJoin(sq, sq.correlationName, jc.onJoinCriterion(), jc.andJoinCriteria) + leftJoin(sq, sq.correlationName, jc.initialCriterion(), jc.subCriteria()) + } + + fun leftJoin(table: SqlTable): JoinCriteriaGatherer = + JoinCriteriaGatherer { + getDsl().leftJoin(table, it.initialCriterion, it.subCriteria) + } + + fun leftJoin(table: SqlTable, alias: String): JoinCriteriaGatherer = + JoinCriteriaGatherer { + getDsl().leftJoin(table, alias, it.initialCriterion, it.subCriteria) + } + + fun leftJoin( + subQuery: KotlinQualifiedSubQueryBuilder.() -> Unit): JoinCriteriaGatherer = + JoinCriteriaGatherer { + val sq = KotlinQualifiedSubQueryBuilder().apply(subQuery) + getDsl().leftJoin(sq, sq.correlationName, it.initialCriterion, it.subCriteria) } + @Deprecated("Please use the new form with the \"on\" keyword outside the lambda") fun rightJoin(table: SqlTable, joinCriteria: JoinReceiver): Unit = applyToDsl(joinCriteria) { jc -> - rightJoin(table, jc.onJoinCriterion(), jc.andJoinCriteria) + rightJoin(table, jc.initialCriterion(), jc.subCriteria()) } + @Deprecated("Please use the new form with the \"on\" keyword outside the lambda") fun rightJoin(table: SqlTable, alias: String, joinCriteria: JoinReceiver): Unit = applyToDsl(joinCriteria) { jc -> - rightJoin(table, alias, jc.onJoinCriterion(), jc.andJoinCriteria) + rightJoin(table, alias, jc.initialCriterion(), jc.subCriteria()) } + @Deprecated("Please use the new form with the \"on\" keyword outside the lambda") fun rightJoin( subQuery: KotlinQualifiedSubQueryBuilder.() -> Unit, joinCriteria: JoinReceiver ): Unit = applyToDsl(subQuery, joinCriteria) { sq, jc -> - rightJoin(sq, sq.correlationName, jc.onJoinCriterion(), jc.andJoinCriteria) + rightJoin(sq, sq.correlationName, jc.initialCriterion(), jc.subCriteria()) + } + + fun rightJoin(table: SqlTable): JoinCriteriaGatherer = + JoinCriteriaGatherer { + getDsl().rightJoin(table, it.initialCriterion, it.subCriteria) + } + + fun rightJoin(table: SqlTable, alias: String): JoinCriteriaGatherer = + JoinCriteriaGatherer { + getDsl().rightJoin(table, alias, it.initialCriterion, it.subCriteria) + } + + fun rightJoin( + subQuery: KotlinQualifiedSubQueryBuilder.() -> Unit): JoinCriteriaGatherer = + JoinCriteriaGatherer { + val sq = KotlinQualifiedSubQueryBuilder().apply(subQuery) + getDsl().rightJoin(sq, sq.correlationName, it.initialCriterion, it.subCriteria) } private fun applyToDsl(joinCriteria: JoinReceiver, applyJoin: D.(JoinCollector) -> Unit) { @@ -182,3 +228,11 @@ abstract class KotlinBaseJoiningBuilder> : getDsl().applyJoin(KotlinQualifiedSubQueryBuilder().apply(subQuery), JoinCollector().apply(joinCriteria)) } } + +class JoinCriteriaGatherer(private val consumer: (GroupingCriteriaCollector) -> Unit) { + infix fun on (joinCriteria: GroupingCriteriaReceiver): Unit = + with(GroupingCriteriaCollector().apply(joinCriteria)) { + assertTrue(initialCriterion != null || subCriteria.isNotEmpty(), "ERROR.22") //$NON-NLS-1$ + consumer.invoke(this) + } +} diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBatchInsertBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBatchInsertBuilder.kt index 65ae006da..aa2dd3703 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBatchInsertBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBatchInsertBuilder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -33,7 +33,7 @@ class KotlinBatchInsertBuilder (private val rows: Collection): Build this.table = table } - fun map(column: SqlColumn) = MultiRowInsertColumnMapCompleter(column) { + fun map(column: SqlColumn) = MultiRowInsertColumnMapCompleter(column) { columnMappings.add(it) } @@ -41,7 +41,7 @@ class KotlinBatchInsertBuilder (private val rows: Collection): Build assertNotNull(table, "ERROR.23") //$NON-NLS-1$ return with(BatchInsertDSL.Builder()) { withRecords(rows) - withTable(table) + withTable(table!!) withColumnMappings(columnMappings) build() }.build() diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinCountBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinCountBuilder.kt index 78f1f880e..c1aadfbe9 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinCountBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinCountBuilder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinDeleteBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinDeleteBuilder.kt index 011928b9e..aac282b9b 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinDeleteBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinDeleteBuilder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -30,7 +30,11 @@ class KotlinDeleteBuilder(private val dsl: DeleteDSL) : } fun limit(limit: Long) { - dsl.limit(limit) + limitWhenPresent(limit) + } + + fun limitWhenPresent(limit: Long?) { + dsl.limitWhenPresent(limit) } override fun build(): DeleteModel = dsl.build() diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinGeneralInsertBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinGeneralInsertBuilder.kt index 152aace24..413ddcac4 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinGeneralInsertBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinGeneralInsertBuilder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -29,7 +29,7 @@ class KotlinGeneralInsertBuilder(private val table: SqlTable) : Buildable() - fun set(column: SqlColumn) = GeneralInsertColumnSetCompleter(column) { + fun set(column: SqlColumn) = GeneralInsertColumnSetCompleter(column) { columnMappings.add(it) } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertBuilder.kt index cb0730575..8b76231f7 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertBuilder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -33,7 +33,7 @@ class KotlinInsertBuilder (private val row: T): Buildable map(column: SqlColumn) = SingleRowInsertColumnMapCompleter(column) { + fun map(column: SqlColumn) = SingleRowInsertColumnMapCompleter(column) { columnMappings.add(it) } @@ -41,7 +41,7 @@ class KotlinInsertBuilder (private val row: T): Buildable()) { withRow(row) - withTable(table) + withTable(table!!) withColumnMappings(columnMappings) build() }.build() diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertColumnMapCompleters.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertColumnMapCompleters.kt index dd995e23c..076001b9e 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertColumnMapCompleters.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertColumnMapCompleters.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -28,7 +28,7 @@ import org.mybatis.dynamic.sql.util.ValueOrNullMapping import org.mybatis.dynamic.sql.util.ValueWhenPresentMapping @MyBatisDslMarker -sealed class AbstractInsertColumnMapCompleter( +sealed class AbstractInsertColumnMapCompleter( internal val column: SqlColumn, internal val mappingConsumer: (AbstractColumnMapping) -> Unit) { @@ -39,7 +39,7 @@ sealed class AbstractInsertColumnMapCompleter( infix fun toStringConstant(constant: String) = mappingConsumer.invoke(StringConstantMapping.of(column, constant)) } -class MultiRowInsertColumnMapCompleter( +class MultiRowInsertColumnMapCompleter( column: SqlColumn, mappingConsumer: (AbstractColumnMapping) -> Unit) : AbstractInsertColumnMapCompleter(column, mappingConsumer) { @@ -49,7 +49,7 @@ class MultiRowInsertColumnMapCompleter( fun toRow() = mappingConsumer.invoke(RowMapping.of(column)) } -class SingleRowInsertColumnMapCompleter( +class SingleRowInsertColumnMapCompleter( column: SqlColumn, mappingConsumer: (AbstractColumnMapping) -> Unit) : AbstractInsertColumnMapCompleter(column, mappingConsumer) { @@ -62,14 +62,14 @@ class SingleRowInsertColumnMapCompleter( fun toRow() = mappingConsumer.invoke(RowMapping.of(column)) } -class GeneralInsertColumnSetCompleter( +class GeneralInsertColumnSetCompleter( column: SqlColumn, mappingConsumer: (AbstractColumnMapping) -> Unit) : AbstractInsertColumnMapCompleter(column, mappingConsumer) { - infix fun toValue(value: T & Any) = toValue { value } + infix fun toValue(value: T) = toValue { value } - infix fun toValue(value: () -> T & Any) = mappingConsumer.invoke(ValueMapping.of(column, value)) + infix fun toValue(value: () -> T) = mappingConsumer.invoke(ValueMapping.of(column, value)) infix fun toValueOrNull(value: T?) = toValueOrNull { value } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiRowInsertBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiRowInsertBuilder.kt index e8049c4ef..b00a62aef 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiRowInsertBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiRowInsertBuilder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -33,7 +33,7 @@ class KotlinMultiRowInsertBuilder (private val rows: Collection): Bu this.table = table } - fun map(column: SqlColumn) = MultiRowInsertColumnMapCompleter(column) { + fun map(column: SqlColumn) = MultiRowInsertColumnMapCompleter(column) { columnMappings.add(it) } @@ -41,7 +41,7 @@ class KotlinMultiRowInsertBuilder (private val rows: Collection): Bu assertNotNull(table, "ERROR.26") //$NON-NLS-1$ return with(MultiRowInsertDSL.Builder()) { withRecords(rows) - withTable(table) + withTable(table!!) withColumnMappings(columnMappings) build() }.build() diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiSelectBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiSelectBuilder.kt index 440232397..618909cd2 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiSelectBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiSelectBuilder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -26,9 +26,9 @@ import org.mybatis.dynamic.sql.util.Buildable typealias MultiSelectCompleter = KotlinMultiSelectBuilder.() -> Unit @MyBatisDslMarker -class KotlinMultiSelectBuilder: Buildable { +class KotlinMultiSelectBuilder: Buildable, KotlinPagingDSL { private var dsl: MultiSelectDSL? = null - private set(value) { + set(value) { assertNull(field, "ERROR.33") //$NON-NLS-1$ field = value } @@ -63,16 +63,16 @@ class KotlinMultiSelectBuilder: Buildable { getDsl().orderBy(columns.asList()) } - fun limit(limit: Long) { - getDsl().limit(limit) + override fun limitWhenPresent(limit: Long?) { + getDsl().limitWhenPresent(limit) } - fun offset(offset: Long) { - getDsl().offset(offset) + override fun offsetWhenPresent(offset: Long?) { + getDsl().offsetWhenPresent(offset) } - fun fetchFirst(fetchFirstRows: Long) { - getDsl().fetchFirst(fetchFirstRows).rowsOnly() + override fun fetchFirstWhenPresent(fetchFirstRows: Long?) { + getDsl().fetchFirstWhenPresent(fetchFirstRows).rowsOnly() } fun configureStatement(c: StatementConfiguration.() -> Unit) { diff --git a/src/test/java/examples/schema_supplier/User.java b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinPagingDSL.kt similarity index 56% rename from src/test/java/examples/schema_supplier/User.java rename to src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinPagingDSL.kt index 02acc9c53..03dd8082f 100644 --- a/src/test/java/examples/schema_supplier/User.java +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinPagingDSL.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -13,25 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package examples.schema_supplier; +package org.mybatis.dynamic.sql.util.kotlin -public class User { - private int id; - private String name; - - public int getId() { - return id; +interface KotlinPagingDSL { + fun limit(limit: Long) { + limitWhenPresent(limit) } - public void setId(int id) { - this.id = id; - } + fun limitWhenPresent(limit: Long?) - public String getName() { - return name; + fun offset(offset: Long) { + offsetWhenPresent(offset) } - public void setName(String name) { - this.name = name; + fun offsetWhenPresent(offset: Long?) + + fun fetchFirst(fetchFirstRows: Long) { + fetchFirstWhenPresent(fetchFirstRows) } + + fun fetchFirstWhenPresent(fetchFirstRows: Long?) } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSelectBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSelectBuilder.kt index e62b5ef26..4c80f9d8c 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSelectBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSelectBuilder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -28,7 +28,7 @@ typealias SelectCompleter = KotlinSelectBuilder.() -> Unit @Suppress("TooManyFunctions") class KotlinSelectBuilder(private val fromGatherer: QueryExpressionDSL.FromGatherer) : - KotlinBaseJoiningBuilder>(), Buildable { + KotlinBaseJoiningBuilder>(), Buildable, KotlinPagingDSL { private var dsl: KQueryExpressionDSL? = null @@ -58,16 +58,16 @@ class KotlinSelectBuilder(private val fromGatherer: QueryExpressionDSL.FromGathe getDsl().orderBy(columns.toList()) } - fun limit(limit: Long) { - getDsl().limit(limit) + override fun limitWhenPresent(limit: Long?) { + getDsl().limitWhenPresent(limit) } - fun offset(offset: Long) { - getDsl().offset(offset) + override fun offsetWhenPresent(offset: Long?) { + getDsl().offsetWhenPresent(offset) } - fun fetchFirst(fetchFirstRows: Long) { - getDsl().fetchFirst(fetchFirstRows).rowsOnly() + override fun fetchFirstWhenPresent(fetchFirstRows: Long?) { + getDsl().fetchFirstWhenPresent(fetchFirstRows).rowsOnly() } fun union(union: KotlinUnionBuilder.() -> Unit): Unit = @@ -76,6 +76,30 @@ class KotlinSelectBuilder(private val fromGatherer: QueryExpressionDSL.FromGathe fun unionAll(unionAll: KotlinUnionBuilder.() -> Unit): Unit = unionAll(KotlinUnionBuilder(getDsl().unionAll())) + fun forUpdate() { + getDsl().forUpdate() + } + + fun forNoKeyUpdate() { + getDsl().forNoKeyUpdate() + } + + fun forShare() { + getDsl().forShare() + } + + fun forKeyShare() { + getDsl().forKeyShare() + } + + fun skipLocked() { + getDsl().skipLocked() + } + + fun nowait() { + getDsl().nowait() + } + override fun build(): SelectModel = getDsl().build() override fun getDsl(): KQueryExpressionDSL = invalidIfNull(dsl, "ERROR.27") //$NON-NLS-1$ diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSubQueryBuilders.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSubQueryBuilders.kt index cf5a12d8a..9439e4058 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSubQueryBuilders.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinSubQueryBuilders.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -84,11 +84,11 @@ class KotlinInsertSelectSubQueryBuilder : KotlinBaseSubQueryBuilder(), Buildable assertNotNull(table, "ERROR.29") //$NON-NLS-1$ val dsl = if (columnList == null) { - SqlBuilder.insertInto(table) + SqlBuilder.insertInto(table!!) .withSelectStatement { buildSelectModel() } } else { - SqlBuilder.insertInto(table) - .withColumnList(columnList) + SqlBuilder.insertInto(table!!) + .withColumnList(columnList!!) .withSelectStatement { buildSelectModel() } } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinUnionBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinUnionBuilder.kt index 817dba6e8..b403d675f 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinUnionBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinUnionBuilder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinUpdateBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinUpdateBuilder.kt index ef7eb6804..f83e45add 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinUpdateBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinUpdateBuilder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -27,14 +27,18 @@ typealias UpdateCompleter = KotlinUpdateBuilder.() -> Unit class KotlinUpdateBuilder(private val dsl: UpdateDSL) : KotlinBaseBuilder>(), Buildable { - fun set(column: SqlColumn): KotlinSetClauseFinisher = KotlinSetClauseFinisher(column) + fun set(column: SqlColumn): KotlinSetClauseFinisher = KotlinSetClauseFinisher(column) fun orderBy(vararg columns: SortSpecification) { dsl.orderBy(columns.toList()) } fun limit(limit: Long) { - dsl.limit(limit) + limitWhenPresent(limit) + } + + fun limitWhenPresent(limit: Long?) { + dsl.limitWhenPresent(limit) } override fun build(): UpdateModel = dsl.build() @@ -43,7 +47,7 @@ class KotlinUpdateBuilder(private val dsl: UpdateDSL) : @MyBatisDslMarker @Suppress("TooManyFunctions") - inner class KotlinSetClauseFinisher(private val column: SqlColumn) { + inner class KotlinSetClauseFinisher(private val column: SqlColumn) { fun equalToNull(): Unit = applyToDsl { set(column).equalToNull() @@ -59,9 +63,9 @@ class KotlinUpdateBuilder(private val dsl: UpdateDSL) : set(column).equalToStringConstant(constant) } - infix fun equalTo(value: T & Any): Unit = equalTo { value } + infix fun equalTo(value: T): Unit = equalTo { value } - infix fun equalTo(value: () -> T & Any): Unit = + infix fun equalTo(value: () -> T): Unit = applyToDsl { set(column).equalTo(value) } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/CaseDSLs.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/CaseDSLs.kt index 639a981fe..ce33f27b7 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/CaseDSLs.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/CaseDSLs.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -16,12 +16,13 @@ package org.mybatis.dynamic.sql.util.kotlin.elements import org.mybatis.dynamic.sql.BasicColumn -import org.mybatis.dynamic.sql.VisitableCondition +import org.mybatis.dynamic.sql.RenderableCondition import org.mybatis.dynamic.sql.select.caseexpression.BasicWhenCondition import org.mybatis.dynamic.sql.select.caseexpression.ConditionBasedWhenCondition import org.mybatis.dynamic.sql.select.caseexpression.SearchedCaseWhenCondition import org.mybatis.dynamic.sql.select.caseexpression.SimpleCaseWhenCondition import org.mybatis.dynamic.sql.util.kotlin.GroupingCriteriaCollector +import org.mybatis.dynamic.sql.util.kotlin.assertNotNull import org.mybatis.dynamic.sql.util.kotlin.assertNull class KSearchedCaseDSL : KElseDSL { @@ -34,9 +35,10 @@ class KSearchedCaseDSL : KElseDSL { fun `when`(dslCompleter: SearchedCaseCriteriaCollector.() -> Unit) = SearchedCaseCriteriaCollector().apply(dslCompleter).run { + assertNotNull(thenValue, "ERROR.47") //$NON-NLS-1$ whenConditions.add(SearchedCaseWhenCondition.Builder().withInitialCriterion(initialCriterion) .withSubCriteria(subCriteria) - .withThenValue(thenValue) + .withThenValue(thenValue!!) .build()) } @@ -65,7 +67,7 @@ class KSimpleCaseDSL : KElseDSL { } internal val whenConditions = mutableListOf>() - fun `when`(vararg conditions: VisitableCondition) = + fun `when`(vararg conditions: RenderableCondition) = SimpleCaseThenGatherer { whenConditions.add(ConditionBasedWhenCondition(conditions.asList(), it)) } fun `when`(vararg values: T) = diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/CastDSL.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/CastDSL.kt index cebf26ee9..836c61443 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/CastDSL.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/CastDSL.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/ColumnExtensions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/ColumnExtensions.kt index fc4e2a104..c3651d995 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/ColumnExtensions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/ColumnExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -17,20 +17,23 @@ package org.mybatis.dynamic.sql.util.kotlin.elements import org.mybatis.dynamic.sql.DerivedColumn import org.mybatis.dynamic.sql.SqlColumn +import org.mybatis.dynamic.sql.SubQueryColumn import org.mybatis.dynamic.sql.select.caseexpression.SearchedCaseModel import org.mybatis.dynamic.sql.select.caseexpression.SimpleCaseModel -infix fun DerivedColumn.`as`(alias: String): DerivedColumn = this.`as`(alias) +infix fun DerivedColumn.`as`(alias: String): DerivedColumn = this.`as`(alias) -infix fun SqlColumn.`as`(alias: String): SqlColumn = this.`as`(alias) +infix fun SqlColumn.`as`(alias: String): SqlColumn = this.`as`(alias) infix fun SearchedCaseModel.`as`(alias: String): SearchedCaseModel = this.`as`(alias) -infix fun SimpleCaseModel.`as`(alias: String): SimpleCaseModel = this.`as`(alias) +infix fun SimpleCaseModel.`as`(alias: String): SimpleCaseModel = this.`as`(alias) + +infix fun SubQueryColumn.`as`(alias: String): SubQueryColumn = this.`as`(alias) /** * Adds a qualifier to a column for use with table aliases (typically in joins or sub queries). * This is as close to natural SQL syntax as we can get in Kotlin. Natural SQL would look like * "qualifier.column". With this function we can say "qualifier(column)". */ -operator fun String.invoke(column: SqlColumn): SqlColumn = column.qualifiedWith(this) +operator fun String.invoke(column: SqlColumn): SqlColumn = column.qualifiedWith(this) diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt index 2d9f1e8a1..2002fc75a 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlElements.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -21,11 +21,12 @@ import org.mybatis.dynamic.sql.BasicColumn import org.mybatis.dynamic.sql.BindableColumn import org.mybatis.dynamic.sql.BoundValue import org.mybatis.dynamic.sql.Constant +import org.mybatis.dynamic.sql.RenderableCondition import org.mybatis.dynamic.sql.SortSpecification import org.mybatis.dynamic.sql.SqlBuilder import org.mybatis.dynamic.sql.SqlColumn import org.mybatis.dynamic.sql.StringConstant -import org.mybatis.dynamic.sql.VisitableCondition +import org.mybatis.dynamic.sql.SubQueryColumn import org.mybatis.dynamic.sql.select.caseexpression.SearchedCaseModel import org.mybatis.dynamic.sql.select.caseexpression.SimpleCaseModel import org.mybatis.dynamic.sql.select.aggregate.Avg @@ -51,14 +52,18 @@ import org.mybatis.dynamic.sql.util.kotlin.GroupingCriteriaReceiver import org.mybatis.dynamic.sql.util.kotlin.KotlinSubQueryBuilder import org.mybatis.dynamic.sql.util.kotlin.invalidIfNull import org.mybatis.dynamic.sql.where.condition.IsBetween +import org.mybatis.dynamic.sql.where.condition.IsBetweenWhenPresent import org.mybatis.dynamic.sql.where.condition.IsEqualTo import org.mybatis.dynamic.sql.where.condition.IsEqualToColumn +import org.mybatis.dynamic.sql.where.condition.IsEqualToWhenPresent import org.mybatis.dynamic.sql.where.condition.IsEqualToWithSubselect import org.mybatis.dynamic.sql.where.condition.IsGreaterThan import org.mybatis.dynamic.sql.where.condition.IsGreaterThanColumn import org.mybatis.dynamic.sql.where.condition.IsGreaterThanOrEqualTo import org.mybatis.dynamic.sql.where.condition.IsGreaterThanOrEqualToColumn +import org.mybatis.dynamic.sql.where.condition.IsGreaterThanOrEqualToWhenPresent import org.mybatis.dynamic.sql.where.condition.IsGreaterThanOrEqualToWithSubselect +import org.mybatis.dynamic.sql.where.condition.IsGreaterThanWhenPresent import org.mybatis.dynamic.sql.where.condition.IsGreaterThanWithSubselect import org.mybatis.dynamic.sql.where.condition.IsIn import org.mybatis.dynamic.sql.where.condition.IsInCaseInsensitive @@ -69,13 +74,19 @@ import org.mybatis.dynamic.sql.where.condition.IsLessThan import org.mybatis.dynamic.sql.where.condition.IsLessThanColumn import org.mybatis.dynamic.sql.where.condition.IsLessThanOrEqualTo import org.mybatis.dynamic.sql.where.condition.IsLessThanOrEqualToColumn +import org.mybatis.dynamic.sql.where.condition.IsLessThanOrEqualToWhenPresent import org.mybatis.dynamic.sql.where.condition.IsLessThanOrEqualToWithSubselect +import org.mybatis.dynamic.sql.where.condition.IsLessThanWhenPresent import org.mybatis.dynamic.sql.where.condition.IsLessThanWithSubselect import org.mybatis.dynamic.sql.where.condition.IsLike import org.mybatis.dynamic.sql.where.condition.IsLikeCaseInsensitive +import org.mybatis.dynamic.sql.where.condition.IsLikeCaseInsensitiveWhenPresent +import org.mybatis.dynamic.sql.where.condition.IsLikeWhenPresent import org.mybatis.dynamic.sql.where.condition.IsNotBetween +import org.mybatis.dynamic.sql.where.condition.IsNotBetweenWhenPresent import org.mybatis.dynamic.sql.where.condition.IsNotEqualTo import org.mybatis.dynamic.sql.where.condition.IsNotEqualToColumn +import org.mybatis.dynamic.sql.where.condition.IsNotEqualToWhenPresent import org.mybatis.dynamic.sql.where.condition.IsNotEqualToWithSubselect import org.mybatis.dynamic.sql.where.condition.IsNotIn import org.mybatis.dynamic.sql.where.condition.IsNotInCaseInsensitive @@ -84,6 +95,8 @@ import org.mybatis.dynamic.sql.where.condition.IsNotInWhenPresent import org.mybatis.dynamic.sql.where.condition.IsNotInWithSubselect import org.mybatis.dynamic.sql.where.condition.IsNotLike import org.mybatis.dynamic.sql.where.condition.IsNotLikeCaseInsensitive +import org.mybatis.dynamic.sql.where.condition.IsNotLikeCaseInsensitiveWhenPresent +import org.mybatis.dynamic.sql.where.condition.IsNotLikeWhenPresent import org.mybatis.dynamic.sql.where.condition.IsNotNull import org.mybatis.dynamic.sql.where.condition.IsNull @@ -129,43 +142,48 @@ fun count(column: BasicColumn): Count = SqlBuilder.count(column) fun countDistinct(column: BasicColumn): CountDistinct = SqlBuilder.countDistinct(column) -fun max(column: BindableColumn): Max = SqlBuilder.max(column) +fun subQuery(subQuery: KotlinSubQueryBuilder.() -> Unit): SubQueryColumn = + SubQueryColumn.of(KotlinSubQueryBuilder().apply(subQuery).build()) -fun min(column: BindableColumn): Min = SqlBuilder.min(column) +fun max(column: BindableColumn): Max = SqlBuilder.max(column) -fun avg(column: BindableColumn): Avg = SqlBuilder.avg(column) +fun min(column: BindableColumn): Min = SqlBuilder.min(column) -fun sum(column: BindableColumn): Sum = SqlBuilder.sum(column) +fun avg(column: BindableColumn): Avg = SqlBuilder.avg(column) -fun sum(column: BindableColumn, condition: VisitableCondition): Sum = SqlBuilder.sum(column, condition) +fun sum(column: BindableColumn): Sum = SqlBuilder.sum(column) + +fun sum(column: BasicColumn): Sum<*> = SqlBuilder.sum(column) + +fun sum(column: BindableColumn, condition: RenderableCondition): Sum = SqlBuilder.sum(column, condition) // constants -fun constant(constant: String): Constant = SqlBuilder.constant(constant) +fun constant(constant: String): Constant = SqlBuilder.constant(constant) fun stringConstant(constant: String): StringConstant = SqlBuilder.stringConstant(constant) -fun value(value: T): BoundValue = SqlBuilder.value(value) +fun value(value: T): BoundValue = SqlBuilder.value(value) // functions -fun add( +fun add( firstColumn: BindableColumn, secondColumn: BasicColumn, vararg subsequentColumns: BasicColumn ): Add = Add.of(firstColumn, secondColumn, subsequentColumns.asList()) -fun divide( +fun divide( firstColumn: BindableColumn, secondColumn: BasicColumn, vararg subsequentColumns: BasicColumn ): Divide = Divide.of(firstColumn, secondColumn, subsequentColumns.asList()) -fun multiply( +fun multiply( firstColumn: BindableColumn, secondColumn: BasicColumn, vararg subsequentColumns: BasicColumn ): Multiply = Multiply.of(firstColumn, secondColumn, subsequentColumns.asList()) -fun subtract( +fun subtract( firstColumn: BindableColumn, secondColumn: BasicColumn, vararg subsequentColumns: BasicColumn @@ -174,147 +192,149 @@ fun subtract( fun cast(receiver: CastDSL.() -> Unit): Cast = invalidIfNull(CastDSL().apply(receiver).cast, "ERROR.43") -fun concat( +fun concat( firstColumn: BindableColumn, vararg subsequentColumns: BasicColumn ): Concat = Concat.of(firstColumn, subsequentColumns.asList()) -fun concatenate( +fun concatenate( firstColumn: BindableColumn, secondColumn: BasicColumn, vararg subsequentColumns: BasicColumn ): Concatenate = Concatenate.of(firstColumn, secondColumn, subsequentColumns.asList()) -fun applyOperator( +fun applyOperator( operator: String, firstColumn: BindableColumn, secondColumn: BasicColumn, vararg subsequentColumns: BasicColumn ): OperatorFunction = OperatorFunction.of(operator, firstColumn, secondColumn, subsequentColumns.asList()) -fun lower(column: BindableColumn): Lower = SqlBuilder.lower(column) +fun lower(column: BindableColumn): Lower = SqlBuilder.lower(column) -fun substring( +fun substring( column: BindableColumn, offset: Int, length: Int ): Substring = SqlBuilder.substring(column, offset, length) -fun upper(column: BindableColumn): Upper = SqlBuilder.upper(column) +fun upper(column: BindableColumn): Upper = SqlBuilder.upper(column) // conditions for all data types -fun isNull(): IsNull = SqlBuilder.isNull() +fun isNull(): IsNull = SqlBuilder.isNull() -fun isNotNull(): IsNotNull = SqlBuilder.isNotNull() +fun isNotNull(): IsNotNull = SqlBuilder.isNotNull() -fun isEqualTo(value: T & Any): IsEqualTo = SqlBuilder.isEqualTo(value) +fun isEqualTo(value: T): IsEqualTo = SqlBuilder.isEqualTo(value) -fun isEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit): IsEqualToWithSubselect = +fun isEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit): IsEqualToWithSubselect = SqlBuilder.isEqualTo(KotlinSubQueryBuilder().apply(subQuery)) -fun isEqualTo(column: BasicColumn): IsEqualToColumn = SqlBuilder.isEqualTo(column) +fun isEqualTo(column: BasicColumn): IsEqualToColumn = SqlBuilder.isEqualTo(column) -fun isEqualToWhenPresent(value: T?): IsEqualTo = SqlBuilder.isEqualToWhenPresent(value) +fun isEqualToWhenPresent(value: T?): IsEqualToWhenPresent = SqlBuilder.isEqualToWhenPresent(value) -fun isNotEqualTo(value: T & Any): IsNotEqualTo = SqlBuilder.isNotEqualTo(value) +fun isNotEqualTo(value: T): IsNotEqualTo = SqlBuilder.isNotEqualTo(value) -fun isNotEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit): IsNotEqualToWithSubselect = +fun isNotEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit): IsNotEqualToWithSubselect = SqlBuilder.isNotEqualTo(KotlinSubQueryBuilder().apply(subQuery)) -fun isNotEqualTo(column: BasicColumn): IsNotEqualToColumn = SqlBuilder.isNotEqualTo(column) +fun isNotEqualTo(column: BasicColumn): IsNotEqualToColumn = SqlBuilder.isNotEqualTo(column) -fun isNotEqualToWhenPresent(value: T?): IsNotEqualTo = SqlBuilder.isNotEqualToWhenPresent(value) +fun isNotEqualToWhenPresent(value: T?): IsNotEqualToWhenPresent = + SqlBuilder.isNotEqualToWhenPresent(value) -fun isGreaterThan(value: T & Any): IsGreaterThan = SqlBuilder.isGreaterThan(value) +fun isGreaterThan(value: T): IsGreaterThan = SqlBuilder.isGreaterThan(value) -fun isGreaterThan(subQuery: KotlinSubQueryBuilder.() -> Unit): IsGreaterThanWithSubselect = +fun isGreaterThan(subQuery: KotlinSubQueryBuilder.() -> Unit): IsGreaterThanWithSubselect = SqlBuilder.isGreaterThan(KotlinSubQueryBuilder().apply(subQuery)) -fun isGreaterThan(column: BasicColumn): IsGreaterThanColumn = SqlBuilder.isGreaterThan(column) +fun isGreaterThan(column: BasicColumn): IsGreaterThanColumn = SqlBuilder.isGreaterThan(column) -fun isGreaterThanWhenPresent(value: T?): IsGreaterThan = SqlBuilder.isGreaterThanWhenPresent(value) +fun isGreaterThanWhenPresent(value: T?): IsGreaterThanWhenPresent = + SqlBuilder.isGreaterThanWhenPresent(value) -fun isGreaterThanOrEqualTo(value: T & Any): IsGreaterThanOrEqualTo = SqlBuilder.isGreaterThanOrEqualTo(value) +fun isGreaterThanOrEqualTo(value: T): IsGreaterThanOrEqualTo = SqlBuilder.isGreaterThanOrEqualTo(value) -fun isGreaterThanOrEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit): IsGreaterThanOrEqualToWithSubselect = +fun isGreaterThanOrEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit): IsGreaterThanOrEqualToWithSubselect = SqlBuilder.isGreaterThanOrEqualTo(KotlinSubQueryBuilder().apply(subQuery)) -fun isGreaterThanOrEqualTo(column: BasicColumn): IsGreaterThanOrEqualToColumn = +fun isGreaterThanOrEqualTo(column: BasicColumn): IsGreaterThanOrEqualToColumn = SqlBuilder.isGreaterThanOrEqualTo(column) -fun isGreaterThanOrEqualToWhenPresent(value: T?): IsGreaterThanOrEqualTo = +fun isGreaterThanOrEqualToWhenPresent(value: T?): IsGreaterThanOrEqualToWhenPresent = SqlBuilder.isGreaterThanOrEqualToWhenPresent(value) -fun isLessThan(value: T & Any): IsLessThan = SqlBuilder.isLessThan(value) +fun isLessThan(value: T): IsLessThan = SqlBuilder.isLessThan(value) -fun isLessThan(subQuery: KotlinSubQueryBuilder.() -> Unit): IsLessThanWithSubselect = +fun isLessThan(subQuery: KotlinSubQueryBuilder.() -> Unit): IsLessThanWithSubselect = SqlBuilder.isLessThan(KotlinSubQueryBuilder().apply(subQuery)) -fun isLessThan(column: BasicColumn): IsLessThanColumn = SqlBuilder.isLessThan(column) +fun isLessThan(column: BasicColumn): IsLessThanColumn = SqlBuilder.isLessThan(column) -fun isLessThanWhenPresent(value: T?): IsLessThan = SqlBuilder.isLessThanWhenPresent(value) +fun isLessThanWhenPresent(value: T?): IsLessThanWhenPresent = SqlBuilder.isLessThanWhenPresent(value) -fun isLessThanOrEqualTo(value: T & Any): IsLessThanOrEqualTo = SqlBuilder.isLessThanOrEqualTo(value) +fun isLessThanOrEqualTo(value: T): IsLessThanOrEqualTo = SqlBuilder.isLessThanOrEqualTo(value) -fun isLessThanOrEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit): IsLessThanOrEqualToWithSubselect = +fun isLessThanOrEqualTo(subQuery: KotlinSubQueryBuilder.() -> Unit): IsLessThanOrEqualToWithSubselect = SqlBuilder.isLessThanOrEqualTo(KotlinSubQueryBuilder().apply(subQuery)) -fun isLessThanOrEqualTo(column: BasicColumn): IsLessThanOrEqualToColumn = SqlBuilder.isLessThanOrEqualTo(column) +fun isLessThanOrEqualTo(column: BasicColumn): IsLessThanOrEqualToColumn = SqlBuilder.isLessThanOrEqualTo(column) -fun isLessThanOrEqualToWhenPresent(value: T?): IsLessThanOrEqualTo = +fun isLessThanOrEqualToWhenPresent(value: T?): IsLessThanOrEqualToWhenPresent = SqlBuilder.isLessThanOrEqualToWhenPresent(value) -fun isIn(vararg values: T & Any): IsIn = isIn(values.asList()) +fun isIn(vararg values: T): IsIn = isIn(values.asList()) @JvmName("isInArray") -fun isIn(values: Array): IsIn = SqlBuilder.isIn(values.asList()) +fun isIn(values: Array): IsIn = SqlBuilder.isIn(values.asList()) -fun isIn(values: Collection): IsIn = SqlBuilder.isIn(values) +fun isIn(values: Collection): IsIn = SqlBuilder.isIn(values) -fun isIn(subQuery: KotlinSubQueryBuilder.() -> Unit): IsInWithSubselect = +fun isIn(subQuery: KotlinSubQueryBuilder.() -> Unit): IsInWithSubselect = SqlBuilder.isIn(KotlinSubQueryBuilder().apply(subQuery)) -fun isInWhenPresent(vararg values: T?): IsInWhenPresent = isInWhenPresent(values.asList()) +fun isInWhenPresent(vararg values: T?): IsInWhenPresent = isInWhenPresent(values.asList()) @JvmName("isInArrayWhenPresent") -fun isInWhenPresent(values: Array?): IsInWhenPresent = SqlBuilder.isInWhenPresent(values?.asList()) +fun isInWhenPresent(values: Array?): IsInWhenPresent = SqlBuilder.isInWhenPresent(values?.asList()) -fun isInWhenPresent(values: Collection?): IsInWhenPresent = SqlBuilder.isInWhenPresent(values) +fun isInWhenPresent(values: Collection?): IsInWhenPresent = SqlBuilder.isInWhenPresent(values) -fun isNotIn(vararg values: T & Any): IsNotIn = isNotIn(values.asList()) +fun isNotIn(vararg values: T): IsNotIn = isNotIn(values.asList()) @JvmName("isNotInArray") -fun isNotIn(values: Array): IsNotIn = SqlBuilder.isNotIn(values.asList()) +fun isNotIn(values: Array): IsNotIn = SqlBuilder.isNotIn(values.asList()) -fun isNotIn(values: Collection): IsNotIn = SqlBuilder.isNotIn(values) +fun isNotIn(values: Collection): IsNotIn = SqlBuilder.isNotIn(values) -fun isNotIn(subQuery: KotlinSubQueryBuilder.() -> Unit): IsNotInWithSubselect = +fun isNotIn(subQuery: KotlinSubQueryBuilder.() -> Unit): IsNotInWithSubselect = SqlBuilder.isNotIn(KotlinSubQueryBuilder().apply(subQuery)) -fun isNotInWhenPresent(vararg values: T?): IsNotInWhenPresent = isNotInWhenPresent(values.asList()) +fun isNotInWhenPresent(vararg values: T?): IsNotInWhenPresent = isNotInWhenPresent(values.asList()) @JvmName("isNotInArrayWhenPresent") -fun isNotInWhenPresent(values: Array?): IsNotInWhenPresent = SqlBuilder.isNotInWhenPresent(values?.asList()) +fun isNotInWhenPresent(values: Array?): IsNotInWhenPresent = SqlBuilder.isNotInWhenPresent(values?.asList()) -fun isNotInWhenPresent(values: Collection?): IsNotInWhenPresent = SqlBuilder.isNotInWhenPresent(values) +fun isNotInWhenPresent(values: Collection?): IsNotInWhenPresent = SqlBuilder.isNotInWhenPresent(values) -fun isBetween(value1: T & Any): BetweenBuilder = BetweenBuilder(value1) +fun isBetween(value1: T): BetweenBuilder = BetweenBuilder(value1) -fun isBetweenWhenPresent(value1: T?): BetweenWhenPresentBuilder = BetweenWhenPresentBuilder(value1) +fun isBetweenWhenPresent(value1: T?): BetweenWhenPresentBuilder = BetweenWhenPresentBuilder(value1) -fun isNotBetween(value1: T & Any): NotBetweenBuilder = NotBetweenBuilder(value1) +fun isNotBetween(value1: T): NotBetweenBuilder = NotBetweenBuilder(value1) -fun isNotBetweenWhenPresent(value1: T?): NotBetweenWhenPresentBuilder = +fun isNotBetweenWhenPresent(value1: T?): NotBetweenWhenPresentBuilder = NotBetweenWhenPresentBuilder(value1) // for string columns, but generic for columns with type handlers -fun isLike(value: T & Any): IsLike = SqlBuilder.isLike(value) +fun isLike(value: T): IsLike = SqlBuilder.isLike(value) -fun isLikeWhenPresent(value: T?): IsLike = SqlBuilder.isLikeWhenPresent(value) +fun isLikeWhenPresent(value: T?): IsLikeWhenPresent = SqlBuilder.isLikeWhenPresent(value) -fun isNotLike(value: T & Any): IsNotLike = SqlBuilder.isNotLike(value) +fun isNotLike(value: T): IsNotLike = SqlBuilder.isNotLike(value) -fun isNotLikeWhenPresent(value: T?): IsNotLike = SqlBuilder.isNotLikeWhenPresent(value) +fun isNotLikeWhenPresent(value: T?): IsNotLikeWhenPresent = SqlBuilder.isNotLikeWhenPresent(value) // shortcuts for booleans fun isTrue(): IsEqualTo = isEqualTo(true) @@ -322,50 +342,53 @@ fun isTrue(): IsEqualTo = isEqualTo(true) fun isFalse(): IsEqualTo = isEqualTo(false) // conditions for strings only -fun isLikeCaseInsensitive(value: String): IsLikeCaseInsensitive = SqlBuilder.isLikeCaseInsensitive(value) +fun isLikeCaseInsensitive(value: String): IsLikeCaseInsensitive = SqlBuilder.isLikeCaseInsensitive(value) -fun isLikeCaseInsensitiveWhenPresent(value: String?): IsLikeCaseInsensitive = +fun isLikeCaseInsensitiveWhenPresent(value: String?): IsLikeCaseInsensitiveWhenPresent = SqlBuilder.isLikeCaseInsensitiveWhenPresent(value) -fun isNotLikeCaseInsensitive(value: String): IsNotLikeCaseInsensitive = SqlBuilder.isNotLikeCaseInsensitive(value) +fun isNotLikeCaseInsensitive(value: String): IsNotLikeCaseInsensitive = SqlBuilder.isNotLikeCaseInsensitive(value) -fun isNotLikeCaseInsensitiveWhenPresent(value: String?): IsNotLikeCaseInsensitive = +fun isNotLikeCaseInsensitiveWhenPresent(value: String?): IsNotLikeCaseInsensitiveWhenPresent = SqlBuilder.isNotLikeCaseInsensitiveWhenPresent(value) -fun isInCaseInsensitive(vararg values: String): IsInCaseInsensitive = isInCaseInsensitive(values.asList()) +fun isInCaseInsensitive(vararg values: String): IsInCaseInsensitive = isInCaseInsensitive(values.asList()) @JvmName("isInArrayCaseInsensitive") -fun isInCaseInsensitive(values: Array): IsInCaseInsensitive = SqlBuilder.isInCaseInsensitive(values.asList()) +fun isInCaseInsensitive(values: Array): IsInCaseInsensitive = + SqlBuilder.isInCaseInsensitive(values.asList()) -fun isInCaseInsensitive(values: Collection): IsInCaseInsensitive = SqlBuilder.isInCaseInsensitive(values) +fun isInCaseInsensitive(values: Collection): IsInCaseInsensitive = + SqlBuilder.isInCaseInsensitive(values) -fun isInCaseInsensitiveWhenPresent(vararg values: String?): IsInCaseInsensitiveWhenPresent = +fun isInCaseInsensitiveWhenPresent(vararg values: String?): IsInCaseInsensitiveWhenPresent = isInCaseInsensitiveWhenPresent(values.asList()) @JvmName("isInArrayCaseInsensitiveWhenPresent") -fun isInCaseInsensitiveWhenPresent(values: Array?): IsInCaseInsensitiveWhenPresent = +fun isInCaseInsensitiveWhenPresent(values: Array?): IsInCaseInsensitiveWhenPresent = SqlBuilder.isInCaseInsensitiveWhenPresent(values?.asList()) -fun isInCaseInsensitiveWhenPresent(values: Collection?): IsInCaseInsensitiveWhenPresent = +fun isInCaseInsensitiveWhenPresent(values: Collection?): IsInCaseInsensitiveWhenPresent = SqlBuilder.isInCaseInsensitiveWhenPresent(values) -fun isNotInCaseInsensitive(vararg values: String): IsNotInCaseInsensitive = isNotInCaseInsensitive(values.asList()) +fun isNotInCaseInsensitive(vararg values: String): IsNotInCaseInsensitive = + isNotInCaseInsensitive(values.asList()) @JvmName("isNotInArrayCaseInsensitive") -fun isNotInCaseInsensitive(values: Array): IsNotInCaseInsensitive = +fun isNotInCaseInsensitive(values: Array): IsNotInCaseInsensitive = SqlBuilder.isNotInCaseInsensitive(values.asList()) -fun isNotInCaseInsensitive(values: Collection): IsNotInCaseInsensitive = +fun isNotInCaseInsensitive(values: Collection): IsNotInCaseInsensitive = SqlBuilder.isNotInCaseInsensitive(values) -fun isNotInCaseInsensitiveWhenPresent(vararg values: String?): IsNotInCaseInsensitiveWhenPresent = +fun isNotInCaseInsensitiveWhenPresent(vararg values: String?): IsNotInCaseInsensitiveWhenPresent = isNotInCaseInsensitiveWhenPresent(values.asList()) @JvmName("isNotInArrayCaseInsensitiveWhenPresent") -fun isNotInCaseInsensitiveWhenPresent(values: Array?): IsNotInCaseInsensitiveWhenPresent = +fun isNotInCaseInsensitiveWhenPresent(values: Array?): IsNotInCaseInsensitiveWhenPresent = SqlBuilder.isNotInCaseInsensitiveWhenPresent(values?.asList()) -fun isNotInCaseInsensitiveWhenPresent(values: Collection?): IsNotInCaseInsensitiveWhenPresent = +fun isNotInCaseInsensitiveWhenPresent(values: Collection?): IsNotInCaseInsensitiveWhenPresent = SqlBuilder.isNotInCaseInsensitiveWhenPresent(values) // order by support @@ -390,22 +413,22 @@ fun sortColumn(name: String): SortSpecification = SqlBuilder.sortColumn(name) fun sortColumn(tableAlias: String, column: SqlColumn<*>): SortSpecification = SqlBuilder.sortColumn(tableAlias, column) // DSL Support Classes -class BetweenBuilder(private val value1: T) { +class BetweenBuilder(private val value1: T) { fun and(value2: T): IsBetween = SqlBuilder.isBetween(value1).and(value2) } -class BetweenWhenPresentBuilder(private val value1: T?) { - fun and(value2: T?): IsBetween { +class BetweenWhenPresentBuilder(private val value1: T?) { + fun and(value2: T?): IsBetweenWhenPresent { return SqlBuilder.isBetweenWhenPresent(value1).and(value2) } } -class NotBetweenBuilder(private val value1: T) { +class NotBetweenBuilder(private val value1: T) { fun and(value2: T): IsNotBetween = SqlBuilder.isNotBetween(value1).and(value2) } -class NotBetweenWhenPresentBuilder(private val value1: T?) { - fun and(value2: T?): IsNotBetween { +class NotBetweenWhenPresentBuilder(private val value1: T?) { + fun and(value2: T?): IsNotBetweenWhenPresent { return SqlBuilder.isNotBetweenWhenPresent(value1).and(value2) } } diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlTableExtensions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlTableExtensions.kt index a59b74874..bbd79dcd7 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlTableExtensions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlTableExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/model/ModelBuilderFunctions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/model/ModelBuilderFunctions.kt index 1dd687443..bd25cfab6 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/model/ModelBuilderFunctions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/model/ModelBuilderFunctions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/MapperSupportFunctions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/MapperSupportFunctions.kt index 42386e013..e43ed38ee 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/MapperSupportFunctions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/MapperSupportFunctions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/ProviderBuilderFunctions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/ProviderBuilderFunctions.kt index 49a1133fd..8324597bc 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/ProviderBuilderFunctions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/ProviderBuilderFunctions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/spring/NamedParameterJdbcTemplateExtensions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/spring/NamedParameterJdbcTemplateExtensions.kt index 881c3d9d0..6cfd88151 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/spring/NamedParameterJdbcTemplateExtensions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/spring/NamedParameterJdbcTemplateExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. @@ -65,7 +65,7 @@ fun NamedParameterJdbcTemplate.deleteFrom(table: SqlTable, completer: DeleteComp delete(org.mybatis.dynamic.sql.util.kotlin.spring.deleteFrom(table, completer)) // batch insert -fun NamedParameterJdbcTemplate.insertBatch(insertStatement: BatchInsert): IntArray = +fun NamedParameterJdbcTemplate.insertBatch(insertStatement: BatchInsert): IntArray = batchUpdate(insertStatement.insertStatementSQL, BatchInsertUtility.createBatch(insertStatement.records)) fun NamedParameterJdbcTemplate.insertBatch( @@ -81,10 +81,10 @@ fun NamedParameterJdbcTemplate.insertBatch( insertBatch(org.mybatis.dynamic.sql.util.kotlin.spring.insertBatch(records, completer)) // single row insert -fun NamedParameterJdbcTemplate.insert(insertStatement: InsertStatementProvider): Int = +fun NamedParameterJdbcTemplate.insert(insertStatement: InsertStatementProvider): Int = update(insertStatement.insertStatement, BeanPropertySqlParameterSource(insertStatement)) -fun NamedParameterJdbcTemplate.insert( +fun NamedParameterJdbcTemplate.insert( insertStatement: InsertStatementProvider, keyHolder: KeyHolder ): Int = @@ -119,10 +119,10 @@ fun NamedParameterJdbcTemplate.insertMultiple( ): Int = insertMultiple(org.mybatis.dynamic.sql.util.kotlin.spring.insertMultiple(records, completer)) -fun NamedParameterJdbcTemplate.insertMultiple(insertStatement: MultiRowInsertStatementProvider): Int = +fun NamedParameterJdbcTemplate.insertMultiple(insertStatement: MultiRowInsertStatementProvider): Int = update(insertStatement.insertStatement, BeanPropertySqlParameterSource(insertStatement)) -fun NamedParameterJdbcTemplate.insertMultiple( +fun NamedParameterJdbcTemplate.insertMultiple( insertStatement: MultiRowInsertStatementProvider, keyHolder: KeyHolder ): Int = diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/spring/ProviderBuilderFunctions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/spring/ProviderBuilderFunctions.kt index 30749c0de..895bbaa20 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/spring/ProviderBuilderFunctions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/spring/ProviderBuilderFunctions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/main/resources/org/mybatis/dynamic/sql/util/messages.properties b/src/main/resources/org/mybatis/dynamic/sql/util/messages.properties index 9927dbeec..3cf560a78 100644 --- a/src/main/resources/org/mybatis/dynamic/sql/util/messages.properties +++ b/src/main/resources/org/mybatis/dynamic/sql/util/messages.properties @@ -1,5 +1,5 @@ # -# Copyright 2016-2024 the original author or authors. +# 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. @@ -52,7 +52,7 @@ ERROR.33=Calling "select" or "selectDistinct" more than once is not allowed. Add union or unionAll expression ERROR.34=You must specify "select" or "selectDistinct" before any other clauses in a multi-select statement ERROR.35=Multi-select statements must have at least one "union" or "union all" expression -ERROR.36=You must either implement the "render" or "renderWithTableAlias" method in a column or function +ERROR.36=Obsolete Message - Not Used ERROR.37=The "{0}" function does not support conditions that fail to render ERROR.38=Bound values cannot be aliased ERROR.39=When clauses in case expressions must render @@ -60,4 +60,11 @@ ERROR.40=Case expressions must have at least one "when" clause ERROR.41=You cannot call "then" in a Kotlin case expression more than once ERROR.42=You cannot call `else` in a Kotlin case expression more than once ERROR.43=A Kotlin cast expression must have one, and only one, `as` element +ERROR.44={0} conditions must contain at least one value +ERROR.45=You cannot call "on" in a Kotlin join expression more than once +ERROR.46=At least one join criterion must render +ERROR.47=A Kotlin case statement must specify a "then" clause for every "when" clause +ERROR.48=You cannot call more than one of "forUpdate", "forNoKeyUpdate", "forShare", or "forKeyShare" in a select \ + statement +ERROR.49=You cannot call more than one of "skipLocked", or "nowait" in a select statement INTERNAL.ERROR=Internal Error {0} diff --git a/src/site/markdown/docs/caseExpressions.md b/src/site/markdown/docs/caseExpressions.md index 9b83d4f3e..3f711e626 100644 --- a/src/site/markdown/docs/caseExpressions.md +++ b/src/site/markdown/docs/caseExpressions.md @@ -3,9 +3,9 @@ Support for case expressions was added in version 1.5.1. For information about case expressions in the Kotlin DSL, see the [Kotlin Case Expressions](kotlinCaseExpressions.md) page. -## Case Statements in SQL +## Case Expressions in SQL The library supports different types of case expressions - a "simple" case expression, and a "searched" case -expressions. +expressions. Case expressions can be used in many places including select lists, order by phrases, etc. A simple case expression checks the values of a single column. It looks like this: diff --git a/src/site/markdown/docs/conditions.md b/src/site/markdown/docs/conditions.md index d5683f206..438249400 100644 --- a/src/site/markdown/docs/conditions.md +++ b/src/site/markdown/docs/conditions.md @@ -166,8 +166,8 @@ table lists the optional conditions and shows how to use them: | Null | where(id, isNull().filter(BooleanSupplier) | The condition will render if BooleanSupplier.getAsBoolean() returns true | ### "When Present" Condition Builders -The library supplies several methods that supply conditions to be used in the common case of checking for null -values. The table below lists the rendering rules for each of these "when present" condition builder methods. +The library supplies conditions for use in the common case of checking for null +values. The table below lists the rendering rules for each of these "when present" conditions. | Condition | Example | Rendering Rules | |---------------------------|---------------------------------------------------|---------------------------------------------------------------| @@ -184,14 +184,20 @@ values. The table below lists the rendering rules for each of these "when presen | Not Like | where(id, isNotLikeWhenPresent(x)) | The condition will render if x is non-null | | Not Like Case Insensitive | where(id, isNotLikeCaseInsensitiveWhenPresent(x)) | The condition will render if x is non-null | -Note that these methods simply apply a "NotNull" filter to a condition. For example: +With our adoption of JSpecify, it is now considered a misuse of the library to pass a null value into a condition +unless the condition is one of the "when present" conditions. If you previously wrote code like this: ```java -// the following two lines are functionally equivalent -... where (id, isEqualToWhenPresent(x)) ... ... where (id, isEqualTo(x).filter(Objects::nonNull)) ... ``` +Starting in version 2.0.0 of the library, you will now see IDE warnings related to nullability. You should change it +to this: + +```java +... where (id, isEqualToWhenPresent(x)) ... +``` + ### Optionality with the "In" Conditions Optionality with the "in" and "not in" conditions is a bit more complex than the other types of conditions. The rules are different for the base conditions ("isIn", "isNotIn", etc.) and the "when present" conditions ("isInWhenPresent", @@ -204,8 +210,8 @@ mapping if you so desire. Starting with version 1.5.2, we made a change to the rendering rules for the "in" conditions. This was done to limit the danger of conditions failing to render and thus affecting more rows than expected. For the base conditions ("isIn", -"isNotIn", etc.), if the list of values is empty, then the condition will still render, but the resulting SQL will -be invalid and will cause a runtime exception. We believe this is the safest outcome. For example, suppose +"isNotIn", etc.), if the list of values is empty, then the library will throw +`org.mybatis.dynamic.sql.exception.InvalidSqlException`. We believe this is the safest outcome. For example, suppose a DELETE statement was coded as follows: ```java @@ -214,12 +220,6 @@ a DELETE statement was coded as follows: .and(id, isIn(Collections.emptyList())); ``` -This statement will be rendered as follows: - -```sql - delete from foo where status = ? and id in () -``` - This will cause a runtime error due to invalid SQL, but it eliminates the possibility of deleting ALL rows with active status. If you want to allow the "in" condition to drop from the SQL if the list is empty, then use the "inWhenPresent" condition. @@ -229,8 +229,8 @@ and the case-insensitive versions of these conditions: | Input | Effect | |------------------------------------------|-----------------------------------------------------------------------------------| -| isIn(null) | NullPointerException | -| isIn(Collections.emptyList()) | Rendered as "in ()" (Invalid SQL) | +| isIn(null) | NullPointerException thrown | +| isIn(Collections.emptyList()) | InvalidSqlException thrown | | isIn(2, 3, null) | Rendered as "in (?, ?, ?)" (Parameter values are 2, 3, and null) | | isInWhenPresent(null) | Condition Not Rendered | | isInWhenPresent(Collections.emptyList()) | Condition Not Rendered | @@ -267,7 +267,7 @@ any null or blank string, and you want to trim all strings. This can be accompli .where(animalName, isIn(" Mouse", " ", null, "", "Musk shrew ") .filter(Objects::nonNull) .map(String::trim) - .filter(st -> !st.isEmpty())) + .filter(not(String::isEmpty))) .orderBy(id) .build() .render(RenderingStrategies.MYBATIS3); @@ -284,7 +284,7 @@ public class MyInCondition { return SqlBuilder.isIn(values) .filter(Objects::nonNull) .map(String::trim) - .filter(st -> !st.isEmpty()); + .filter(not(String::isEmpty)); } } ``` diff --git a/src/site/markdown/docs/extending.md b/src/site/markdown/docs/extending.md index ae04f434a..a1fee9adb 100644 --- a/src/site/markdown/docs/extending.md +++ b/src/site/markdown/docs/extending.md @@ -11,10 +11,10 @@ The SELECT support is the most complex part of the library, and also the part of extended. There are two main interfaces involved with extending the SELECT support. Picking which interface to implement is dependent on how you want to use your extension. -| Interface | Purpose | -|------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------| -| `org.mybatis.dynamic.sql.BasicColumn` | Use this interface if you want to add capabilities to a SELECT list or a GROUP BY expression. For example, using a database function. | -| `org.mybatis.dynamic.sql.BindableColumn` | Use this interface if you want to add capabilities to a WHERE clause. For example, creating a custom condition. | +| Interface | Purpose | +|------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `org.mybatis.dynamic.sql.BasicColumn` | Use this interface if you want to add capabilities to a SELECT list, a GROUP BY, or an ORDER BY expression. For example, using a database function. | +| `org.mybatis.dynamic.sql.BindableColumn` | Use this interface if you want to add capabilities to a WHERE clause in addition to the capabilities of `BasicColumn`. For example, creating a custom condition. | Rendering is the process of generating an appropriate SQL fragment to implement the function or calculated column. The library will call a method `render(RenderingContext)` in your implementation. This method should return an @@ -101,6 +101,7 @@ the function changes the data type from `byte[]` to `String`. import java.sql.JDBCType; import java.util.Optional; +import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.select.function.AbstractTypeConvertingFunction; @@ -108,7 +109,7 @@ import org.mybatis.dynamic.sql.util.FragmentAndParameters; public class ToBase64 extends AbstractTypeConvertingFunction { - protected ToBase64(BindableColumn column) { + private ToBase64(BasicColumn column) { super(column); } @@ -143,13 +144,15 @@ public class ToBase64 extends AbstractTypeConvertingFunction { - private Upper(BindableColumn column) { + private Upper(BasicColumn column) { super(column); } @@ -178,19 +181,21 @@ Note that `FragmentAndParameters` has a utility method that can simplify the imp add any new parameters to the resulting fragment. For example, the UPPER function can be simplified as follows: ```java +import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.render.RenderingContext; import org.mybatis.dynamic.sql.select.function.AbstractUniTypeFunction; import org.mybatis.dynamic.sql.util.FragmentAndParameters; public class Upper extends AbstractUniTypeFunction { - private Upper(BindableColumn column) { + private Upper(BasicColumn column) { super(column); } @Override public FragmentAndParameters render(RenderingContext renderingContext) { - return = column.render(renderingContext).mapFragment(f -> "upper(" + f + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + return column.render(renderingContext).mapFragment(f -> "upper(" + f + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } @Override @@ -211,9 +216,16 @@ The following function implements the concatenate operator. Note that the operat arbitrary length: ```java +import java.util.Arrays; +import java.util.List; + +import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.BindableColumn; +import org.mybatis.dynamic.sql.select.function.OperatorFunction; + public class Concatenate extends OperatorFunction { - protected Concatenate(BindableColumn firstColumn, BasicColumn secondColumn, + protected Concatenate(BasicColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { super("||", firstColumn, secondColumn, subsequentColumns); //$NON-NLS-1$ } @@ -280,3 +292,89 @@ it. You can write your own rendering support if you are dissatisfied with the S Writing a custom renderer is quite complex. If you want to undertake that task, we suggest that you take the time to understand how the default renderers work first. Feel free to ask questions about this topic on the MyBatis mailing list. + +## Writing Custom Conditions + +The library supplies a full range of conditions for all the common SQL operators (=, !=, like, between, etc.) Some +databases support extensions to the standard operators. For example, MySQL supports an extension to the "LIKE" +condition - the "ESCAPE" clause. If you need to implement a condition like that, then you will need to code a +custom condition. + +Here's an example of implementing a LIKE condition that supports ESCAPE: + +```java +@NullMarked +public class IsLikeEscape extends AbstractSingleValueCondition + implements AbstractSingleValueCondition.Filterable, AbstractSingleValueCondition.Mappable { + private static final IsLikeEscape EMPTY = new IsLikeEscape(-1, null) { + @Override + public Object value() { + throw new NoSuchElementException("No value present"); //$NON-NLS-1$ + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + public static IsLikeEscape empty() { + @SuppressWarnings("unchecked") + IsLikeEscape t = (IsLikeEscape) EMPTY; + return t; + } + + private final @Nullable Character escapeCharacter; + + protected IsLikeEscape(T value, @Nullable Character escapeCharacter) { + super(value); + this.escapeCharacter = escapeCharacter; + } + + @Override + public String operator() { + return "like"; + } + + @Override + public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn leftColumn) { + var fragment = super.renderCondition(renderingContext, leftColumn); + if (escapeCharacter != null) { + fragment = fragment.mapFragment(this::addEscape); + } + + return fragment; + } + + private String addEscape(String s) { + return s + " ESCAPE '" + escapeCharacter + "'"; + } + + @Override + public IsLikeEscape filter(Predicate predicate) { + return filterSupport(predicate, IsLikeEscape::empty, this); + } + + @Override + public IsLikeEscape map(Function mapper) { + return mapSupport(mapper, v -> new IsLikeEscape<>(v, escapeCharacter), IsLikeEscape::empty); + } + + public static IsLikeEscape isLike(T value) { + return new IsLikeEscape<>(value, null); + } + + public static IsLikeEscape isLike(T value, Character escapeCharacter) { + return new IsLikeEscape<>(value, escapeCharacter); + } +} +``` + +Important notes: + +1. The class extends `AbstractSingleValueCondition` - which is appropriate for like conditions +2. The class constructor accepts an escape character that will be rendered into an ESCAPE phrase +3. The class overrides `renderCondition` and changes the library generated `FragmentAndParameters` to add the ESCAPE + phrase. **This is the key to what's needed to implement a custom condition.** +4. The class implements `Filterable` and `Mappable` to provide `filter` and `map` functions as is expected for most + conditions in the library diff --git a/src/site/markdown/docs/kotlinCaseExpressions.md b/src/site/markdown/docs/kotlinCaseExpressions.md index 5366db40b..d0fdb4b4d 100644 --- a/src/site/markdown/docs/kotlinCaseExpressions.md +++ b/src/site/markdown/docs/kotlinCaseExpressions.md @@ -3,9 +3,9 @@ Support for case expressions was added in version 1.5.1. For information about case expressions in the Java DSL, see the [Java Case Expressions](caseExpressions.md) page. -## Case Statements in SQL +## Case Expressions in SQL The library supports different types of case expressions - a "simple" case expression, and a "searched" case -expressions. +expressions. Case expressions can be used in many places including select lists, order by phrases, etc. A simple case expression checks the values of a single column. It looks like this: diff --git a/src/site/markdown/docs/kotlinOverview.md b/src/site/markdown/docs/kotlinOverview.md index 5b646a7b0..78dba6a78 100644 --- a/src/site/markdown/docs/kotlinOverview.md +++ b/src/site/markdown/docs/kotlinOverview.md @@ -417,9 +417,9 @@ val selectStatement = select(orderMaster.orderId, orderMaster.orderDate, orderDe orderDetail.description, orderDetail.quantity ) { from(orderMaster, "om") - join(orderDetail, "od") { - on(orderMaster.orderId) equalTo orderDetail.orderId - and(orderMaster.orderId) equalTo orderDetail.orderId + join(orderDetail, "od") on { + orderMaster.orderId isEqualTo orderDetail.orderId + and { orderMaster.orderId isEqualTo orderDetail.orderId } } where { orderMaster.orderId isEqualTo 1 } or { @@ -433,8 +433,7 @@ val selectStatement = select(orderMaster.orderId, orderMaster.orderDate, orderDe In a select statement you must specify a table in a `from` clause. Everything else is optional. -Multiple join clauses can be specified if you need to join additional tables. In a join clause, you must -specify an `on` condition, and you may specify additional `and` conditions as necessary. Full, left, right, inner, +Multiple join clauses can be specified if you need to join additional tables. Full, left, right, inner, and outer joins are supported. Where clauses can be of arbitrary complexity and support all SQL operators including exists operators, subqueries, etc. diff --git a/src/site/markdown/docs/migratingV1toV2.md b/src/site/markdown/docs/migratingV1toV2.md new file mode 100644 index 000000000..12b6adc37 --- /dev/null +++ b/src/site/markdown/docs/migratingV1toV2.md @@ -0,0 +1,49 @@ +# V1 to V2 Migration Guide + +Version 2 of MyBatis Dynamic SQL introduced many new features. On this page we will provide examples for the more +significant changes - changes that are more substantial than following deprecation messages. + +## Kotlin Join Syntax + +The Java DSL for joins was changed to allow much more flexible joins. Of course, not all capabilities are supported in +all databases, but you should now be able to code most joins specification that are supported by your database. +The changes in the Java DSL are mostly internal and should not impact most users. The `equalTo` methods has been +deprecated in favor of `isEqualTo`, but all other changes should be hidden. + +Like the Java DSL, the V2 Kotlin DSL offers a fully flexible join specification and allows for much more flexible join +specifications. The changes in the Kotlin DSL allow a more natural expressions of a join specification. The main +difference is that the "on" keyword should be moved outside the join specification lambda (it is now an infix function). +Inside the lambda, the conditions should be rewritten to match the syntax of a where clause. + +V1 (Deprecated) Join Specification Example: +```kotlin +val selectStatement = select( + orderMaster.orderId, orderMaster.orderDate, + orderDetail.lineNumber, orderDetail.description, orderDetail.quantity +) { + from(orderMaster, "om") + join(orderDetail, "od") { + on(orderMaster.orderId) equalTo orderDetail.orderId + and(orderMaster.orderId) equalTo constant("1") + } +} +``` + +V2 Join Specification Example: +```kotlin +val selectStatement = select( + orderMaster.orderId, orderMaster.orderDate, + orderDetail.lineNumber, orderDetail.description, orderDetail.quantity +) { + from(orderMaster, "om") + join(orderDetail, "od") on { + orderMaster.orderId isEqualTo orderDetail.orderId + and { orderMaster.orderId isEqualTo constant("1") } + } +} +``` + +Notice that the "on" keyword has been moved outside the lambda, and the conditions are coded with the same syntax used +by WHERE, HAVING, and CASE expressions. + +The prior syntax is deprecated and will be removed in a future release. diff --git a/src/site/markdown/docs/select.md b/src/site/markdown/docs/select.md index 923c30c42..f73ef17dd 100644 --- a/src/site/markdown/docs/select.md +++ b/src/site/markdown/docs/select.md @@ -10,7 +10,7 @@ In general, the following are supported: 2. Tables can be aliased per select statement 3. Columns can be aliased per select statement 4. Some support for aggregates (avg, min, max, sum) -5. Equijoins of type INNER, LEFT OUTER, RIGHT OUTER, FULL OUTER +5. Joins of type INNER, LEFT OUTER, RIGHT OUTER, FULL OUTER 6. Subqueries in where clauses. For example, `where foo in (select foo from foos where id < 36)` 7. Select from another select. For example `select count(*) from (select foo from foos where id < 36)` 8. Multi-Selects. For example `(select * from foo order by id limit 3) union (select * from foo order by id desc limit 3)` @@ -21,47 +21,50 @@ At this time, the library does not support the following: 2. INTERSECT, EXCEPT, etc. The user guide page for WHERE Clauses shows examples of many types of SELECT statements with different complexities of -the WHERE clause including support for sub-queries. We will just show a single example here, including an ORDER BY clause: +the WHERE clause including support for sub-queries. We will just show a single example here, including an ORDER BY +clause: ```java - SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight) - .from(animalData) - .where(id, isIn(1, 5, 7)) - .and(bodyWeight, isBetween(1.0).and(3.0)) - .orderBy(id.descending(), bodyWeight) - .build() - .render(RenderingStrategies.MYBATIS3); - - List animals = mapper.selectMany(selectStatement); +SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight) + .from(animalData) + .where(id, isIn(1, 5, 7)) + .and(bodyWeight, isBetween(1.0).and(3.0)) + .orderBy(id.descending(), bodyWeight) + .build() + .render(RenderingStrategies.MYBATIS3); + +List animals = mapper.selectMany(selectStatement); ``` The WHERE and ORDER BY clauses are optional. ## Joins -The library supports the generation of equijoin statements - joins defined by column matching. For example: +The library supports the generation of join statements. For example: ```java - SelectStatementProvider selectStatement = select(orderMaster.orderId, orderDate, orderDetail.lineNumber, orderDetail.description, orderDetail.quantity) - .from(orderMaster, "om") - .join(orderDetail, "od").on(orderMaster.orderId, equalTo(orderDetail.orderId)) - .build() - .render(RenderingStrategies.MYBATIS3); +SelectStatementProvider selectStatement = select(orderMaster.orderId, orderDate, orderDetail.lineNumber, orderDetail.description, orderDetail.quantity) + .from(orderMaster, "om") + .join(orderDetail, "od").on(orderMaster.orderId, isEqualTo(orderDetail.orderId)) + .build() + .render(RenderingStrategies.MYBATIS3); ``` -Notice that you can give an alias to a table if desired. If you don't specify an alias, the full table name will be used in the generated SQL. +Notice that you can give an alias to a table if desired. If you don't specify an alias, the full table name will be +used in the generated SQL. Multiple tables can be joined in a single statement. For example: ```java - SelectStatementProvider selectStatement = select(orderMaster.orderId, orderDate, orderLine.lineNumber, itemMaster.description, orderLine.quantity) - .from(orderMaster, "om") - .join(orderLine, "ol").on(orderMaster.orderId, equalTo(orderLine.orderId)) - .join(itemMaster, "im").on(orderLine.itemId, equalTo(itemMaster.itemId)) - .where(orderMaster.orderId, isEqualTo(2)) - .build() - .render(RenderingStrategies.MYBATIS3); +SelectStatementProvider selectStatement = select(orderMaster.orderId, orderDate, orderLine.lineNumber, itemMaster.description, orderLine.quantity) + .from(orderMaster, "om") + .join(orderLine, "ol").on(orderMaster.orderId, isEqualTo(orderLine.orderId)) + .join(itemMaster, "im").on(orderLine.itemId, isEqualTo(itemMaster.itemId)) + .where(orderMaster.orderId, isEqualTo(2)) + .build() + .render(RenderingStrategies.MYBATIS3); ``` -Join queries will likely require you to define a MyBatis result mapping in XML. This is the only instance where XML is required. This is due to the limitations of the MyBatis annotations when mapping collections. +Join queries will likely require you to define a MyBatis result mapping in XML. This is the only instance where XML is +required. This is due to the limitations of the MyBatis annotations when mapping collections. The library supports four join types: @@ -74,14 +77,14 @@ The library supports four join types: The library supports the generation of UNION and UNION ALL queries. For example: ```java - SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight) - .from(animalData) - .union() - .selectDistinct(id, animalName, bodyWeight, brainWeight) - .from(animalData) - .orderBy(id) - .build() - .render(RenderingStrategies.MYBATIS3); +SelectStatementProvider selectStatement = select(id, animalName, bodyWeight, brainWeight) + .from(animalData) + .union() + .selectDistinct(id, animalName, bodyWeight, brainWeight) + .from(animalData) + .orderBy(id) + .build() + .render(RenderingStrategies.MYBATIS3); ``` Any number of SELECT statements can be added to a UNION query. Only one ORDER BY phrase is allowed. @@ -96,16 +99,16 @@ Multi-select queries are a special case of union select statements. The differen paging clauses can be applied to the merged queries. For example: ```java - SelectStatementProvider selectStatement = multiSelect( - select(id, animalName, bodyWeight, brainWeight) - .from(animalData) - .orderBy(id) - .limit(2) +SelectStatementProvider selectStatement = multiSelect( + select(id, animalName, bodyWeight, brainWeight) + .from(animalData) + .orderBy(id) + .limit(2) ).union( - selectDistinct(id, animalName, bodyWeight, brainWeight) - .from(animalData) - .orderBy(id.descending()) - .limit(3) + selectDistinct(id, animalName, bodyWeight, brainWeight) + .from(animalData) + .orderBy(id.descending()) + .limit(3) ) .build() .render(RenderingStrategies.MYBATIS3); @@ -114,7 +117,8 @@ paging clauses can be applied to the merged queries. For example: ## MyBatis Mapper for Select Statements The SelectStatementProvider object can be used as a parameter to a MyBatis mapper method directly. If you -are using an annotated mapper, the select method should look like this (note that we recommend coding a "selectMany" and a "selectOne" method with a shared result mapping): +are using an annotated mapper, the select method should look like this (note that we recommend coding a "selectMany" +and a "selectOne" method with a shared result mapping): ```java import org.apache.ibatis.annotations.Result; @@ -143,7 +147,9 @@ import org.mybatis.dynamic.sql.util.SqlProviderAdapter; ## XML Mapper for Join Statements -If you are coding a join, it is likely you will need to code an XML mapper to define the result map. This is due to a MyBatis limitation - the annotations cannot define a collection mapping. If you have to do this, the Java code looks like this: +If you are coding a join, it is likely you will need to code an XML mapper to define the result map. This is due to a +MyBatis limitation - the annotations cannot define a collection mapping. If you have to do this, the Java code looks +like this: ```java @SelectProvider(type=SqlProviderAdapter.class, method="select") @@ -171,7 +177,8 @@ And the corresponding XML looks like this: Notice that the resultMap is the only element in the XML mapper. This is our recommended practice. ## XML Mapper for Select Statements -We do not recommend using an XML mapper for select statements, but if you want to do so the SelectStatementProvider object can be used as a parameter to a MyBatis mapper method directly. +We do not recommend using an XML mapper for select statements, but if you want to do so the SelectStatementProvider +object can be used as a parameter to a MyBatis mapper method directly. If you are using an XML mapper, the select method should look like this in the Java interface: @@ -205,30 +212,33 @@ Order by phrases can be difficult to calculate when there are aliased columns, a This library has taken a relatively simple approach: 1. When specifying an SqlColumn in an ORDER BY phrase the library will either write the column alias or the column -name into the ORDER BY phrase. For the ORDER BY phrase, the table alias (if there is one) will be ignored. Use this pattern -when the ORDER BY column is a member of the select list. For example `orderBy(foo)`. If the column has an alias, then -it is easist to use the "arbitrary string" method with the column alias as shown below. -1. It is also possible to explicitly specify a table alias for a column in an ORDER BY phrase. Use this pattern when -there is a join, and the ORDER BY column is in two or more tables, and the ORDER BY column is not in the select -list. For example `orderBy(sortColumn("t1", foo))`. -1. If none of the above use cases meet your needs, then you can specify an arbitrary String to write into the rendered ORDER BY -phrase (see below for an example). + name into the ORDER BY phrase. For the ORDER BY phrase, the table alias (if there is one) will be ignored. Use this + pattern when the ORDER BY column is a member of the select list. For example `orderBy(foo)`. If the column has an + alias, then it is easiest to use the "arbitrary string" method with the column alias as shown below. +2. It is also possible to explicitly specify a table alias for a column in an ORDER BY phrase. Use this pattern when + there is a join, and the ORDER BY column is in two or more tables, and the ORDER BY column is not in the select + list. For example `orderBy(sortColumn("t1", foo))`. +3. If none of the above use cases meet your needs, then you can specify an arbitrary String to write into the rendered + ORDER BY phrase (see below for an example). In our testing, this caused an issue in only one case. When there is an outer join and the select list contains both the left and right join column. In that case, the workaround is to supply a column alias for both columns. -When using a column function (lower, upper, etc.), then it is customary to give the calculated column an alias so you will have a predictable result set. In cases like this there will not be a column to use for an alias. The library supports arbitrary values in an ORDER BY expression like this: +When using a column function (lower, upper, etc.), then it is customary to give the calculated column an alias so you +will have a predictable result set. In cases like this there will not be a column to use for an alias. The library +supports arbitrary values in an ORDER BY expression like this: ```java - SelectStatementProvider selectStatement = select(substring(gender, 1, 1).as("ShortGender"), avg(age).as("AverageAge")) - .from(person, "a") - .groupBy(substring(gender, 1, 1)) - .orderBy(sortColumn("ShortGender").descending()) - .build() - .render(RenderingStrategies.MYBATIS3); +SelectStatementProvider selectStatement = select(substring(gender, 1, 1).as("ShortGender"), avg(age).as("AverageAge")) + .from(person, "a") + .groupBy(substring(gender, 1, 1)) + .orderBy(sortColumn("ShortGender").descending()) + .build() + .render(RenderingStrategies.MYBATIS3); ``` -In this example the `substring` function is used in both the select list and the GROUP BY expression. In the ORDER BY expression, we use the `sortColumn` function to duplicate the alias given to the column in the select list. +In this example the `substring` function is used in both the select list and the GROUP BY expression. In the ORDER BY +expression, we use the `sortColumn` function to duplicate the alias given to the column in the select list. ## Limit and Offset Support Since version 1.1.1 the select statement supports limit and offset for paging (or slicing) queries. You can specify: @@ -237,18 +247,22 @@ Since version 1.1.1 the select statement supports limit and offset for paging (o - Offset only - Both limit and offset -It is important to note that the select renderer writes limit and offset clauses into the generated select statement as is. The library does not attempt to normalize those values for databases that don't support limit and offset directly. Therefore, it is very important for users to understand whether or not the target database supports limit and offset. If the target database does not support limit and offset, then it is likely that using this support will create SQL that has runtime errors. +It is important to note that the select renderer writes limit and offset clauses into the generated select statement as +is. The library does not attempt to normalize those values for databases that don't support limit and offset directly. +Therefore, it is very important for users to understand whether the target database supports limit and offset. +If the target database does not support limit and offset, then it is likely that using this support will create SQL +that has runtime errors. An example follows: ```java - SelectStatementProvider selectStatement = select(animalData.allColumns()) - .from(animalData) - .orderBy(id) - .limit(3) - .offset(22) - .build() - .render(RenderingStrategies.MYBATIS3); +SelectStatementProvider selectStatement = select(animalData.allColumns()) + .from(animalData) + .orderBy(id) + .limit(3) + .offset(22) + .build() + .render(RenderingStrategies.MYBATIS3); ``` ## Fetch First Support @@ -263,11 +277,11 @@ Fetch first is an SQL standard and is supported by most databases. An example follows: ```java - SelectStatementProvider selectStatement = select(animalData.allColumns()) - .from(animalData) - .orderBy(id) - .offset(22) - .fetchFirst(3).rowsOnly() - .build() - .render(RenderingStrategies.MYBATIS3); +SelectStatementProvider selectStatement = select(animalData.allColumns()) + .from(animalData) + .orderBy(id) + .offset(22) + .fetchFirst(3).rowsOnly() + .build() + .render(RenderingStrategies.MYBATIS3); ``` diff --git a/src/site/markdown/docs/springBatch.md b/src/site/markdown/docs/springBatch.md index 4b1855a97..aca011c39 100644 --- a/src/site/markdown/docs/springBatch.md +++ b/src/site/markdown/docs/springBatch.md @@ -1,100 +1,164 @@ # Spring Batch Support This library provides some utilities to make it easier to interact with the MyBatis Spring Batch support. -## The Problem +MyBatis Spring provides support for interacting with Spring Batch (see +[http://www.mybatis.org/spring/batch.html](http://www.mybatis.org/spring/batch.html)). This support consists of +specialized implementations of Spring Batch's `ItemReader` and `ItemWriter` interfaces that have support for MyBatis +mappers. -MyBatis Spring support provides utility classes for interacting with Spring Batch (see [http://www.mybatis.org/spring/batch.html](http://www.mybatis.org/spring/batch.html)). These classes are specialized implementations of Spring Batch's `ItemReader` and `ItemWriter` interfaces that have support for MyBatis mappers. +The `ItemWriter` implementation works with SQL generated by MyBatis Dynamic SQL with no modification needed. -The `ItemWriter` implementations work with SQL generated by MyBatis Dynamic SQL with no modification needed. +The `ItemReader` implementations need special care. Those classes assume that all query parameters will be placed in a +Map (as per usual when using multiple parameters in a query). MyBatis Dynamic SQL, by default, builds a parameter +object that is intended to be the only parameter for a query. The library contains utilities for overcoming this +difficulty. -The `ItemReader` implementations need special care. Those classes assume that all query parameters will be placed in a Map (as per usual when using multiple parameters in a query). MyBatis Dynamic SQL, by default, builds a parameter object that should be the only parameter in a query and will not work when placed in a Map of parameters. +## Using MyBatisCursorItemReader -## The Solution +The `MyBatisCursorItemReader` class works with built-in support for cursor based queries in MyBatis. Queries of this +type will read row by row and MyBatis will convert each result row to a result object without having to read the entire +result set into memory. The normal rendering for MyBatis will work for queries using this reader, but special care +must be taken to prepare the parameter values for use with this reader. See the following example: -The solution involves these steps: - -1. The SQL must be rendered such that the parameter markers are aware of the enclosing parameter Map in the `ItemReader` -1. The `SelectStatementProvider` must be placed in the `ItemReader` parameter Map with a known key. -1. The `@SelectProvider` must be configured to be aware of the enclosing parameter Map +```java +@Bean +public MyBatisCursorItemReader reader(SqlSessionFactory sqlSessionFactory) { + SelectStatementProvider selectStatement = select(person.allColumns()) + .from(person) + .where(lastName, isEqualTo("flintstone")) + .build() + .render(RenderingStrategies.MYBATIS3); + + MyBatisCursorItemReader reader = new MyBatisCursorItemReader<>(); + reader.setQueryId(PersonMapper.class.getName() + ".selectMany"); + reader.setSqlSessionFactory(sqlSessionFactory); + reader.setParameterValues(SpringBatchUtility.toParameterValues(selectStatement)); + return reader; +} +``` -MyBatis Dynamic SQL provides utilities for each of these requirements. Each utility uses a shared Map key for consistency. +Note the use of `SpringBatchUtility.toParameterValues(...)`. This utility will set up the parameter Map correctly for the +rendered statement, and for use with a library supplied `@selectProvider`. See the following for an example of the mapper +method used for the query coded above: -## Spring Batch Item Readers +```java +@Mapper +public interface PersonMapper { + + @SelectProvider(type=SpringBatchProviderAdapter.class, method="select") + @Results({ + @Result(column="id", property="id", id=true), + @Result(column="first_name", property="firstName"), + @Result(column="last_name", property="lastName") + }) + List selectMany(Map parameterValues); +} +``` -MyBatis Spring support supplies two implementations of the `ItemReader` interface: +Note the use of the `SpringBatchProviderAdapter` - that adapter knows how to retrieve the rendered queries from the +parameter map initialed in the method above. -1. `org.mybatis.spring.batch.MyBatisCursorItemReader` - for queries that can be efficiently processed through a single select statement and a cursor -1. `org.mybatis.spring.batch.MyBatisPagingItemReader` - for queries that should be processed as a series of paged selects. Note that MyBatis does not provide any native support for paged queries - it is up to the user to write SQL for paging. The `MyBatisPagingItemWriter` simply makes properties available that specify which page should be read currently. +### Migrating from 1.x Support for MyBatisCursorItemReader -MyBatis Dynamic SQL supplies specialized select statements that will render properly for the different implementations of `ItemReader`: +In version 1.x, the library supplied a special utility for creating a select statement as follows: -1. `SpringBatchUtility.selectForCursor(...)` will create a select statement that is appropriate for the `MyBatisCursorItemReader` - a single select statement that will be read with a cursor -1. `SpringBatchUtility.selectForPaging(...)` will create a select statement that is appropriate for the `MyBatisPagingItemReader` - a select statement that will be called multiple times - one for each page as configured on the batch job. +```java +SelectStatementProvider selectStatement = SpringBatchUtility.selectForCursor(person.allColumns()) + .from(person) + .where(lastName, isEqualTo("flintstone")) + .build() + .render(); +``` -**Very Important:** The paging implementation will only work for databases that support limit and offset in select statements. Fortunately, most databases do support this - with the notable exception of Oracle. +That utility method was limited in capability and has been removed. The new method described above allows the full +capabilities of the library. To migrate, follow these steps: +1. Replace `SpringBatchUtility.selectForCursor(...)` with `SqlBuilder.select(...)` +2. Replace `render()` with `render(RenderingStrategies.MYBATIS3)` -### Rendering for Cursor +## Using MyBatisPagingItemReader -Queries intended for the `MyBatisCursorItemReader` should be rendered as follows: +The `MyBatisPagingItemReader` class works with paging queries - queries that read rows in pages and process page by page +rather than row by row. The normal rendering for MyBatis will work NOT for queries using this reader because MyBatis +Spring support supplies specially named parameters for page size, offset, etc. So the query must be rendered properly +to respond to these parameter values that are supplied at runtime. As with the other reader, special care +must also be taken to prepare the parameter values for use with this reader. See the following example: ```java - SelectStatementProvider selectStatement = SpringBatchUtility.selectForCursor(person.allColumns()) - .from(person) - .where(lastName, isEqualTo("flintstone")) - .build() - .render(); // renders for MyBatisCursorItemReader +@Bean +public MyBatisPagingItemReader reader(SqlSessionFactory sqlSessionFactory) { + SelectStatementProvider selectStatement = select(person.allColumns()) + .from(person) + .where(forPagingTest, isEqualTo(true)) + .orderBy(id) + .limit(SpringBatchUtility.MYBATIS_SPRING_BATCH_PAGESIZE) + .offset(SpringBatchUtility.MYBATIS_SPRING_BATCH_SKIPROWS) + .build() + .render(SpringBatchUtility.SPRING_BATCH_PAGING_ITEM_READER_RENDERING_STRATEGY); + + MyBatisPagingItemReader reader = new MyBatisPagingItemReader<>(); + reader.setQueryId(PersonMapper.class.getName() + ".selectMany"); + reader.setSqlSessionFactory(sqlSessionFactory); + reader.setParameterValues(SpringBatchUtility.toParameterValues(selectStatement)); + reader.setPageSize(7); + return reader; +} ``` - -### Rendering for Paging - -Queries intended for the `MyBatisPagingItemReader` should be rendered as follows: +Notice the following important items: + +1. The `limit` and `offset` methods in the query are used to set up paging support in the query. With MyBatis Spring + batch support, the integration library will supply values for those parameters at runtime. Any values you code in the + select statement will be ignored - only the values supplied by the library will be used. We supply two constants + to make this clearer: `MYBATIS_SPRING_BATCH_PAGESIZE` and `MYBATIS_SPRING_BATCH_SKIPROWS`. You can use these values + to make the code clearer, but again the values will be ignored at runtime. +2. The query must be rendered with the `SPRING_BATCH_PAGING_ITEM_READER_RENDERING_STRATEGY` rendering strategy. This + rendering strategy will render the query so that it will respond properly to the runtime values supplied for page size + and skip rows. +3. Note the use of `SpringBatchUtility.toParameterValues(...)`. This utility will set up the parameter Map correctly for + the rendered statement, and for use with a library supplied `@selectProvider`. See the following for an example of + the mapper method used for the query coded above: ```java - SelectStatementProvider selectStatement = SpringBatchUtility.selectForPaging(person.allColumns()) - .from(person) - .where(lastName, isEqualTo("flintstone")) - .build() - .render(); // renders for MyBatisPagingItemReader +@Mapper +public interface PersonMapper { + + @SelectProvider(type=SpringBatchProviderAdapter.class, method="select") + @Results({ + @Result(column="id", property="id", id=true), + @Result(column="first_name", property="firstName"), + @Result(column="last_name", property="lastName") + }) + List selectMany(Map parameterValues); +} ``` -## Creating the Parameter Map - -The `SpringBatchUtility` provides a method to create the parameter values Map needed by the MyBatis Spring `ItemReader` implementations. It can be used as follows: +Note the use of the `SpringBatchProviderAdapter` - that adapter knows how to retrieve the rendered queries from the +parameter map initialed in the method above. -For cursor based queries... +### Migrating from 1.x Support for MyBatisPagingItemReader -```java - MyBatisCursorItemReader reader = new MyBatisCursorItemReader<>(); - reader.setQueryId(PersonMapper.class.getName() + ".selectMany"); - reader.setSqlSessionFactory(sqlSessionFactory); - reader.setParameterValues(SpringBatchUtility.toParameterValues(selectStatement)); // create parameter map -``` -For paging based queries... +In version 1.x, the library supplied a special utility for creating a select statement as follows: ```java - MyBatisPagingItemReader reader = new MyBatisPagingItemReader<>(); - reader.setQueryId(PersonMapper.class.getName() + ".selectMany"); - reader.setSqlSessionFactory(sqlSessionFactory); - reader.setPageSize(7); - reader.setParameterValues(SpringBatchUtility.toParameterValues(selectStatement)); // create parameter map +SelectStatementProvider selectStatement = SpringBatchUtility.selectForPaging(person.allColumns()) + .from(person) + .where(forPagingTest, isEqualTo(true)) + .orderBy(id) + .build() + .render(); ``` +That utility method was very limited in capability and has been removed. The prior method only supported limit and +offset based queries - which are not supported in all databases. The new method described above allows the full +capabilities of the library to be used. To migrate, follow these steps: -## Specialized @SelectProvider Adapter +1. Replace `SpringBatchUtility.selectForPaging(...)` with `SqlBuilder.select(...)` +2. Add `limit()`, `fetchFirst()`, and `offset()` method calls as appropriate for your query and database +3. Replace `render()` with `render(RenderingStrategies.SPRING_BATCH_PAGING_ITEM_READER_RENDERING_STRATEGY)` -MyBatis mapper methods should be configured to use the specialized `@SelectProvider` adapter as follows: - -```java - @SelectProvider(type=SpringBatchProviderAdapter.class, method="select") // use the Spring batch adapter - @Results({ - @Result(column="id", property="id", id=true), - @Result(column="first_name", property="firstName"), - @Result(column="last_name", property="lastName") - }) - List selectMany(Map parameterValues); -``` -## Complete Example +## Complete Examples -The unit tests for MyBatis Dynamic SQL include a complete example of using MyBatis Spring Batch support using the MyBatis supplied reader as well as both types of MyBatis supplied writers. You can see the full example here: [https://github.com/mybatis/mybatis-dynamic-sql/tree/master/src/test/java/examples/springbatch](https://github.com/mybatis/mybatis-dynamic-sql/tree/master/src/test/java/examples/springbatch) +The unit tests for MyBatis Dynamic SQL include a complete example of using MyBatis Spring Batch support using the +MyBatis supplied reader as well as both types of MyBatis supplied writers. You can see the full example +here: [https://github.com/mybatis/mybatis-dynamic-sql/tree/master/src/test/java/examples/springbatch](https://github.com/mybatis/mybatis-dynamic-sql/tree/master/src/test/java/examples/springbatch) diff --git a/src/site/resources/css/site.css b/src/site/resources/css/site.css index 9012c031a..54e023e50 100644 --- a/src/site/resources/css/site.css +++ b/src/site/resources/css/site.css @@ -1,5 +1,5 @@ /** - * Copyright 2016-2024 the original author or authors. + * 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. diff --git a/src/site/site.xml b/src/site/site.xml index e22bf264d..86b32feac 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -1,7 +1,7 @@