From 8a7bed9843bcad384858415680402f331bd9967b Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 24 Jul 2020 14:31:34 -0400 Subject: [PATCH 1/4] New Hierarchy for Functions --- .../org/mybatis/dynamic/sql/SqlBuilder.java | 12 ++++ .../sql/select/function/AbstractFunction.java | 29 ++++++++- ...tractMultipleColumnArithmeticFunction.java | 22 ++++--- .../function/AbstractUniTypeFunction.java | 47 ++++++++++++++ .../dynamic/sql/select/function/Add.java | 11 +--- .../sql/select/function/Concatenate.java | 40 ++++++++++++ .../dynamic/sql/select/function/Divide.java | 11 +--- .../dynamic/sql/select/function/Lower.java | 4 +- .../dynamic/sql/select/function/Multiply.java | 11 +--- .../sql/select/function/OperatorFunction.java | 64 +++++++++++++++++++ .../sql/select/function/Substring.java | 4 +- .../dynamic/sql/select/function/Subtract.java | 11 +--- .../dynamic/sql/select/function/Upper.java | 4 +- .../examples/animal/data/AnimalDataTest.java | 27 ++++++++ 14 files changed, 248 insertions(+), 49 deletions(-) create mode 100644 src/main/java/org/mybatis/dynamic/sql/select/function/AbstractUniTypeFunction.java create mode 100644 src/main/java/org/mybatis/dynamic/sql/select/function/Concatenate.java create mode 100644 src/main/java/org/mybatis/dynamic/sql/select/function/OperatorFunction.java diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java index e1b248c4c..017cfe5e8 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java @@ -40,9 +40,11 @@ import org.mybatis.dynamic.sql.select.aggregate.Min; import org.mybatis.dynamic.sql.select.aggregate.Sum; import org.mybatis.dynamic.sql.select.function.Add; +import org.mybatis.dynamic.sql.select.function.Concatenate; import org.mybatis.dynamic.sql.select.function.Divide; import org.mybatis.dynamic.sql.select.function.Lower; import org.mybatis.dynamic.sql.select.function.Multiply; +import org.mybatis.dynamic.sql.select.function.OperatorFunction; import org.mybatis.dynamic.sql.select.function.Substring; import org.mybatis.dynamic.sql.select.function.Subtract; import org.mybatis.dynamic.sql.select.function.Upper; @@ -285,6 +287,16 @@ static Subtract subtract(BindableColumn firstColumn, Ba return Subtract.of(firstColumn, secondColumn, Arrays.asList(subsequentColumns)); } + static Concatenate concatenate(BindableColumn firstColumn, BasicColumn secondColumn, + BasicColumn... subsequentColumns) { + return Concatenate.concatenate(firstColumn, secondColumn, subsequentColumns); + } + + static OperatorFunction operatorFunction(String operator, BindableColumn firstColumn, BasicColumn secondColumn, + BasicColumn... subsequentColumns) { + return OperatorFunction.of(operator, firstColumn, secondColumn, subsequentColumns); + } + static Lower lower(BindableColumn column) { return Lower.of(column); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractFunction.java b/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractFunction.java index 9f2bec767..0fe5ebe7b 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractFunction.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractFunction.java @@ -16,17 +16,38 @@ package org.mybatis.dynamic.sql.select.function; import java.sql.JDBCType; +import java.util.Objects; import java.util.Optional; import org.mybatis.dynamic.sql.BindableColumn; -public abstract class AbstractFunction> - extends AbstractTypeConvertingFunction { +/** + * @deprecated in favor of {@link AbstractUniTypeFunction} + * + * @author Jeff Butler + */ +@Deprecated +public abstract class AbstractFunction> implements BindableColumn { + + protected BindableColumn column; + protected String alias; protected AbstractFunction(BindableColumn column) { - super(column); + this.column = Objects.requireNonNull(column); } + @Override + public Optional alias() { + return Optional.ofNullable(alias); + } + + @Override + public U as(String alias) { + U newThing = copy(); + newThing.alias = alias; + return newThing; + } + @Override public Optional jdbcType() { return column.jdbcType(); @@ -36,4 +57,6 @@ public Optional jdbcType() { public Optional typeHandler() { return column.typeHandler(); } + + protected abstract U copy(); } diff --git a/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractMultipleColumnArithmeticFunction.java b/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractMultipleColumnArithmeticFunction.java index cc387ead7..f2294daf5 100644 --- a/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractMultipleColumnArithmeticFunction.java +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractMultipleColumnArithmeticFunction.java @@ -1,5 +1,5 @@ /** - * Copyright 2016-2018 the original author or authors. + * Copyright 2016-2020 the original author 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,13 +26,19 @@ import org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.render.TableAliasCalculator; +/** + * @deprecated in favor of {@link OperatorFunction}. + * + * @author Jeff Butler + */ +@Deprecated public abstract class AbstractMultipleColumnArithmeticFunction> - extends AbstractFunction> { - + extends AbstractFunction> { + protected BasicColumn secondColumn; protected List subsequentColumns = new ArrayList<>(); - + protected AbstractMultipleColumnArithmeticFunction(BindableColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { super(firstColumn); @@ -42,13 +48,13 @@ protected AbstractMultipleColumnArithmeticFunction(BindableColumn firstColumn @Override public String renderWithTableAlias(TableAliasCalculator tableAliasCalculator) { - // note - the cast below is added for a type inference bug in the Java9 compiler. + // note - the cast below is added for a type inference bug in the Java9 + // compiler. return Stream.of(Stream.of((BasicColumn) column), Stream.of(secondColumn), subsequentColumns.stream()) - .flatMap(Function.identity()) - .map(column -> column.renderWithTableAlias(tableAliasCalculator)) + .flatMap(Function.identity()).map(column -> column.renderWithTableAlias(tableAliasCalculator)) .collect(Collectors.joining(padOperator(), "(", ")")); //$NON-NLS-1$ //$NON-NLS-2$ } - + private String padOperator() { return " " + operator() + " "; //$NON-NLS-1$ //$NON-NLS-2$ } 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 new file mode 100644 index 000000000..641d31883 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/AbstractUniTypeFunction.java @@ -0,0 +1,47 @@ +/** + * Copyright 2016-2020 the original author 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "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.function; + +import java.sql.JDBCType; +import java.util.Optional; + +import org.mybatis.dynamic.sql.BindableColumn; + +/** + * Represents a function that does not change the underlying data type. + * + * @author Jeff Butler + * + * @param The type of the underlying column + * @param the specific subtype that implements the function + */ +public abstract class AbstractUniTypeFunction> + extends AbstractTypeConvertingFunction { + + protected AbstractUniTypeFunction(BindableColumn column) { + super(column); + } + + @Override + public Optional jdbcType() { + return column.jdbcType(); + } + + @Override + public Optional typeHandler() { + return column.typeHandler(); + } +} 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 72100276a..2ae63e13b 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-2018 the original author or authors. + * Copyright 2016-2020 the original author 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,11 @@ import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; -public class Add extends AbstractMultipleColumnArithmeticFunction> { +public class Add extends OperatorFunction { private Add(BindableColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { - super(firstColumn, secondColumn, subsequentColumns); + super("+", firstColumn, secondColumn, subsequentColumns); //$NON-NLS-1$ } @Override @@ -32,11 +32,6 @@ protected Add copy() { return new Add<>(column, secondColumn, subsequentColumns); } - @Override - protected String operator() { - return "+"; //$NON-NLS-1$ - } - public static Add of(BindableColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { return new Add<>(firstColumn, secondColumn, 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 new file mode 100644 index 000000000..dce828955 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/Concatenate.java @@ -0,0 +1,40 @@ +/** + * Copyright 2016-2020 the original author 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "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.function; + +import java.util.Arrays; +import java.util.List; + +import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.BindableColumn; + +public class Concatenate extends OperatorFunction { + + protected Concatenate(BindableColumn firstColumn, BasicColumn secondColumn, + List subsequentColumns) { + super("||", firstColumn, secondColumn, subsequentColumns); //$NON-NLS-1$ + } + + @Override + protected Concatenate copy() { + return new Concatenate<>(column, secondColumn, subsequentColumns); + } + + public static Concatenate concatenate(BindableColumn firstColumn, BasicColumn secondColumn, + BasicColumn... subsequentColumns) { + return new Concatenate<>(firstColumn, secondColumn, Arrays.asList(subsequentColumns)); + } +} 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 1489afc1b..fc1a678d2 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-2018 the original author or authors. + * Copyright 2016-2020 the original author 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,11 @@ import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; -public class Divide extends AbstractMultipleColumnArithmeticFunction> { +public class Divide extends OperatorFunction { private Divide(BindableColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { - super(firstColumn, secondColumn, subsequentColumns); + super("/", firstColumn, secondColumn, subsequentColumns); //$NON-NLS-1$ } @Override @@ -32,11 +32,6 @@ protected Divide copy() { return new Divide<>(column, secondColumn, subsequentColumns); } - @Override - protected String operator() { - return "/"; //$NON-NLS-1$ - } - public static Divide of(BindableColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { return new Divide<>(firstColumn, secondColumn, subsequentColumns); 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 184d4e8dc..8d9fd397a 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-2018 the original author or authors. + * Copyright 2016-2020 the original author 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 org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.render.TableAliasCalculator; -public class Lower extends AbstractFunction { +public class Lower extends AbstractUniTypeFunction { private Lower(BindableColumn 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 4fdd3f5d9..9e543bb24 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-2018 the original author or authors. + * Copyright 2016-2020 the original author 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,11 @@ import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; -public class Multiply extends AbstractMultipleColumnArithmeticFunction> { +public class Multiply extends OperatorFunction { private Multiply(BindableColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { - super(firstColumn, secondColumn, subsequentColumns); + super("*", firstColumn, secondColumn, subsequentColumns); //$NON-NLS-1$ } @Override @@ -32,11 +32,6 @@ protected Multiply copy() { return new Multiply<>(column, secondColumn, subsequentColumns); } - @Override - protected String operator() { - return "*"; //$NON-NLS-1$ - } - public static Multiply of(BindableColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { return new Multiply<>(firstColumn, secondColumn, subsequentColumns); 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 new file mode 100644 index 000000000..934cd7fc5 --- /dev/null +++ b/src/main/java/org/mybatis/dynamic/sql/select/function/OperatorFunction.java @@ -0,0 +1,64 @@ +/** + * Copyright 2016-2020 the original author 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "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.function; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.mybatis.dynamic.sql.BasicColumn; +import org.mybatis.dynamic.sql.BindableColumn; +import org.mybatis.dynamic.sql.render.TableAliasCalculator; + +public class OperatorFunction extends AbstractUniTypeFunction> { + + protected BasicColumn secondColumn; + protected List subsequentColumns = new ArrayList<>(); + private String operator; + + protected OperatorFunction(String operator, BindableColumn firstColumn, BasicColumn secondColumn, + List subsequentColumns) { + super(firstColumn); + this.secondColumn = Objects.requireNonNull(secondColumn); + this.subsequentColumns.addAll(subsequentColumns); + this.operator = Objects.requireNonNull(operator); + } + + @Override + protected OperatorFunction copy() { + return new OperatorFunction<>(operator, column, secondColumn, subsequentColumns); + } + + @Override + public String renderWithTableAlias(TableAliasCalculator tableAliasCalculator) { + String paddedOperator = " " + operator + " "; //$NON-NLS-1$ //$NON-NLS-2$ + + // note - the cast below is added for a type inference bug in the Java9 compiler. + return Stream.of(Stream.of((BasicColumn) column), Stream.of(secondColumn), subsequentColumns.stream()) + .flatMap(Function.identity()) + .map(column -> column.renderWithTableAlias(tableAliasCalculator)) + .collect(Collectors.joining(paddedOperator, "(", ")")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public static OperatorFunction of(String operator, BindableColumn firstColumn, BasicColumn secondColumn, + BasicColumn... subsequentColumns) { + return new OperatorFunction<>(operator, firstColumn, secondColumn, Arrays.asList(subsequentColumns)); + } +} 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 9efb9a148..3a21a3b3c 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-2018 the original author or authors. + * Copyright 2016-2020 the original author 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 org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.render.TableAliasCalculator; -public class Substring extends AbstractFunction { +public class Substring extends AbstractUniTypeFunction { private int offset; private int 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 d6b0482ab..3776b83ac 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-2018 the original author or authors. + * Copyright 2016-2020 the original author 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,11 @@ import org.mybatis.dynamic.sql.BasicColumn; import org.mybatis.dynamic.sql.BindableColumn; -public class Subtract extends AbstractMultipleColumnArithmeticFunction> { +public class Subtract extends OperatorFunction { private Subtract(BindableColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { - super(firstColumn, secondColumn, subsequentColumns); + super("-", firstColumn, secondColumn, subsequentColumns); //$NON-NLS-1$ } @Override @@ -32,11 +32,6 @@ protected Subtract copy() { return new Subtract<>(column, secondColumn, subsequentColumns); } - @Override - protected String operator() { - return "-"; //$NON-NLS-1$ - } - public static Subtract of(BindableColumn firstColumn, BasicColumn secondColumn, List subsequentColumns) { return new Subtract<>(firstColumn, secondColumn, subsequentColumns); 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 8895b7e90..305066eb6 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-2018 the original author or authors. + * Copyright 2016-2020 the original author 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 org.mybatis.dynamic.sql.BindableColumn; import org.mybatis.dynamic.sql.render.TableAliasCalculator; -public class Upper extends AbstractFunction { +public class Upper extends AbstractUniTypeFunction { private Upper(BindableColumn column) { super(column); diff --git a/src/test/java/examples/animal/data/AnimalDataTest.java b/src/test/java/examples/animal/data/AnimalDataTest.java index 06797371d..d53561888 100644 --- a/src/test/java/examples/animal/data/AnimalDataTest.java +++ b/src/test/java/examples/animal/data/AnimalDataTest.java @@ -827,6 +827,33 @@ void testAddConstant() { } } + @Test + void testConcatenate() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class); + + SelectStatementProvider selectStatement = select(id, concatenate(animalName, stringConstant(" - The Legend")).as("display_name")) + .from(animalData, "a") + .where(add(bodyWeight, brainWeight), isGreaterThan(10000.0)) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expected = "select a.id, (a.animal_name || ' - The Legend') as display_name " + + "from AnimalData a " + + "where (a.body_weight + a.brain_weight) > #{parameters.p1,jdbcType=DOUBLE}"; + + List> animals = mapper.generalSelect(selectStatement); + + assertAll( + () -> assertThat(selectStatement.getSelectStatement()).isEqualTo(expected), + () -> assertThat(animals).hasSize(3), + () -> assertThat(animals.get(0)).containsEntry("DISPLAY_NAME", "African elephant - The Legend"), + () -> assertThat(animals.get(1)).containsEntry("DISPLAY_NAME", "Dipliodocus - The Legend"), + () -> assertThat(animals.get(2)).containsEntry("DISPLAY_NAME", "Brachiosaurus - The Legend") + ); + } + } + @Test void testDivide() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { From c341978704914474a1329db332593b27d5a5ae31 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 24 Jul 2020 16:48:30 -0400 Subject: [PATCH 2/4] Make constants more flexible so they can appear With the refactoring of the functions, we now allow constants to appear anywhere in a column list. Previously they could not appear as the first column. --- CHANGELOG.md | 6 + .../mybatis/dynamic/sql/BindableColumn.java | 8 +- .../org/mybatis/dynamic/sql/Constant.java | 12 +- .../org/mybatis/dynamic/sql/SqlBuilder.java | 4 +- .../mybatis/dynamic/sql/StringConstant.java | 4 +- .../examples/animal/data/AnimalDataTest.java | 117 ++++++++++++++++++ .../examples/animal/data/DeprecatedAdd.java | 46 +++++++ .../dynamic/sql/BindableColumnTest.java | 31 +++++ 8 files changed, 216 insertions(+), 12 deletions(-) create mode 100644 src/test/java/examples/animal/data/DeprecatedAdd.java create mode 100644 src/test/java/org/mybatis/dynamic/sql/BindableColumnTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d78d065..566514549 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ This log will detail notable changes to MyBatis Dynamic SQL. Full details are av GitHub milestone: [https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.1.5+](https://github.com/mybatis/mybatis-dynamic-sql/issues?q=milestone%3A1.1.5+) +### General Announcements + +This release includes a significant refactoring of the classes in the "org.mybatis.dynamic.sql.select.function" package. The new classes are more consistent and flexible and should be compatible with existing code at the source level (meaning that code should be recompiled for the new version of the library). + +If you have written your own set of functions to extend the library, you will notice that the base classes 'AbstractFunction" and "AbstractMultipleColumnArithmeticFunction" are now deprecated. Their replacement classes are "AbstractUniTypeFunction" and "OperatorFunction" respectively. + ### Added - Added a general insert statement that does not require a separate record class to hold values for the insert. ([#201](https://github.com/mybatis/mybatis-dynamic-sql/issues/201)) diff --git a/src/main/java/org/mybatis/dynamic/sql/BindableColumn.java b/src/main/java/org/mybatis/dynamic/sql/BindableColumn.java index dc50921cd..7e85f8b9e 100644 --- a/src/main/java/org/mybatis/dynamic/sql/BindableColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/BindableColumn.java @@ -38,9 +38,13 @@ public interface BindableColumn extends BasicColumn { @Override BindableColumn as(String alias); - Optional jdbcType(); + default Optional jdbcType() { + return Optional.empty(); + } - Optional typeHandler(); + default Optional typeHandler() { + return Optional.empty(); + } default Optional renderingStrategy() { return Optional.empty(); diff --git a/src/main/java/org/mybatis/dynamic/sql/Constant.java b/src/main/java/org/mybatis/dynamic/sql/Constant.java index e6a56bf54..ff700f8ff 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-2018 the original author or authors. + * Copyright 2016-2020 the original author 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 org.mybatis.dynamic.sql.render.TableAliasCalculator; -public class Constant implements BasicColumn { +public class Constant implements BindableColumn { private String alias; private String value; @@ -40,13 +40,13 @@ public String renderWithTableAlias(TableAliasCalculator tableAliasCalculator) { } @Override - public Constant as(String alias) { - Constant copy = new Constant(value); + public Constant as(String alias) { + Constant copy = new Constant<>(value); copy.alias = alias; return copy; } - public static Constant of(String value) { - return new Constant(value); + public static Constant of(String value) { + return new Constant<>(value); } } diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java index 017cfe5e8..7800a7153 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java @@ -258,7 +258,7 @@ static Sum sum(BasicColumn column) { } // constants - static Constant constant(String constant) { + static Constant constant(String constant) { return Constant.of(constant); } @@ -292,7 +292,7 @@ static Concatenate concatenate(BindableColumn firstColumn, BasicColumn return Concatenate.concatenate(firstColumn, secondColumn, subsequentColumns); } - static OperatorFunction operatorFunction(String operator, BindableColumn firstColumn, BasicColumn secondColumn, + static OperatorFunction applyOperator(String operator, BindableColumn firstColumn, BasicColumn secondColumn, BasicColumn... subsequentColumns) { return OperatorFunction.of(operator, firstColumn, secondColumn, subsequentColumns); } diff --git a/src/main/java/org/mybatis/dynamic/sql/StringConstant.java b/src/main/java/org/mybatis/dynamic/sql/StringConstant.java index 00361e562..5fd50da35 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-2018 the original author or authors. + * Copyright 2016-2020 the original author 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 org.mybatis.dynamic.sql.render.TableAliasCalculator; -public class StringConstant implements BasicColumn { +public class StringConstant implements BindableColumn { private String alias; private String value; diff --git a/src/test/java/examples/animal/data/AnimalDataTest.java b/src/test/java/examples/animal/data/AnimalDataTest.java index d53561888..112dbb7fe 100644 --- a/src/test/java/examples/animal/data/AnimalDataTest.java +++ b/src/test/java/examples/animal/data/AnimalDataTest.java @@ -767,6 +767,36 @@ void testStringConstant() { } } + @Test + void testDeprecatedAdd() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class); + + SelectStatementProvider selectStatement = select(id, animalName, DeprecatedAdd.of(bodyWeight, brainWeight).as("calculated_weight")) + .from(animalData, "a") + .where(DeprecatedAdd.of(bodyWeight, brainWeight), isGreaterThan(10000.0)) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expected = "select a.id, a.animal_name, (a.body_weight + a.brain_weight) as calculated_weight " + + "from AnimalData a " + + "where (a.body_weight + a.brain_weight) > #{parameters.p1,jdbcType=DOUBLE}"; + + List> animals = mapper.generalSelect(selectStatement); + + assertAll( + () -> assertThat(selectStatement.getSelectStatement()).isEqualTo(expected), + () -> assertThat(animals).hasSize(3), + () -> assertThat(animals.get(0)).containsEntry("ANIMAL_NAME", "African elephant"), + () -> assertThat(animals.get(0)).containsEntry("CALCULATED_WEIGHT", 12366.0), + () -> assertThat(animals.get(1)).containsEntry("ANIMAL_NAME", "Dipliodocus"), + () -> assertThat(animals.get(1)).containsEntry("CALCULATED_WEIGHT", 11750.0), + () -> assertThat(animals.get(2)).containsEntry("ANIMAL_NAME", "Brachiosaurus"), + () -> assertThat(animals.get(2)).containsEntry("CALCULATED_WEIGHT", 87154.5) + ); + } + } + @Test void testAdd() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { @@ -827,6 +857,36 @@ void testAddConstant() { } } + @Test + void testAddConstantWithConstantFirst() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class); + + SelectStatementProvider selectStatement = select(id, animalName, add(constant("22"), bodyWeight, constant("33")).as("calculated_weight")) + .from(animalData, "a") + .where(add(bodyWeight, brainWeight), isGreaterThan(10000.0)) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expected = "select a.id, a.animal_name, (22 + a.body_weight + 33) as calculated_weight " + + "from AnimalData a " + + "where (a.body_weight + a.brain_weight) > #{parameters.p1,jdbcType=DOUBLE}"; + + List> animals = mapper.generalSelect(selectStatement); + + assertAll( + () -> assertThat(selectStatement.getSelectStatement()).isEqualTo(expected), + () -> assertThat(animals).hasSize(3), + () -> assertThat(animals.get(0)).containsEntry("ANIMAL_NAME", "African elephant"), + () -> assertThat(animals.get(0)).containsEntry("CALCULATED_WEIGHT", 5767.0), + () -> assertThat(animals.get(1)).containsEntry("ANIMAL_NAME", "Dipliodocus"), + () -> assertThat(animals.get(1)).containsEntry("CALCULATED_WEIGHT", 105.0), + () -> assertThat(animals.get(2)).containsEntry("ANIMAL_NAME", "Brachiosaurus"), + () -> assertThat(animals.get(2)).containsEntry("CALCULATED_WEIGHT", 209.5) + ); + } + } + @Test void testConcatenate() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { @@ -854,6 +914,33 @@ void testConcatenate() { } } + @Test + void testConcatenateConstantFirst() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class); + + SelectStatementProvider selectStatement = select(id, concatenate(stringConstant("Name: "), animalName).as("display_name")) + .from(animalData, "a") + .where(add(bodyWeight, brainWeight), isGreaterThan(10000.0)) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expected = "select a.id, ('Name: ' || a.animal_name) as display_name " + + "from AnimalData a " + + "where (a.body_weight + a.brain_weight) > #{parameters.p1,jdbcType=DOUBLE}"; + + List> animals = mapper.generalSelect(selectStatement); + + assertAll( + () -> assertThat(selectStatement.getSelectStatement()).isEqualTo(expected), + () -> assertThat(animals).hasSize(3), + () -> assertThat(animals.get(0)).containsEntry("DISPLAY_NAME", "Name: African elephant"), + () -> assertThat(animals.get(1)).containsEntry("DISPLAY_NAME", "Name: Dipliodocus"), + () -> assertThat(animals.get(2)).containsEntry("DISPLAY_NAME", "Name: Brachiosaurus") + ); + } + } + @Test void testDivide() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { @@ -1034,6 +1121,36 @@ void testSubtractConstant() { } } + @Test + void testGeneralOperator() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class); + + SelectStatementProvider selectStatement = select(id, animalName, applyOperator("-", bodyWeight, brainWeight).as("calculated_weight")) + .from(animalData, "a") + .where(add(bodyWeight, brainWeight), isGreaterThan(10000.0)) + .build() + .render(RenderingStrategies.MYBATIS3); + + String expected = "select a.id, a.animal_name, (a.body_weight - a.brain_weight) as calculated_weight " + + "from AnimalData a " + + "where (a.body_weight + a.brain_weight) > #{parameters.p1,jdbcType=DOUBLE}"; + + List> animals = mapper.generalSelect(selectStatement); + + assertAll( + () -> assertThat(selectStatement.getSelectStatement()).isEqualTo(expected), + () -> assertThat(animals).hasSize(3), + () -> assertThat(animals.get(0)).containsEntry("ANIMAL_NAME", "African elephant"), + () -> assertThat(animals.get(0)).containsEntry("CALCULATED_WEIGHT", -942.0), + () -> assertThat(animals.get(1)).containsEntry("ANIMAL_NAME", "Dipliodocus"), + () -> assertThat(animals.get(1)).containsEntry("CALCULATED_WEIGHT", -11650.0), + () -> assertThat(animals.get(2)).containsEntry("ANIMAL_NAME", "Brachiosaurus"), + () -> assertThat(animals.get(2)).containsEntry("CALCULATED_WEIGHT", -86845.5) + ); + } + } + @Test void testComplexExpression() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { diff --git a/src/test/java/examples/animal/data/DeprecatedAdd.java b/src/test/java/examples/animal/data/DeprecatedAdd.java new file mode 100644 index 000000000..f1bbb13d5 --- /dev/null +++ b/src/test/java/examples/animal/data/DeprecatedAdd.java @@ -0,0 +1,46 @@ +/** + * Copyright 2016-2020 the original author 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package examples.animal.data; + +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.AbstractMultipleColumnArithmeticFunction; + +public class DeprecatedAdd extends AbstractMultipleColumnArithmeticFunction> { + + private DeprecatedAdd(BindableColumn firstColumn, BasicColumn secondColumn, + List subsequentColumns) { + super(firstColumn, secondColumn, subsequentColumns); + } + + @Override + protected DeprecatedAdd copy() { + return new DeprecatedAdd<>(column, secondColumn, subsequentColumns); + } + + @Override + protected String operator() { + return "+"; //$NON-NLS-1$ + } + + public static DeprecatedAdd of(BindableColumn firstColumn, BasicColumn secondColumn, + BasicColumn... subsequentColumns) { + return new DeprecatedAdd<>(firstColumn, secondColumn, Arrays.asList(subsequentColumns)); + } +} diff --git a/src/test/java/org/mybatis/dynamic/sql/BindableColumnTest.java b/src/test/java/org/mybatis/dynamic/sql/BindableColumnTest.java new file mode 100644 index 000000000..46c85cbf4 --- /dev/null +++ b/src/test/java/org/mybatis/dynamic/sql/BindableColumnTest.java @@ -0,0 +1,31 @@ +/** + * Copyright 2016-2020 the original author 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "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 static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class BindableColumnTest { + + @Test + void testDefaultFunctions() { + StringConstant constant = StringConstant.of("Fred"); + + assertThat(constant.jdbcType()).isEmpty(); + assertThat(constant.typeHandler()).isEmpty(); + } +} From 239e89135308933406e92dfae6f67e954b14baf7 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 24 Jul 2020 17:40:03 -0400 Subject: [PATCH 3/4] Documenation for updated function support --- src/site/markdown/docs/extending.md | 115 ++++++++++++++++++++-------- src/site/markdown/docs/functions.md | 26 +++++++ src/site/site.xml | 1 + 3 files changed, 109 insertions(+), 33 deletions(-) create mode 100644 src/site/markdown/docs/functions.md diff --git a/src/site/markdown/docs/extending.md b/src/site/markdown/docs/extending.md index a20560cf3..c04a572ab 100644 --- a/src/site/markdown/docs/extending.md +++ b/src/site/markdown/docs/extending.md @@ -21,7 +21,7 @@ A calculated column can be used anywhere in a SELECT statement. If you don't ne ```java public class CountAll implements BasicColumn { - private Optional alias = Optional.empty(); + private String alias; public CountAll() { super(); @@ -34,13 +34,13 @@ public class CountAll implements BasicColumn { @Override public Optional alias() { - return alias; + return Optional.ofNullable(alias); } @Override public CountAll as(String alias) { CountAll copy = new CountAll(); - copy.alias = Optional.of(alias); + copy.alias = alias; return copy; } } @@ -52,64 +52,113 @@ This class is used to implement the `count(*)` function in a SELECT list. There 2. `as` - this method can be called by the user to add an alias to the column expression. In the method you should return a new instance of the object, with the alias passed by the user. 3. `alias` - this method is called by the default renderer to obtain the column alias for the select list. If there is no alias, then returning Optional.empty() will disable setting a column alias. -### Writing a Custom Where Condition +## Writing Custom Functions -If you want to use your calculated column in a WHERE clause in addition the select list and the GROUP BY clause, then you must implement `org.mybatis.dynamic.sql.BindableColumn`. This interface extends the `BasicColumn` interface from above and adds two methods. An example from the library is the `org.mybatis.dynamic.sql.select.function.Add` class: +Relational database vendors provide hundreds of functions in their SQL dialects to aid with queries and offload processing to the database servers. This library does not try to implement every function that exists. This library also does not provide any abstraction over the different functions on different databases. For example, bitwise operator support is non-standard and it would be difficult to provide a function in this library that worked on every database. So we take the approach of supplying examples for a few very common functions, and making it relatively easy to write your own functions. + +The supplied functions are all in the `org.mybatis.dynamic.sql.select.function` package. They are all implemented as `BindableColumn` - meaning that they can appear in a select list or a where clause. + +We provide some base classes that you can easily extend to write functions of your own. Those classes are as follows: + +Note: the base classes are all in the `org.mybatis.dynamic.sql.select.function` package. + +| Interface | Purpose| +|-----------|--------| +| `o.m.d.s.s.f.AbstractTypeConvertingFunction` | Extend this class if you want to build a function that changes a column data type. For example, using a database function to calculate the Base64 String for a binary field. | +| `o.m.d.s.s.f.AbstractUniTypeFunction` | Extend this class if you want to build a function that does not change a column data type. For example UPPER(), LOWER(), etc. | +| `o.m.d.s.s.f.OperatorFunction` | Extend this class if you want to build a function the implements an operator. For example column1 + column2. | + +### AbstractTypeConvertingFunction Example + +The following function uses HSQLDB's `TO_BASE64` function to calculate the BASE64 string for a binary field. Note that the function changes the data type from `byte[]` to `String`. ```java -public class Add implements BindableColumn { - - private Optional alias = Optional.empty(); - private BindableColumn column1; - private BindableColumn column2; - - private Add(BindableColumn column1, BindableColumn column2) { - this.column1 = Objects.requireNonNull(column1); - this.column2 = Objects.requireNonNull(column2); +public class ToBase64 extends AbstractTypeConvertingFunction { + + protected ToBase64(BindableColumn column) { + super(column); } @Override - public Optional alias() { - return alias; + public Optional jdbcType() { + return Optional.of(JDBCType.VARCHAR); + } + + @Override + public Optional typeHandler() { + return Optional.empty(); } @Override public String renderWithTableAlias(TableAliasCalculator tableAliasCalculator) { - return column1.applyTableAliasToName(tableAliasCalculator) - + " + " //$NON-NLS-1$ - + column2.applyTableAliasToName(tableAliasCalculator); + return "TO_BASE64(" //$NON-NLS-1$ + + column.renderWithTableAlias(tableAliasCalculator) + + ")"; //$NON-NLS-1$ } @Override - public BindableColumn as(String alias) { - Add newColumn = new Add<>(column1, column2); - newColumn.alias = Optional.of(alias); - return newColumn; + protected ToBase64 copy() { + return new ToBase64(column); + } + + public static ToBase64 toBase64(BindableColumn column) { + return new ToBase64(column); + } +} +``` + +### AbstractUniTypeFunction Example + +The following function implements the common database `UPPER()` function. + +```java +public class Upper extends AbstractUniTypeFunction { + + private Upper(BindableColumn column) { + super(column); } @Override - public JDBCType jdbcType() { - return column1.jdbcType(); + public String renderWithTableAlias(TableAliasCalculator tableAliasCalculator) { + return "upper(" //$NON-NLS-1$ + + column.renderWithTableAlias(tableAliasCalculator) + + ")"; //$NON-NLS-1$ } @Override - public Optional typeHandler() { - return column1.typeHandler(); + protected Upper copy() { + return new Upper(column); } - public static Add of(BindableColumn column1, BindableColumn column2) { - return new Add<>(column1, column2); + public static Upper of(BindableColumn column) { + return new Upper(column); } } ``` -This class implements the idea of adding two numeric columns together in a SELECT statement. This class accepts two other columns as parameters and then overrides `renderWithTableAlias` to render the addition. The `alias` and `as` methods work as described above. +### OperatorFunction Example -The two additional methods are: +The following function implements the concatenate operator. Note that the operator can be applied to list of columns of arbitrary length: -1. `jdbcType` - returns the JDBC Type of the column for the WHERE clause. This is used by the MyBatis3 rendering strategy to render a full MyBatis parameter expression. -2. `typeHandler` - returns a type handler if specified by the user. Again, this is used by the MyBatis3 rendering strategy to render a full MyBatis parameter expression. +```java +public class Concatenate extends OperatorFunction { + + protected Concatenate(BindableColumn firstColumn, BasicColumn secondColumn, + List subsequentColumns) { + super("||", firstColumn, secondColumn, subsequentColumns); //$NON-NLS-1$ + } + @Override + protected Concatenate copy() { + return new Concatenate<>(column, secondColumn, subsequentColumns); + } + + public static Concatenate concatenate(BindableColumn firstColumn, BasicColumn secondColumn, + BasicColumn... subsequentColumns) { + return new Concatenate<>(firstColumn, secondColumn, Arrays.asList(subsequentColumns)); + } +} +``` ## Writing Custom Rendering Strategies diff --git a/src/site/markdown/docs/functions.md b/src/site/markdown/docs/functions.md new file mode 100644 index 000000000..7613c0f6e --- /dev/null +++ b/src/site/markdown/docs/functions.md @@ -0,0 +1,26 @@ +# Database Functions + +The library supplies implementations for several common database functions. We do not try to implement a large set of functions. Rather we supply some common functions as examples and make it relatively easy to write your own implementations of functions you might want to use that are not supplied by the library. See the page "Extending the Library" for informtion about how to write your own functions. + +The supplied functions are all in the `org.mybatis.dynamic.sql.select.function` package. In addition, there are static methods in the `SqlBuilder` to access all functions. + +In the following table... + +- "Function Class" is implementing class in the `org.mybatis.dynamic.sql.select.function` package +- "Example" shows the static method from the `SqlBuilder` class +- "Rendered Result" shows the rendered SQL fragment generated by the function + + +| Function Class | Example | Rendered Result | +|----------|---------|--------| +| Add | add(column1, column2, constant(55)) | column1 + column2 + 55 | +| Concatenate | concatenate(stringConstant("Name: ", column1) | 'Name: ' \|\| column1 | +| Divide | divide(column1, column2, constant(55)) | column1 / column2 / 55 | +| Lower | lower(column1) | lower(column1) | +| Multiply | multiply(column1, column2, constant(55)) | column1 * column2 * 55 | +| OperatorFunction | applyOperator("^", column1, column2) | column1 ^ column2 | +| Substring | substring(column1, 5, 7) | substring(column1, 5, 7) | +| Subtract | subtract(column1, column2, constant(55)) | column1 - column2 - 55 | +| Upper | upper(column1) | upper(column1) | + +Note especially the `OperatorFunction` - you can use this function to easily implement operators supported by your database. For example, MySQL supports a number of bitwise operators that can be easily implemented with this function. diff --git a/src/site/site.xml b/src/site/site.xml index d5a98930b..551e6c9ae 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -47,6 +47,7 @@ + From 13e5bb61b6768959953f03163d2bf6ced3655be0 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 24 Jul 2020 17:49:31 -0400 Subject: [PATCH 4/4] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 566514549..9a789da08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ If you have written your own set of functions to extend the library, you will no - Added a general insert statement that does not require a separate record class to hold values for the insert. ([#201](https://github.com/mybatis/mybatis-dynamic-sql/issues/201)) - Added the capability to specify a rendering strategy on a column to override the defaut rendering strategy for a statement. This will allow certain edge cases where a parameter marker needs to be formatted in a unique way (for example, "::jsonb" needs to be added to parameter markers for JSON fields in PostgreSQL) ([#200](https://github.com/mybatis/mybatis-dynamic-sql/issues/200)) - Added the ability to write a function that will change the column data type ([#197](https://github.com/mybatis/mybatis-dynamic-sql/issues/197)) +- Added the `applyOperator` function to make it easy to use non-standard database operators in expressions ([#220](https://github.com/mybatis/mybatis-dynamic-sql/issues/220)) ## Release 1.1.4 - November 23, 2019