leftColumn) {
+ RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(leftColumn);
+ return FragmentAndParameters.withFragment(parameterInfo.renderedPlaceHolder())
+ .withParameter(parameterInfo.parameterMapKey(), leftColumn.convertParameterType(value))
+ .build();
+ }
+
+ /**
+ * 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 AbstractListValueCondition#filterSupport(Predicate, Function, AbstractListValueCondition, Supplier)} as
+ * a common implementation of the filtering algorithm.
+ *
+ * @param the Java type related to the database column type
+ */
+ 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 super T> predicate);
+ }
+
+ /**
+ * 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 super T, ? extends R> mapper);
+ }
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java
index 9d483742f..71daa7763 100644
--- a/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java
+++ b/src/main/java/org/mybatis/dynamic/sql/AbstractNoValueCondition.java
@@ -1,11 +1,11 @@
-/**
- * Copyright 2016-2018 the original author or authors.
+/*
+ * Copyright 2016-2025 the original author 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
+ * 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,
@@ -15,30 +15,54 @@
*/
package org.mybatis.dynamic.sql;
-import java.util.Objects;
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;
- private BooleanSupplier booleanSupplier;
-
- protected AbstractNoValueCondition() {
- booleanSupplier = () -> true;
- }
-
- protected AbstractNoValueCondition(BooleanSupplier booleanSupplier) {
- this.booleanSupplier = Objects.requireNonNull(booleanSupplier);
+public abstract class AbstractNoValueCondition implements RenderableCondition {
+
+ protected > S filterSupport(BooleanSupplier booleanSupplier,
+ Supplier emptySupplier, S self) {
+ if (isEmpty()) {
+ return self;
+ } else {
+ return booleanSupplier.getAsBoolean() ? self : emptySupplier.get();
+ }
}
-
+
+ public abstract String operator();
+
@Override
- public boolean shouldRender() {
- return booleanSupplier.getAsBoolean();
+ public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn leftColumn) {
+ return FragmentAndParameters.fromFragment(operator());
}
-
- @Override
- public R accept(ConditionVisitor visitor) {
- return visitor.visit(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.
+ *
+ *
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);
}
-
- public abstract String renderCondition(String columnName);
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java
index fc6fc4630..c16dbf08c 100644
--- a/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java
+++ b/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java
@@ -1,11 +1,11 @@
-/**
- * Copyright 2016-2018 the original author or authors.
+/*
+ * Copyright 2016-2025 the original author 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
+ * 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,
@@ -15,37 +15,104 @@
*/
package org.mybatis.dynamic.sql;
-import java.util.Objects;
+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 {
- protected Supplier valueSupplier;
- private Predicate predicate;
-
- protected AbstractSingleValueCondition(Supplier valueSupplier) {
- this.valueSupplier = Objects.requireNonNull(valueSupplier);
- predicate = v -> true;
- }
-
- protected AbstractSingleValueCondition(Supplier valueSupplier, Predicate predicate) {
- this.valueSupplier = Objects.requireNonNull(valueSupplier);
- this.predicate = Objects.requireNonNull(predicate);
+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) {
+ this.value = value;
}
-
+
public T value() {
- return valueSupplier.get();
+ return value;
}
-
- @Override
- public boolean shouldRender() {
- return predicate.test(value());
+
+ protected > S filterSupport(Predicate super T> predicate,
+ Supplier emptySupplier, S self) {
+ if (isEmpty()) {
+ return self;
+ } else {
+ return predicate.test(value) ? self : emptySupplier.get();
+ }
+ }
+
+ protected > S mapSupport(Function super T, ? extends R> mapper,
+ Function constructor, Supplier emptySupplier) {
+ if (isEmpty()) {
+ return emptySupplier.get();
+ } else {
+ return constructor.apply(mapper.apply(value));
+ }
}
-
+
+ public abstract String operator();
+
@Override
- public R accept(ConditionVisitor visitor) {
- return visitor.visit(this);
+ public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn leftColumn) {
+ RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(leftColumn);
+ String finalFragment = operator() + spaceBefore(parameterInfo.renderedPlaceHolder());
+
+ return FragmentAndParameters.withFragment(finalFragment)
+ .withParameter(parameterInfo.parameterMapKey(), leftColumn.convertParameterType(value()))
+ .build();
+ }
+
+ /**
+ * 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 AbstractSingleValueCondition#filterSupport(Predicate, Supplier, AbstractSingleValueCondition)} as
+ * a common implementation of the filtering algorithm.
+ *
+ * @param the Java type related to the database column type
+ */
+ 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 super T> predicate);
+ }
+
+ /**
+ * 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 super T, ? extends R> mapper);
}
-
- public abstract String renderCondition(String columnName, String placeholder);
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractSubselectCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractSubselectCondition.java
index 44de4f445..dcfbd4b3c 100644
--- a/src/main/java/org/mybatis/dynamic/sql/AbstractSubselectCondition.java
+++ b/src/main/java/org/mybatis/dynamic/sql/AbstractSubselectCondition.java
@@ -1,11 +1,11 @@
-/**
- * Copyright 2016-2017 the original author or authors.
+/*
+ * Copyright 2016-2025 the original author 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
+ * 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,
@@ -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 RenderableCondition {
+ private final SelectModel selectModel;
-public abstract class AbstractSubselectCondition implements VisitableCondition {
- private 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 renderCondition(String columnName, String renderedSelectStatement);
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java
index d31333fd0..6cceff16e 100644
--- a/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java
+++ b/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java
@@ -1,11 +1,11 @@
-/**
- * Copyright 2016-2018 the original author or authors.
+/*
+ * Copyright 2016-2025 the original author 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
+ * 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,
@@ -15,44 +15,150 @@
*/
package org.mybatis.dynamic.sql;
-import java.util.Objects;
+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 {
- protected Supplier valueSupplier1;
- protected Supplier valueSupplier2;
- private BiPredicate predicate;
-
- protected AbstractTwoValueCondition(Supplier valueSupplier1, Supplier valueSupplier2) {
- this.valueSupplier1 = Objects.requireNonNull(valueSupplier1);
- this.valueSupplier2 = Objects.requireNonNull(valueSupplier2);
- predicate = (v1, v2) -> true;
- }
+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;
- protected AbstractTwoValueCondition(Supplier valueSupplier1, Supplier valueSupplier2,
- BiPredicate predicate) {
- this(valueSupplier1, valueSupplier2);
- this.predicate = Objects.requireNonNull(predicate);
+ protected AbstractTwoValueCondition(T value1, T value2) {
+ this.value1 = value1;
+ this.value2 = value2;
}
public T value1() {
- return valueSupplier1.get();
+ return value1;
}
public T value2() {
- return valueSupplier2.get();
+ return value2;
}
-
- @Override
- public boolean shouldRender() {
- return predicate.test(value1(), value2());
+
+ protected > S filterSupport(BiPredicate super T, ? super T> predicate,
+ Supplier emptySupplier, S self) {
+ if (isEmpty()) {
+ return self;
+ } else {
+ return predicate.test(value1, value2) ? self : emptySupplier.get();
+ }
}
-
+
+ protected > S filterSupport(Predicate super T> predicate,
+ Supplier emptySupplier, S self) {
+ return filterSupport((v1, v2) -> predicate.test(v1) && predicate.test(v2), emptySupplier, self);
+ }
+
+ protected > S mapSupport(Function super T, ? extends R> mapper1,
+ Function super T, ? extends R> mapper2, BiFunction constructor, Supplier emptySupplier) {
+ if (isEmpty()) {
+ return emptySupplier.get();
+ } else {
+ return constructor.apply(mapper1.apply(value1), mapper2.apply(value2));
+ }
+ }
+
+ public abstract String operator1();
+
+ public abstract String operator2();
+
@Override
- public R accept(ConditionVisitor visitor) {
- return visitor.visit(this);
+ public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn 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();
}
- public abstract String renderCondition(String columnName, String placeholder1, String placeholder2);
+ /**
+ * 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 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 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 super T, ? super T> 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 super T> predicate);
+ }
+
+ /**
+ * 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 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 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 super T, ? extends R> mapper1,
+ Function super T, ? extends R> mapper2);
+
+ /**
+ * 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 super T, ? extends R> mapper);
+ }
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/AliasableSqlTable.java b/src/main/java/org/mybatis/dynamic/sql/AliasableSqlTable.java
new file mode 100644
index 000000000..915b6a561
--- /dev/null
+++ b/src/main/java/org/mybatis/dynamic/sql/AliasableSqlTable.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-2025 the original author 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 java.util.function.Supplier;
+
+import org.jspecify.annotations.Nullable;
+
+public abstract class AliasableSqlTable> extends SqlTable {
+
+ private @Nullable String tableAlias;
+ private final Supplier constructor;
+
+ protected AliasableSqlTable(String tableName, Supplier constructor) {
+ super(tableName);
+ this.constructor = Objects.requireNonNull(constructor);
+ }
+
+ public T withAlias(String alias) {
+ T newTable = constructor.get();
+ ((AliasableSqlTable) newTable).tableAlias = alias;
+ newTable.tableName = tableName;
+ return newTable;
+ }
+
+ /**
+ * Returns a new instance of this table with the specified name. All column instances are recreated.
+ * This is useful for sharding where the table name may change at runtime based on some sharding algorithm,
+ * but all other table attributes are the same.
+ *
+ * @param name new name for the table
+ * @return a new AliasableSqlTable with the specified name, all other table attributes are copied
+ */
+ public T withName(String name) {
+ Objects.requireNonNull(name);
+ T newTable = constructor.get();
+ ((AliasableSqlTable) newTable).tableAlias = tableAlias;
+ newTable.tableName = name;
+ return newTable;
+ }
+
+ @Override
+ public Optional tableAlias() {
+ return Optional.ofNullable(tableAlias);
+ }
+}
diff --git a/src/main/java/org/mybatis/dynamic/sql/AndOrCriteriaGroup.java b/src/main/java/org/mybatis/dynamic/sql/AndOrCriteriaGroup.java
new file mode 100644
index 000000000..ff630a036
--- /dev/null
+++ b/src/main/java/org/mybatis/dynamic/sql/AndOrCriteriaGroup.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-2025 the original author 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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+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
+ * available where an AND or an OR condition is appropriate.
+ *
+ * @author Jeff Butler
+ *
+ * @since 1.4.0
+ */
+public class AndOrCriteriaGroup {
+ private final String connector;
+ private final @Nullable SqlCriterion initialCriterion;
+ private final List subCriteria;
+
+ private AndOrCriteriaGroup(Builder builder) {
+ connector = Objects.requireNonNull(builder.connector);
+ initialCriterion = builder.initialCriterion;
+ subCriteria = builder.subCriteria;
+ }
+
+ public String connector() {
+ return connector;
+ }
+
+ public Optional initialCriterion() {
+ return Optional.ofNullable(initialCriterion);
+ }
+
+ public List subCriteria() {
+ return Collections.unmodifiableList(subCriteria);
+ }
+
+ public static class Builder {
+ private @Nullable String connector;
+ private @Nullable SqlCriterion initialCriterion;
+ private final List subCriteria = new ArrayList<>();
+
+ public Builder withConnector(String connector) {
+ this.connector = connector;
+ return this;
+ }
+
+ public Builder withInitialCriterion(@Nullable SqlCriterion initialCriterion) {
+ this.initialCriterion = initialCriterion;
+ return this;
+ }
+
+ public Builder withSubCriteria(List subCriteria) {
+ this.subCriteria.addAll(subCriteria);
+ return this;
+ }
+
+ public AndOrCriteriaGroup build() {
+ return new AndOrCriteriaGroup(this);
+ }
+ }
+}
diff --git a/src/main/java/org/mybatis/dynamic/sql/BasicColumn.java b/src/main/java/org/mybatis/dynamic/sql/BasicColumn.java
index 80b8e0d70..2573b4529 100644
--- a/src/main/java/org/mybatis/dynamic/sql/BasicColumn.java
+++ b/src/main/java/org/mybatis/dynamic/sql/BasicColumn.java
@@ -1,11 +1,11 @@
-/**
- * Copyright 2016-2019 the original author or authors.
+/*
+ * Copyright 2016-2025 the original author 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
+ * 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,
@@ -15,66 +15,71 @@
*/
package org.mybatis.dynamic.sql;
+import java.sql.JDBCType;
import java.util.Optional;
-import org.mybatis.dynamic.sql.render.TableAliasCalculator;
+import org.mybatis.dynamic.sql.render.RenderingContext;
+import org.mybatis.dynamic.sql.render.RenderingStrategy;
+import org.mybatis.dynamic.sql.util.FragmentAndParameters;
/**
* Describes attributes of columns that are necessary for rendering if the column is not expected to
* be bound as a JDBC parameter. Columns in select lists, join expressions, and group by expressions
* are typically not bound.
- *
- * @author Jeff Butler
*
+ * @author Jeff Butler
*/
public interface BasicColumn {
/**
* Returns the columns alias if one has been specified.
- *
+ *
* @return the column alias
*/
Optional alias();
-
+
/**
* Returns a new instance of a BasicColumn with the alias set.
- *
- * @param alias the column alias to set
+ *
+ * @param alias
+ * the column alias to set
+ *
* @return new instance with alias set
*/
BasicColumn as(String alias);
-
- /**
- * 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
- */
- String renderWithTableAlias(TableAliasCalculator tableAliasCalculator);
-
+
/**
- * Returns the name of the item aliased with a table name and column alias if appropriate.
- * For example, "a.foo as bar". This is appropriate for select list clauses.
- *
- * @param tableAliasCalculator the table alias calculator for the current renderer
- * @return the item name with the table and column aliases applied
+ * Returns a rendering of the column.
+ * The rendered fragment should include the table alias based on the TableAliasCalculator
+ * in the RenderingContext. The fragment could contain prepared statement parameter
+ * markers and associated parameter values if desired.
+ *
+ * @param renderingContext the rendering context (strategy, sequence, etc.)
+ * @return a rendered SQL fragment and, optionally, parameters associated with the fragment
+ * @since 1.5.1
*/
- default String renderWithTableAndColumnAlias(TableAliasCalculator tableAliasCalculator) {
- String nameAndTableAlias = renderWithTableAlias(tableAliasCalculator);
-
- return alias().map(a -> nameAndTableAlias + " as " + a) //$NON-NLS-1$
- .orElse(nameAndTableAlias);
+ FragmentAndParameters render(RenderingContext renderingContext);
+
+ default Optional jdbcType() {
+ return Optional.empty();
+ }
+
+ default Optional typeHandler() {
+ return Optional.empty();
}
-
+
+ default Optional renderingStrategy() {
+ return Optional.empty();
+ }
+
/**
* Utility method to make it easier to build column lists for methods that require an
* array rather than the varargs method.
- *
+ *
* @param columns list of BasicColumn
* @return an array of BasicColumn
*/
- static BasicColumn[] columnList(BasicColumn...columns) {
+ static BasicColumn[] columnList(BasicColumn... columns) {
return columns;
}
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/BindableColumn.java b/src/main/java/org/mybatis/dynamic/sql/BindableColumn.java
index 00c53364a..0fd90b7c8 100644
--- a/src/main/java/org/mybatis/dynamic/sql/BindableColumn.java
+++ b/src/main/java/org/mybatis/dynamic/sql/BindableColumn.java
@@ -1,11 +1,11 @@
-/**
- * Copyright 2016-2018 the original author or authors.
+/*
+ * Copyright 2016-2025 the original author 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
+ * 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,
@@ -15,28 +15,32 @@
*/
package org.mybatis.dynamic.sql;
-import java.sql.JDBCType;
import java.util.Optional;
+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
*
- * @param - even though the type is not directly used in this class,
- * it is used by the compiler to match columns with conditions so it should
- * not be removed.
-*/
+ * @param
+ * - the Java type that corresponds to this column
+ */
public interface BindableColumn extends BasicColumn {
/**
- * Override the base method definition to make it more specific to this interface.
+ * Override the base method definition to make it more specific to this interface.
*/
@Override
BindableColumn as(String alias);
- Optional jdbcType();
-
- Optional typeHandler();
+ default @Nullable Object convertParameterType(T value) {
+ return value;
+ }
+
+ default Optional> javaType() {
+ return Optional.empty();
+ }
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/BoundValue.java b/src/main/java/org/mybatis/dynamic/sql/BoundValue.java
new file mode 100644
index 000000000..5151a5dad
--- /dev/null
+++ b/src/main/java/org/mybatis/dynamic/sql/BoundValue.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016-2025 the original author 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.mybatis.dynamic.sql.exception.InvalidSqlException;
+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.Messages;
+
+/**
+ * BoundValues are added to rendered SQL as a parameter marker only.
+ *
+ * BoundValues are most useful in the context of functions. For example, a column value could be
+ * incremented with an update statement like this:
+ *
+ * UpdateStatementProvider updateStatement = update(person)
+ * .set(age).equalTo(add(age, value(1)))
+ * .where(id, isEqualTo(5))
+ * .build()
+ * .render(RenderingStrategies.MYBATIS3);
+ *
+ *
+ * @param the column type
+ * @since 1.5.1
+ */
+public class BoundValue implements BindableColumn {
+ private final T value;
+
+ private BoundValue(T value) {
+ this.value = Objects.requireNonNull(value);
+ }
+
+ @Override
+ public FragmentAndParameters render(RenderingContext renderingContext) {
+ RenderedParameterInfo rpi = renderingContext.calculateParameterInfo(this);
+ return FragmentAndParameters.withFragment(rpi.renderedPlaceHolder())
+ .withParameter(rpi.parameterMapKey(), value)
+ .build();
+ }
+
+ @Override
+ public Optional alias() {
+ return Optional.empty();
+ }
+
+ @Override
+ public BoundValue as(String alias) {
+ throw new InvalidSqlException(Messages.getString("ERROR.38")); //$NON-NLS-1$
+ }
+
+ public static BoundValue of(T value) {
+ return new BoundValue<>(value);
+ }
+}
diff --git a/src/main/java/org/mybatis/dynamic/sql/ColumnAndConditionCriterion.java b/src/main/java/org/mybatis/dynamic/sql/ColumnAndConditionCriterion.java
new file mode 100644
index 000000000..053c18f64
--- /dev/null
+++ b/src/main/java/org/mybatis/dynamic/sql/ColumnAndConditionCriterion.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;
+
+import java.util.Objects;
+
+import org.jspecify.annotations.Nullable;
+
+public class ColumnAndConditionCriterion extends SqlCriterion {
+ private final BindableColumn column;
+ private final RenderableCondition condition;
+
+ private ColumnAndConditionCriterion(Builder builder) {
+ super(builder);
+ column = Objects.requireNonNull(builder.column);
+ condition = Objects.requireNonNull(builder.condition);
+ }
+
+ public BindableColumn column() {
+ return column;
+ }
+
+ public RenderableCondition condition() {
+ return condition;
+ }
+
+ @Override
+ public R accept(SqlCriterionVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ public static Builder withColumn(BindableColumn column) {
+ return new Builder().withColumn(column);
+ }
+
+ public static class Builder extends AbstractBuilder> {
+ private @Nullable BindableColumn column;
+ private @Nullable RenderableCondition condition;
+
+ public Builder withColumn(BindableColumn column) {
+ this.column = column;
+ return this;
+ }
+
+ public Builder withCondition(RenderableCondition condition) {
+ this.condition = condition;
+ return this;
+ }
+
+ @Override
+ protected Builder getThis() {
+ return this;
+ }
+
+ public ColumnAndConditionCriterion build() {
+ return new ColumnAndConditionCriterion<>(this);
+ }
+ }
+}
diff --git a/src/main/java/org/mybatis/dynamic/sql/Constant.java b/src/main/java/org/mybatis/dynamic/sql/Constant.java
index e6a56bf54..90547765f 100644
--- a/src/main/java/org/mybatis/dynamic/sql/Constant.java
+++ b/src/main/java/org/mybatis/dynamic/sql/Constant.java
@@ -1,11 +1,11 @@
-/**
- * Copyright 2016-2018 the original author or authors.
+/*
+ * Copyright 2016-2025 the original author 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
+ * 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,
@@ -18,15 +18,22 @@
import java.util.Objects;
import java.util.Optional;
-import org.mybatis.dynamic.sql.render.TableAliasCalculator;
+import org.jspecify.annotations.Nullable;
+import org.mybatis.dynamic.sql.render.RenderingContext;
+import org.mybatis.dynamic.sql.util.FragmentAndParameters;
-public class Constant implements BasicColumn {
+public class Constant implements BindableColumn {
+
+ private final @Nullable String alias;
+ private final String value;
- private String alias;
- private String value;
-
private Constant(String value) {
+ this(value, null);
+ }
+
+ private Constant(String value, @Nullable String alias) {
this.value = Objects.requireNonNull(value);
+ this.alias = alias;
}
@Override
@@ -35,18 +42,16 @@ public Optional alias() {
}
@Override
- public String renderWithTableAlias(TableAliasCalculator tableAliasCalculator) {
- return value;
+ public FragmentAndParameters render(RenderingContext renderingContext) {
+ return FragmentAndParameters.fromFragment(value);
}
@Override
- public Constant as(String alias) {
- Constant copy = new Constant(value);
- copy.alias = alias;
- return copy;
+ public Constant as(String alias) {
+ return new Constant<>(value, alias);
}
-
- 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/CriteriaGroup.java b/src/main/java/org/mybatis/dynamic/sql/CriteriaGroup.java
new file mode 100644
index 000000000..476cb6b3f
--- /dev/null
+++ b/src/main/java/org/mybatis/dynamic/sql/CriteriaGroup.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016-2025 the original author 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.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
+ * as in an expression like ((A < 5 and B > 6) or C = 3)
+ *
+ * @author Jeff Butler, inspired by @JoshuaJeme
+ *
+ * @since 1.4.0
+ */
+public class CriteriaGroup extends SqlCriterion {
+ private final @Nullable SqlCriterion initialCriterion;
+
+ protected CriteriaGroup(AbstractGroupBuilder> builder) {
+ super(builder);
+ initialCriterion = builder.initialCriterion;
+ }
+
+ public Optional initialCriterion() {
+ return Optional.ofNullable(initialCriterion);
+ }
+
+ @Override
+ public R accept(SqlCriterionVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ public abstract static class AbstractGroupBuilder> extends AbstractBuilder {
+ private @Nullable SqlCriterion initialCriterion;
+
+ public T withInitialCriterion(@Nullable SqlCriterion initialCriterion) {
+ this.initialCriterion = initialCriterion;
+ return getThis();
+ }
+ }
+
+ public static class Builder extends AbstractGroupBuilder {
+ public CriteriaGroup build() {
+ return new CriteriaGroup(this);
+ }
+
+ @Override
+ protected Builder getThis() {
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/org/mybatis/dynamic/sql/DerivedColumn.java b/src/main/java/org/mybatis/dynamic/sql/DerivedColumn.java
new file mode 100644
index 000000000..df19b8d6b
--- /dev/null
+++ b/src/main/java/org/mybatis/dynamic/sql/DerivedColumn.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2016-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mybatis.dynamic.sql;
+
+import java.sql.JDBCType;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.jspecify.annotations.Nullable;
+import org.mybatis.dynamic.sql.render.RenderingContext;
+import org.mybatis.dynamic.sql.util.FragmentAndParameters;
+
+/**
+ * A derived column is a column that is not directly related to a table. This is primarily
+ * used for supporting sub-queries. The main difference in this class and {@link SqlColumn} is
+ * that this class does not have a related {@link SqlTable} and therefore ignores any table
+ * qualifier set in a query. If a table qualifier is required it can be set directly in the
+ * builder for this class.
+ *
+ * @param
+ * The Java type that corresponds to this column - not used except for compiler type checking for conditions
+ */
+public class DerivedColumn implements BindableColumn {
+ private final String name;
+ 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);
+ this.tableQualifier = builder.tableQualifier;
+ this.columnAlias = builder.columnAlias;
+ this.jdbcType = builder.jdbcType;
+ this.typeHandler = builder.typeHandler;
+ }
+
+ @Override
+ public Optional alias() {
+ return Optional.ofNullable(columnAlias);
+ }
+
+ @Override
+ public Optional jdbcType() {
+ return Optional.ofNullable(jdbcType);
+ }
+
+ @Override
+ public Optional typeHandler() {
+ return Optional.ofNullable(typeHandler);
+ }
+
+ @Override
+ public FragmentAndParameters render(RenderingContext renderingContext) {
+ String fragment = tableQualifier == null ? name : tableQualifier + "." + name; //$NON-NLS-1$
+ return FragmentAndParameters.fromFragment(fragment);
+ }
+
+ @Override
+ public DerivedColumn as(String columnAlias) {
+ return new Builder()
+ .withName(name)
+ .withColumnAlias(columnAlias)
+ .withJdbcType(jdbcType)
+ .withTypeHandler(typeHandler)
+ .withTableQualifier(tableQualifier)
+ .build();
+ }
+
+ public static DerivedColumn of(String name) {
+ return new Builder()
+ .withName(name)
+ .build();
+ }
+
+ public static DerivedColumn of(String name, String tableQualifier) {
+ return new Builder()
+ .withName(name)
+ .withTableQualifier(tableQualifier)
+ .build();
+ }
+
+ public static class Builder {
+ 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(@Nullable String tableQualifier) {
+ this.tableQualifier = tableQualifier;
+ return this;
+ }
+
+ public Builder withColumnAlias(String columnAlias) {
+ this.columnAlias = columnAlias;
+ return this;
+ }
+
+ public Builder withJdbcType(@Nullable JDBCType jdbcType) {
+ this.jdbcType = jdbcType;
+ return this;
+ }
+
+ public Builder withTypeHandler(@Nullable String typeHandler) {
+ this.typeHandler = typeHandler;
+ return this;
+ }
+
+ public DerivedColumn build() {
+ return new DerivedColumn<>(this);
+ }
+ }
+}
diff --git a/src/main/java/org/mybatis/dynamic/sql/ExistsCriterion.java b/src/main/java/org/mybatis/dynamic/sql/ExistsCriterion.java
new file mode 100644
index 000000000..17cecdaa6
--- /dev/null
+++ b/src/main/java/org/mybatis/dynamic/sql/ExistsCriterion.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016-2025 the original author 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 org.jspecify.annotations.Nullable;
+
+public class ExistsCriterion extends SqlCriterion {
+ private final ExistsPredicate existsPredicate;
+
+ private ExistsCriterion(Builder builder) {
+ super(builder);
+ this.existsPredicate = Objects.requireNonNull(builder.existsPredicate);
+ }
+
+ public ExistsPredicate existsPredicate() {
+ return existsPredicate;
+ }
+
+ @Override
+ public R accept(SqlCriterionVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ public static class Builder extends AbstractBuilder {
+ private @Nullable ExistsPredicate existsPredicate;
+
+ public Builder withExistsPredicate(ExistsPredicate existsPredicate) {
+ this.existsPredicate = existsPredicate;
+ return this;
+ }
+
+ public ExistsCriterion build() {
+ return new ExistsCriterion(this);
+ }
+
+ @Override
+ protected Builder getThis() {
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/org/mybatis/dynamic/sql/ExistsPredicate.java b/src/main/java/org/mybatis/dynamic/sql/ExistsPredicate.java
new file mode 100644
index 000000000..c22a84aa2
--- /dev/null
+++ b/src/main/java/org/mybatis/dynamic/sql/ExistsPredicate.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2025 the original author 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 org.mybatis.dynamic.sql.select.SelectModel;
+import org.mybatis.dynamic.sql.util.Buildable;
+
+public class ExistsPredicate {
+ private final Buildable selectModelBuilder;
+ private final String operator;
+
+ private ExistsPredicate(String operator, Buildable selectModelBuilder) {
+ this.selectModelBuilder = Objects.requireNonNull(selectModelBuilder);
+ this.operator = Objects.requireNonNull(operator);
+ }
+
+ public String operator() {
+ return operator;
+ }
+
+ public Buildable selectModelBuilder() {
+ return selectModelBuilder;
+ }
+
+ public static ExistsPredicate exists(Buildable selectModelBuilder) {
+ return new ExistsPredicate("exists", selectModelBuilder); //$NON-NLS-1$
+ }
+
+ 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
new file mode 100644
index 000000000..f0a0010f8
--- /dev/null
+++ b/src/main/java/org/mybatis/dynamic/sql/NotCriterion.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016-2025 the original author 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;
+
+/**
+ * This class represents a criteria group with a NOT.
+ *
+ * @author Jeff Butler
+ *
+ * @since 1.4.0
+ */
+public class NotCriterion extends CriteriaGroup {
+ private NotCriterion(Builder builder) {
+ super(builder);
+ }
+
+ @Override
+ public R accept(SqlCriterionVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ public static class Builder extends AbstractGroupBuilder {
+ public NotCriterion build() {
+ return new NotCriterion(this);
+ }
+
+ @Override
+ protected Builder getThis() {
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/org/mybatis/dynamic/sql/ParameterTypeConverter.java b/src/main/java/org/mybatis/dynamic/sql/ParameterTypeConverter.java
new file mode 100644
index 000000000..4f4856e19
--- /dev/null
+++ b/src/main/java/org/mybatis/dynamic/sql/ParameterTypeConverter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016-2025 the original author 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.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
+ * to somewhat mimic the function of a MyBatis type handler for runtimes such as Spring that don't have
+ * a corresponding concept.
+ *
+ * Since Spring does not have the concept of type handlers, it is a best practice to only use
+ * Java data types that have a clear correlation to SQL data types (for example Java String correlates
+ * automatically with VARCHAR). Using a parameter type converter will allow you to use data types in your
+ * model classes that would otherwise be difficult to use with Spring.
+ *
+ *
A parameter type converter is associated with a SqlColumn.
+ *
+ *
This interface is based on Spring's general Converter interface and is intentionally compatible with it.
+ * Existing converters may be reused if they are marked with this additional interface.
+ *
+ *
The converter is only used for parameters in a parameter map. It is not used for result set processing.
+ * It is also not used for insert statements that are based on an external row class. The converter will be called
+ * in the following circumstances:
+ *
+ *
+ * - Parameters in a general insert statement (for the Value and ValueWhenPresent mappings)
+ * - Parameters in an update statement (for the Value and ValueWhenPresent mappings)
+ * - Parameters in a where clause in any statement (for conditions that accept a value or multiple values)
+ *
+ *
+ * @param Source Type
+ * @param Target Type
+ *
+ * @see SqlColumn
+ * @author Jeff Butler
+ * @since 1.1.5
+ */
+@FunctionalInterface
+public interface ParameterTypeConverter {
+ /**
+ * 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 c60a8ab1e..13cfdee55 100644
--- a/src/main/java/org/mybatis/dynamic/sql/SortSpecification.java
+++ b/src/main/java/org/mybatis/dynamic/sql/SortSpecification.java
@@ -1,11 +1,11 @@
-/**
- * Copyright 2016-2017 the original author or authors.
+/*
+ * Copyright 2016-2025 the original author 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
+ * 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,
@@ -15,32 +15,30 @@
*/
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.
- *
- * @author Jeff Butler
*
+ * @author Jeff Butler
*/
public interface SortSpecification {
/**
* Returns a new instance of the SortSpecification that should render as descending in an
* ORDER BY clause.
- *
+ *
* @return new instance of SortSpecification
*/
SortSpecification descending();
/**
- * Return the column alias or column name.
- *
- * @return the column alias if one has been specified by the user, or else the column name
- */
- String aliasOrName();
-
- /**
- * Return true if the sort order is descending.
- *
- * @return true if the SortSpcification should render as descending
+ * Return a fragment rendered for use in an ORDER BY clause. The fragment should include "DESC" if a
+ * descending order is desired.
+ *
+ * @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 f579b835e..2a8243999 100644
--- a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java
+++ b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java
@@ -1,11 +1,11 @@
-/**
- * Copyright 2016-2019 the original author or authors.
+/*
+ * Copyright 2016-2025 the original author 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
+ * 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,
@@ -17,15 +17,22 @@
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
import java.util.function.Supplier;
+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;
+import org.mybatis.dynamic.sql.insert.GeneralInsertDSL;
import org.mybatis.dynamic.sql.insert.InsertDSL;
import org.mybatis.dynamic.sql.insert.InsertSelectDSL;
import org.mybatis.dynamic.sql.insert.MultiRowInsertDSL;
+import org.mybatis.dynamic.sql.select.ColumnSortSpecification;
import org.mybatis.dynamic.sql.select.CountDSL;
+import org.mybatis.dynamic.sql.select.HavingDSL;
+import org.mybatis.dynamic.sql.select.MultiSelectDSL;
import org.mybatis.dynamic.sql.select.QueryExpressionDSL.FromGatherer;
import org.mybatis.dynamic.sql.select.SelectDSL;
import org.mybatis.dynamic.sql.select.SelectModel;
@@ -37,16 +44,19 @@
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;
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;
-import org.mybatis.dynamic.sql.select.join.EqualTo;
-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;
@@ -103,203 +113,519 @@
public interface SqlBuilder {
// statements
+
+ /**
+ * Renders as select count(distinct column) from table...
+ *
+ * @param column
+ * the column to count
+ *
+ * @return the next step in the DSL
+ */
+ static CountDSL.FromGatherer countDistinctColumn(BasicColumn column) {
+ return CountDSL.countDistinct(column);
+ }
+
+ /**
+ * Renders as select count(column) from table...
+ *
+ * @param column
+ * the column to count
+ *
+ * @return the next step in the DSL
+ */
+ static CountDSL.FromGatherer countColumn(BasicColumn column) {
+ return CountDSL.count(column);
+ }
+
+ /**
+ * Renders as select count(*) from table...
+ *
+ * @param table
+ * the table to count
+ *
+ * @return the next step in the DSL
+ */
static CountDSL countFrom(SqlTable table) {
return CountDSL.countFrom(table);
}
-
+
static DeleteDSL deleteFrom(SqlTable table) {
return DeleteDSL.deleteFrom(table);
}
- static InsertDSL.IntoGatherer insert(T record) {
- return InsertDSL.insert(record);
+ static DeleteDSL deleteFrom(SqlTable table, String tableAlias) {
+ return DeleteDSL.deleteFrom(table, tableAlias);
+ }
+
+ static InsertDSL.IntoGatherer insert(T row) {
+ return InsertDSL.insert(row);
}
-
+
+ /**
+ * Insert a Batch of records. The model object is structured to support bulk inserts with JDBC batch support.
+ *
+ * @param records
+ * records to insert
+ * @param
+ * the type of record to insert
+ *
+ * @return the next step in the DSL
+ */
@SafeVarargs
- static