diff --git a/ebean-api/src/main/java/io/ebean/Query.java b/ebean-api/src/main/java/io/ebean/Query.java index 495da5977..0a9723d24 100644 --- a/ebean-api/src/main/java/io/ebean/Query.java +++ b/ebean-api/src/main/java/io/ebean/Query.java @@ -1774,5 +1774,21 @@ public interface Query extends CancelableQuery { * Implemented by query bean properties and expressions based on those properties. */ interface Property { + + /** + * Return a property given the expression. + */ + static Property of(String expression) { + return new SimpleProperty(expression); + } + + /** + * Return the property in string expression form. + *

+ * This is a path to a database column (like "name" or "billingAddress.city") or a function + * wrapping a path (like lower(name), concat(name, '-', billingAddress.city) + */ + @Override + String toString(); } } diff --git a/ebean-api/src/main/java/io/ebean/SimpleProperty.java b/ebean-api/src/main/java/io/ebean/SimpleProperty.java new file mode 100644 index 000000000..274978246 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/SimpleProperty.java @@ -0,0 +1,15 @@ +package io.ebean; + +final class SimpleProperty implements Query.Property { + + private final String expression; + + SimpleProperty(String expression) { + this.expression = expression; + } + + @Override + public String toString() { + return expression; + } +} diff --git a/ebean-api/src/main/java/io/ebean/StdFunctions.java b/ebean-api/src/main/java/io/ebean/StdFunctions.java new file mode 100644 index 000000000..35d9140c5 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/StdFunctions.java @@ -0,0 +1,100 @@ +package io.ebean; + +import io.ebean.Query.Property; + +public final class StdFunctions { + + public static Property avg(Property property) { + return Property.of("avg(" + property + ")"); + } + + public static Property count(Property property) { + return Property.of("count(" + property + ")"); + } + + public static Property max(Property property) { + return Property.of("max(" + property + ")"); + } + + public static Property min(Property property) { + return Property.of("min(" + property + ")"); + } + + public static Property sum(Property property) { + return Property.of("sum(" + property + ")"); + } + + public static Property lower(Property property) { + return Property.of("lower(" + property + ")"); + } + + public static Property upper(Property property) { + return Property.of("upper(" + property + ")"); + } + + public static Property concat(Property property, Object... values) { + StringBuilder expression = new StringBuilder(50); + expression.append("concat(").append(property.toString()); + for (Object value : values) { + expression.append(",").append(sqlConcatString(value)); + } + expression.append(")"); + return Property.of(expression.toString()); + } + + public static Property coalesce(Property property, Object value) { + return Property.of("coalesce(" + property.toString() + "," + sqlValue(value) + ")"); + } + + private static String sqlConcatString(Object value) { + String asStr = String.valueOf(value); + return (value instanceof Property || isSqlQuoted(asStr)) ? asStr : "'" + value + "'"; + } + + private static boolean isSqlQuoted(String asStr) { + return asStr.length() > 0 && asStr.charAt(0) == '\''; + } + + private static String sqlValue(Object value) { + if (value instanceof Property || value instanceof Number) { + return value.toString(); + } else { + return "'" + value + "'"; + } + } + + // -------------------------------------------------------------------------------------------- // + // ---- Expressions --------------------------------------------------------------------------- // + + public static Expression eq(Property property, Object value) { + return Expr.eq(property.toString(), value); + } + + public static Expression gt(Property property, Object value) { + return Expr.gt(property.toString(), value); + } + + public static Expression like(Property property, String value) { + return Expr.like(property.toString(), value); + } + + public static Expression ilike(Property property, String value) { + return Expr.ilike(property.toString(), value); + } + + public static Expression startsWith(Property property, String value) { + return Expr.startsWith(property.toString(), value); + } + + public static Expression istartsWith(Property property, String value) { + return Expr.istartsWith(property.toString(), value); + } + + public static Expression contains(Property property, String value) { + return Expr.contains(property.toString(), value); + } + + public static Expression icontains(Property property, String value) { + return Expr.icontains(property.toString(), value); + } +} diff --git a/ebean-api/src/test/java/io/ebean/StdFunctionsTest.java b/ebean-api/src/test/java/io/ebean/StdFunctionsTest.java new file mode 100644 index 000000000..e22344c43 --- /dev/null +++ b/ebean-api/src/test/java/io/ebean/StdFunctionsTest.java @@ -0,0 +1,68 @@ +package io.ebean; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static io.ebean.StdFunctions.*; +import static org.assertj.core.api.Assertions.assertThat; + +class StdFunctionsTest { + + final Query.Property foo = Query.Property.of("foo"); + final Query.Property bar = Query.Property.of("bar"); + + @Test + void testAvg() { + assertThat(avg(foo).toString()).isEqualTo("avg(foo)"); + } + + @Test + void testCount() { + assertThat(count(foo).toString()).isEqualTo("count(foo)"); + } + + @Test + void testMax() { + assertThat(max(foo).toString()).isEqualTo("max(foo)"); + } + + @Test + void testMin() { + assertThat(min(foo).toString()).isEqualTo("min(foo)"); + } + + @Test + void testSum() { + assertThat(sum(foo).toString()).isEqualTo("sum(foo)"); + } + + @Test + void testLower() { + assertThat(lower(foo).toString()).isEqualTo("lower(foo)"); + } + + @Test + void testUpper() { + assertThat(upper(foo).toString()).isEqualTo("upper(foo)"); + } + + @Test + void testConcat() { + assertThat(concat(foo, "+").toString()).isEqualTo("concat(foo,'+')"); + assertThat(concat(foo, 42).toString()).isEqualTo("concat(foo,'42')"); + assertThat(concat(foo, LocalDate.of(2022, 9, 1)).toString()).isEqualTo("concat(foo,'2022-09-01')"); + assertThat(concat(foo, bar).toString()).isEqualTo("concat(foo,bar)"); + assertThat(concat(foo, ":", 42, bar).toString()).isEqualTo("concat(foo,':','42',bar)"); + } + + @Test + void testCoalesce() { + assertThat(coalesce(foo, bar).toString()).isEqualTo("coalesce(foo,bar)"); + assertThat(coalesce(foo, 42).toString()).isEqualTo("coalesce(foo,42)"); + assertThat(coalesce(foo, 0).toString()).isEqualTo("coalesce(foo,0)"); + assertThat(coalesce(foo, "apple").toString()).isEqualTo("coalesce(foo,'apple')"); + assertThat(coalesce(foo, "banana").toString()).isEqualTo("coalesce(foo,'banana')"); + } + +} diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/StdExpressions.java b/ebean-querybean/src/main/java/io/ebean/typequery/StdExpressions.java deleted file mode 100644 index 13498f41b..000000000 --- a/ebean-querybean/src/main/java/io/ebean/typequery/StdExpressions.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.ebean.typequery; - -import io.ebean.Expr; -import io.ebean.Expression; -import io.ebean.Query; - -public class StdExpressions { - - public static Expression gt(Query.Property property, Object value) { - return Expr.gt(property.toString(), value); - } - - public static Expression like(Query.Property property, String value) { - return Expr.like(property.toString(), value); - } - - public static Expression ilike(Query.Property property, String value) { - return Expr.ilike(property.toString(), value); - } - - public static Expression startsWith(Query.Property property, String value) { - return Expr.startsWith(property.toString(), value); - } - - public static Expression istartsWith(Query.Property property, String value) { - return Expr.istartsWith(property.toString(), value); - } - - public static Expression contains(Query.Property property, String value) { - return Expr.contains(property.toString(), value); - } - - public static Expression icontains(Query.Property property, String value) { - return Expr.icontains(property.toString(), value); - } -} diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/StdFunctions.java b/ebean-querybean/src/main/java/io/ebean/typequery/StdFunctions.java deleted file mode 100644 index b9c295a22..000000000 --- a/ebean-querybean/src/main/java/io/ebean/typequery/StdFunctions.java +++ /dev/null @@ -1,54 +0,0 @@ -package io.ebean.typequery; - -import io.ebean.Query.Property; - -public final class StdFunctions { - - public static Property max(Property property) { - return new Standard("max(" + property + ")"); - } - - public static Property sum(Property property) { - return new Standard("sum(" + property + ")"); - } - - public static Property concat(Property property, Object... values) { - StringBuilder expression = new StringBuilder(50); - expression.append("concat(").append(property.toString()); - for (Object value : values) { - expression.append(",").append(sqlStringExpression(value)); - } - expression.append(")"); - return new Standard(expression.toString()); - } - - public static Property coalesce(Property property, Object value) { - StringBuilder expression = new StringBuilder(50); - expression.append("coalesce(").append(property.toString()).append(","); - expression.append(sqlStringExpression(value)); - expression.append(")"); - return new Standard(expression.toString()); - } - - private static String sqlStringExpression(Object value) { - if (value instanceof Property || value instanceof Number) { - return value.toString(); - } else { - return "'" + value + "'"; - } - } - - private static class Standard implements Property { - - private final String expression; - - private Standard(String expression) { - this.expression = expression; - } - - @Override - public String toString() { - return expression; - } - } -} diff --git a/ebean-querybean/src/test/java/org/querytest/QOrderTest.java b/ebean-querybean/src/test/java/org/querytest/QOrderTest.java index 24eda0518..fa17ca5a5 100644 --- a/ebean-querybean/src/test/java/org/querytest/QOrderTest.java +++ b/ebean-querybean/src/test/java/org/querytest/QOrderTest.java @@ -17,9 +17,9 @@ import org.junit.jupiter.api.Test; import java.util.List; -import static io.ebean.typequery.StdExpressions.gt; -import static io.ebean.typequery.StdExpressions.ilike; -import static io.ebean.typequery.StdFunctions.*; +import static io.ebean.StdFunctions.gt; +import static io.ebean.StdFunctions.ilike; +import static io.ebean.StdFunctions.*; import static org.assertj.core.api.Assertions.assertThat; public class QOrderTest { diff --git a/ebean-test/src/test/java/org/tests/query/TestStdFunctions.java b/ebean-test/src/test/java/org/tests/query/TestStdFunctions.java new file mode 100644 index 000000000..27ff61760 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/query/TestStdFunctions.java @@ -0,0 +1,44 @@ +package org.tests.query; + +import io.ebean.DB; +import io.ebean.Query; +import org.junit.jupiter.api.Test; +import org.tests.model.basic.Customer; + +import static io.ebean.StdFunctions.*; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Testing the expressions in StdFunctions (but without query beans so using Query.Property.of). + */ +public class TestStdFunctions { + + @Test + void coalesceLike() { + var name = Query.Property.of("name"); + + var query = DB.find(Customer.class) + .select(coalesce(name, "na").toString()) + .where() + .add(like(coalesce(name, "na"), "foo%")) + .query(); + + query.findSingleAttributeList(); + assertThat(query.getGeneratedSql()).contains("select coalesce(t0.name,'na') from o_customer t0 where coalesce(t0.name,'na') like ?"); + } + + @Test + void concatEq() { + var name = Query.Property.of("name"); + var status = Query.Property.of("status"); + + var query = DB.find(Customer.class) + .select(concat(name, "na", status).toString()) + .where() + .add(eq(concat(name, "na", status), "foo")) + .query(); + + query.findSingleAttributeList(); + assertThat(query.getGeneratedSql()).contains("select concat(t0.name,'na',t0.status) from o_customer t0 where concat(t0.name,'na',t0.status) = ?"); + } +}