Skip to content

Commit

Permalink
Merge pull request #761 from jeffgbutler/case-expression
Browse files Browse the repository at this point in the history
Add Support for CASE Expressions
  • Loading branch information
jeffgbutler authored Mar 15, 2024
2 parents 5d5b9b6 + f1a3e00 commit f450687
Show file tree
Hide file tree
Showing 36 changed files with 3,612 additions and 39 deletions.
23 changes: 22 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,31 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are av

## Release 1.5.1 - Unreleased

This is a minor release with a few small enhancements.
This is a minor release with several enhancements.

GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/milestone/13](https://github.com/mybatis/mybatis-dynamic-sql/milestone/13)

### Case Expressions and Cast Function
We've added support for CASE expressions to the library. Both simple and searched case expressions are supported.
This is a fairly extensive enhancement as case expressions are quite complex, but we were able to reuse many of the
building blocks from the WHERE and HAVING support already in the library. You should be able to build CASE expressions
with relatively few limitations.

It is also common to use a CAST function with CASE expressions, so we have added CAST as a built-in function
in the library.

The DSL for both Java and Kotlin has been updated to fully support CASE expressions in the same idiomatic forms
as other parts of the library.

We've tested this extensively and the code is, of course, 100% covered by test code. But it is possible that we've not
covered every scenario. Please let us know if you find issues.

Full documentation is available here:
- [Java Case Expression DSL Documentation](caseExpressions.md)
- [Kotlin Case Expression DSL Documentation](kotlinCaseExpressions.md)

The pull request for this change is ([#761](https://github.com/mybatis/mybatis-dynamic-sql/pull/761))

### Parameter Values in Joins

We've added the ability to specify typed values in equi-joins. This allows you to avoid the use of constants, and it is
Expand Down
41 changes: 41 additions & 0 deletions src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@
import org.mybatis.dynamic.sql.select.aggregate.Max;
import org.mybatis.dynamic.sql.select.aggregate.Min;
import org.mybatis.dynamic.sql.select.aggregate.Sum;
import org.mybatis.dynamic.sql.select.caseexpression.SearchedCaseDSL;
import org.mybatis.dynamic.sql.select.caseexpression.SimpleCaseDSL;
import org.mybatis.dynamic.sql.select.function.Add;
import org.mybatis.dynamic.sql.select.function.Cast;
import org.mybatis.dynamic.sql.select.function.Concat;
import org.mybatis.dynamic.sql.select.function.Concatenate;
import org.mybatis.dynamic.sql.select.function.Divide;
Expand Down Expand Up @@ -442,6 +445,17 @@ static <T> JoinCriterion<T> on(BindableColumn<T> joinColumn, JoinCondition<T> jo
.build();
}

// case expressions
@SuppressWarnings("java:S100")
static <T> SimpleCaseDSL<T> case_(BindableColumn<T> column) {
return SimpleCaseDSL.simpleCase(column);
}

@SuppressWarnings("java:S100")
static SearchedCaseDSL case_() {
return SearchedCaseDSL.searchedCase();
}

static <T> EqualTo<T> equalTo(BindableColumn<T> column) {
return new EqualTo<>(column);
}
Expand Down Expand Up @@ -517,6 +531,18 @@ static <T> Subtract<T> subtract(BindableColumn<T> firstColumn, BasicColumn secon
return Subtract.of(firstColumn, secondColumn, subsequentColumns);
}

static CastFinisher cast(String value) {
return cast(stringConstant(value));
}

static CastFinisher cast(Double value) {
return cast(constant(value.toString()));
}

static CastFinisher cast(BasicColumn column) {
return new CastFinisher(column);
}

/**
* Concatenate function that renders as "(x || y || z)". This will not work on some
* databases like MySql. In that case, use {@link SqlBuilder#concat(BindableColumn, BasicColumn...)}
Expand Down Expand Up @@ -968,4 +994,19 @@ public <T> GeneralInsertDSL.SetClauseFinisher<T> set(SqlColumn<T> column) {
.set(column);
}
}

class CastFinisher {
private final BasicColumn column;

public CastFinisher(BasicColumn column) {
this.column = column;
}

public Cast as(String targetType) {
return new Cast.Builder()
.withColumn(column)
.withTargetType(targetType)
.build();
}
}
}
3 changes: 2 additions & 1 deletion src/main/java/org/mybatis/dynamic/sql/StringConstant.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public Optional<String> alias() {

@Override
public FragmentAndParameters render(RenderingContext renderingContext) {
return FragmentAndParameters.fromFragment("'" + value + "'"); //$NON-NLS-1$ //$NON-NLS-2$
String escaped = value.replace("'", "''"); //$NON-NLS-1$ //$NON-NLS-2$
return FragmentAndParameters.fromFragment("'" + escaped + "'"); //$NON-NLS-1$ //$NON-NLS-2$
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,13 @@ private void addSubCriteria(String connector, List<AndOrCriteriaGroup> criteria)
.build());
}

protected void setInitialCriterion(SqlCriterion initialCriterion) {
this.initialCriterion = initialCriterion;
}

protected void setInitialCriterion(SqlCriterion initialCriterion, StatementType statementType) {
Validator.assertTrue(this.initialCriterion == null, statementType.messageNumber());
this.initialCriterion = initialCriterion;
setInitialCriterion(initialCriterion);
}

// may be null!
Expand Down
11 changes: 6 additions & 5 deletions src/main/java/org/mybatis/dynamic/sql/select/aggregate/Sum.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import org.mybatis.dynamic.sql.select.function.AbstractUniTypeFunction;
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
import org.mybatis.dynamic.sql.util.Validator;
import org.mybatis.dynamic.sql.where.render.DefaultConditionVisitor;
import org.mybatis.dynamic.sql.where.render.ColumnAndConditionRenderer;

public class Sum<T> extends AbstractUniTypeFunction<T, Sum<T>> {
private final Function<RenderingContext, FragmentAndParameters> renderer;
Expand All @@ -38,12 +38,13 @@ private Sum(BindableColumn<T> column, VisitableCondition<T> condition) {
renderer = rc -> {
Validator.assertTrue(condition.shouldRender(rc), "ERROR.37", "sum"); //$NON-NLS-1$ //$NON-NLS-2$

DefaultConditionVisitor<T> visitor = new DefaultConditionVisitor.Builder<T>()
return new ColumnAndConditionRenderer.Builder<T>()
.withColumn(column)
.withCondition(condition)
.withRenderingContext(rc)
.build();

return condition.accept(visitor).mapFragment(this::applyAggregate);
.build()
.render()
.mapFragment(this::applyAggregate);
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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.caseexpression;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import org.mybatis.dynamic.sql.BasicColumn;

public class BasicWhenCondition<T> extends SimpleCaseWhenCondition<T> {
private final List<T> conditions = new ArrayList<>();

public BasicWhenCondition(List<T> conditions, BasicColumn thenValue) {
super(thenValue);
this.conditions.addAll(conditions);
}

public Stream<T> conditions() {
return conditions.stream();
}

@Override
public <R> R accept(SimpleCaseWhenConditionVisitor<T, R> visitor) {
return visitor.visit(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.caseexpression;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import org.mybatis.dynamic.sql.BasicColumn;
import org.mybatis.dynamic.sql.VisitableCondition;

public class ConditionBasedWhenCondition<T> extends SimpleCaseWhenCondition<T> {
private final List<VisitableCondition<T>> conditions = new ArrayList<>();

public ConditionBasedWhenCondition(List<VisitableCondition<T>> conditions, BasicColumn thenValue) {
super(thenValue);
this.conditions.addAll(conditions);
}

public Stream<VisitableCondition<T>> conditions() {
return conditions.stream();
}

@Override
public <R> R accept(SimpleCaseWhenConditionVisitor<T, R> visitor) {
return visitor.visit(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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.caseexpression;

import org.mybatis.dynamic.sql.BasicColumn;
import org.mybatis.dynamic.sql.Constant;
import org.mybatis.dynamic.sql.StringConstant;

public interface ElseDSL<T> {

@SuppressWarnings("java:S100")
default T else_(String value) {
return else_(StringConstant.of(value));
}

@SuppressWarnings("java:S100")
default T else_(Boolean value) {
return else_(Constant.of(value.toString()));
}

@SuppressWarnings("java:S100")
default T else_(Integer value) {
return else_(Constant.of(value.toString()));
}

@SuppressWarnings("java:S100")
default T else_(Long value) {
return else_(Constant.of(value.toString()));
}

@SuppressWarnings("java:S100")
default T else_(Double value) {
return else_(Constant.of(value.toString()));
}

@SuppressWarnings("java:S100")
T else_(BasicColumn column);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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.caseexpression;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

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.SqlCriterion;
import org.mybatis.dynamic.sql.VisitableCondition;
import org.mybatis.dynamic.sql.common.AbstractBooleanExpressionDSL;

public class SearchedCaseDSL implements ElseDSL<SearchedCaseDSL.SearchedCaseEnder> {
private final List<SearchedCaseModel.SearchedWhenCondition> whenConditions = new ArrayList<>();
private BasicColumn elseValue;

public <T> WhenDSL when(BindableColumn<T> column, VisitableCondition<T> condition,
AndOrCriteriaGroup... subCriteria) {
return when(column, condition, Arrays.asList(subCriteria));
}

public <T> WhenDSL when(BindableColumn<T> column, VisitableCondition<T> condition,
List<AndOrCriteriaGroup> subCriteria) {
SqlCriterion sqlCriterion = ColumnAndConditionCriterion.withColumn(column)
.withCondition(condition)
.withSubCriteria(subCriteria)
.build();

return initialize(sqlCriterion);
}

public WhenDSL when(SqlCriterion initialCriterion, AndOrCriteriaGroup... subCriteria) {
return when(initialCriterion, Arrays.asList(subCriteria));
}

public WhenDSL when(SqlCriterion initialCriterion, List<AndOrCriteriaGroup> subCriteria) {
SqlCriterion sqlCriterion = new CriteriaGroup.Builder()
.withInitialCriterion(initialCriterion)
.withSubCriteria(subCriteria)
.build();

return initialize(sqlCriterion);
}

private WhenDSL initialize(SqlCriterion sqlCriterion) {
return new WhenDSL(sqlCriterion);
}

@SuppressWarnings("java:S100")
@Override
public SearchedCaseEnder else_(BasicColumn column) {
elseValue = column;
return new SearchedCaseEnder();
}

public BasicColumn end() {
return new SearchedCaseModel.Builder()
.withElseValue(elseValue)
.withWhenConditions(whenConditions)
.build();
}

public class WhenDSL extends AbstractBooleanExpressionDSL<WhenDSL> implements ThenDSL<SearchedCaseDSL> {
private WhenDSL(SqlCriterion sqlCriterion) {
setInitialCriterion(sqlCriterion);
}

@Override
public SearchedCaseDSL then(BasicColumn column) {
whenConditions.add(new SearchedCaseModel.SearchedWhenCondition(getInitialCriterion(), subCriteria,
column));
return SearchedCaseDSL.this;
}

@Override
protected WhenDSL getThis() {
return this;
}
}

public class SearchedCaseEnder {
public BasicColumn end() {
return SearchedCaseDSL.this.end();
}
}

public static SearchedCaseDSL searchedCase() {
return new SearchedCaseDSL();
}
}
Loading

0 comments on commit f450687

Please sign in to comment.