diff --git a/src/main/java/io/ebean/SqlQuery.java b/src/main/java/io/ebean/SqlQuery.java index 6c1bcf00b..1e6ec1edf 100644 --- a/src/main/java/io/ebean/SqlQuery.java +++ b/src/main/java/io/ebean/SqlQuery.java @@ -83,6 +83,23 @@ public interface SqlQuery extends Serializable { @Nonnull Optional findOneOrEmpty(); + /** + * Execute the query returning a single scalar attribute. + *
@{code
+   *
+   *   String sql = "select max(unit_price) from o_order_detail where order_qty > ?";
+   *
+   *   BigDecimal maxPrice = Ebean.createSqlQuery(sql)
+   *     .setParameter(1, 2)
+   *     .findSingleAttribute(BigDecimal.class);
+   *
+   *
+   * }
+ * + * @param attributeType The type of the returned value + */ + T findSingleAttribute(Class attributeType); + /** * The same as bind for named parameters. */ diff --git a/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java index b8c9e8e02..4de6f02e2 100644 --- a/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -234,6 +234,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL */ void scopedTransactionExit(Object returnOrThrowable, int opCode); + /** + * SqlQuery find single attribute. + */ + T findSingleAttribute(SpiSqlQuery query, Class cls); + /** * DTO findList query. */ diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 245673619..b2f75985b 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -67,6 +67,7 @@ import io.ebeaninternal.api.SpiJsonContext; import io.ebeaninternal.api.SpiLogManager; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiQuery.Type; +import io.ebeaninternal.api.SpiSqlQuery; import io.ebeaninternal.api.SpiSqlUpdate; import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.api.SpiTransactionManager; @@ -1585,6 +1586,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } } + @Override + public T findSingleAttribute(SpiSqlQuery query, Class cls) { + + RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, null); + try { + request.initTransIfRequired(); + return request.findSingleAttribute(cls); + + } finally { + request.endTransIfRequired(); + } + } + @Override public void findDtoEach(SpiDtoQuery query, Consumer consumer) { DtoQueryRequest request = new DtoQueryRequest<>(this, dtoQueryEngine, query); diff --git a/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java b/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java index fa2a13b93..038f3842a 100644 --- a/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java +++ b/src/main/java/io/ebeaninternal/server/core/RelationalQueryEngine.java @@ -30,6 +30,11 @@ public interface RelationalQueryEngine { */ void findEach(RelationalQueryRequest request, Predicate consumer); + /** + * Find single attribute. + */ + T findSingleAttribute(RelationalQueryRequest request, Class cls); + /** * Collect SQL query execution statistics. */ diff --git a/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java index 24278ea11..221a3d36e 100644 --- a/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java +++ b/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java @@ -53,6 +53,10 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest { } } + public T findSingleAttribute(Class cls) { + return queryEngine.findSingleAttribute(this, cls); + } + public void findEach(Consumer consumer) { queryEngine.findEach(this, consumer); } @@ -104,4 +108,11 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest { } } + public ResultSet getResultSet() { + return resultSet; + } + + public void incrementRows() { + rows++; + } } diff --git a/src/main/java/io/ebeaninternal/server/persist/Binder.java b/src/main/java/io/ebeaninternal/server/persist/Binder.java index 3ab04e410..a45aa27f0 100644 --- a/src/main/java/io/ebeaninternal/server/persist/Binder.java +++ b/src/main/java/io/ebeaninternal/server/persist/Binder.java @@ -8,6 +8,8 @@ import io.ebeaninternal.server.core.timezone.DataTimeZone; import io.ebeaninternal.server.expression.platform.DbExpressionHandler; import io.ebeaninternal.server.persist.platform.MultiValueBind; import io.ebeaninternal.server.type.DataBind; +import io.ebeaninternal.server.type.DataReader; +import io.ebeaninternal.server.type.RsetDataReader; import io.ebeaninternal.server.type.ScalarType; import io.ebeaninternal.server.type.TypeManager; import org.slf4j.Logger; @@ -18,6 +20,7 @@ import java.math.BigDecimal; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; @@ -196,7 +199,7 @@ public class Binder { } } - private ScalarType getScalarType(Class clazz) { + public ScalarType getScalarType(Class clazz) { ScalarType type = typeManager.getScalarType(clazz); if (type == null) { throw new PersistenceException("No ScalarType registered for " + clazz); @@ -457,4 +460,8 @@ public class Binder { public DataBind dataBind(PreparedStatement stmt, Connection connection) { return new DataBind(dataTimeZone, stmt, connection); } + + public DataReader createDataReader(ResultSet resultSet) { + return new RsetDataReader(dataTimeZone, resultSet); + } } diff --git a/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java b/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java index f1282f9d2..9271ae624 100644 --- a/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java +++ b/src/main/java/io/ebeaninternal/server/query/DefaultRelationalQueryEngine.java @@ -10,6 +10,7 @@ import io.ebeaninternal.server.core.Message; import io.ebeaninternal.server.core.RelationalQueryEngine; import io.ebeaninternal.server.core.RelationalQueryRequest; import io.ebeaninternal.server.persist.Binder; +import io.ebeaninternal.server.type.ScalarType; import javax.persistence.PersistenceException; import java.sql.SQLException; @@ -91,6 +92,35 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { } } + @SuppressWarnings("unchecked") + @Override + public T findSingleAttribute(RelationalQueryRequest request, Class cls) { + + ScalarType scalarType = (ScalarType) binder.getScalarType(cls); + return findScalar(request, scalarType); + } + + private T findScalar(RelationalQueryRequest request, ScalarType scalarType) { + try { + request.executeSql(binder, SpiQuery.Type.ATTRIBUTE); + + T value = null; + if (request.next()) { + request.incrementRows(); + value = scalarType.read(binder.createDataReader(request.getResultSet())); + } + + request.logSummary(); + return value; + + } catch (Exception e) { + throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e); + + } finally { + request.close(); + } + } + @Override public List findList(RelationalQueryRequest request) { diff --git a/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java b/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java index eb344b003..e5f81c2bf 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java @@ -1,8 +1,8 @@ package io.ebeaninternal.server.querydefn; -import io.ebean.EbeanServer; import io.ebean.SqlRow; import io.ebeaninternal.api.BindParams; +import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiSqlQuery; import java.util.List; @@ -17,7 +17,7 @@ public class DefaultRelationalQuery implements SpiSqlQuery { private static final long serialVersionUID = -1098305779779591068L; - private final transient EbeanServer server; + private final transient SpiEbeanServer server; private String label; @@ -39,16 +39,11 @@ public class DefaultRelationalQuery implements SpiSqlQuery { /** * Additional supply a query detail object. */ - public DefaultRelationalQuery(EbeanServer server, String query) { + public DefaultRelationalQuery(SpiEbeanServer server, String query) { this.server = server; this.query = query; } - public DefaultRelationalQuery setQuery(String query) { - this.query = query; - return this; - } - @Override public void findEach(Consumer consumer) { server.findEach(this, consumer, null); @@ -64,6 +59,11 @@ public class DefaultRelationalQuery implements SpiSqlQuery { return server.findList(this, null); } + @Override + public T findSingleAttribute(Class cls) { + return server.findSingleAttribute(this, cls); + } + @Override public SqlRow findOne() { return server.findOne(this, null); diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeBigDecimal.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeBigDecimal.java index f66149ad0..526f0b4db 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeBigDecimal.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeBigDecimal.java @@ -32,7 +32,6 @@ public class ScalarTypeBigDecimal extends ScalarTypeBase { @Override public BigDecimal read(DataReader dataReader) throws SQLException { - return dataReader.getBigDecimal(); } diff --git a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index fcab9b1a9..ab117a62d 100644 --- a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -457,6 +457,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer { return null; } + @Override + public T findSingleAttribute(SpiSqlQuery query, Class cls) { + return null; + } + @Override public SqlQuery createSqlQuery(String sql) { return null; diff --git a/src/test/java/org/tests/basic/type/TestSqlRowUUID.java b/src/test/java/org/tests/basic/type/TestSqlRowUUID.java index 9a6161b5b..2ebcb4db0 100644 --- a/src/test/java/org/tests/basic/type/TestSqlRowUUID.java +++ b/src/test/java/org/tests/basic/type/TestSqlRowUUID.java @@ -5,11 +5,14 @@ import io.ebean.Ebean; import io.ebean.SqlQuery; import io.ebean.SqlRow; import org.tests.model.basic.TUuidEntity; -import org.junit.Assert; import org.junit.Test; import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + public class TestSqlRowUUID extends BaseTestCase { @Test @@ -26,9 +29,17 @@ public class TestSqlRowUUID extends BaseTestCase { UUID id = sqlRow.getUUID("id"); - Assert.assertNotNull(id); + assertNotNull(id); Boolean b = sqlRow.getBoolean("name"); - Assert.assertFalse(b); + assertFalse(b); + + + SqlQuery q2 = Ebean.createSqlQuery("select id from tuuid_entity where id = :id"); + q2.setParameter("id", e.getId()); + UUID value = q2.findSingleAttribute(UUID.class); + + assertThat(value).isEqualTo(e.getId()); + } } diff --git a/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java b/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java index 1e0a5e830..bd9b80191 100644 --- a/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java +++ b/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java @@ -10,6 +10,8 @@ import org.junit.Test; import org.tests.model.basic.Order; import org.tests.model.basic.ResetBasicData; +import java.math.BigDecimal; +import java.time.OffsetDateTime; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -18,6 +20,49 @@ import static org.junit.Assert.assertEquals; public class SqlQueryTests extends BaseTestCase { + @Test + public void findSingleAttribute_decimal() { + + ResetBasicData.reset(); + + String sql = "select max(unit_price) from o_order_detail where order_qty > ?"; + + BigDecimal maxPrice = Ebean.createSqlQuery(sql) + .setParameter(1, 2) + .findSingleAttribute(BigDecimal.class); + + assertThat(maxPrice).isNotNull(); + } + + @Test + public void findSingleAttribute_long() { + + ResetBasicData.reset(); + + String sql = "select count(order_qty) from o_order_detail where unit_price > ?"; + + long count = Ebean.createSqlQuery(sql) + .setParameter(1, 2) + .findSingleAttribute(Long.class); + + assertThat(count).isGreaterThan(0); + } + + @Test + public void findSingleAttribute_OffsetDateTime() { + + ResetBasicData.reset(); + + String sql = "select min(updtime) from o_order_detail where unit_price > ? and updtime is not null"; + + OffsetDateTime minCreated = Ebean.createSqlQuery(sql) + .setParameter(1, 2) + .findSingleAttribute(OffsetDateTime.class); + + assertThat(minCreated).isBefore(OffsetDateTime.now()); + } + + @Test public void newline_replacedInLogsOnly() {