diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/BasicSqlAnsiLimiter.java b/src/main/java/com/avaje/ebean/config/dbplatform/BasicSqlAnsiLimiter.java new file mode 100644 index 000000000..dc5bc8b2f --- /dev/null +++ b/src/main/java/com/avaje/ebean/config/dbplatform/BasicSqlAnsiLimiter.java @@ -0,0 +1,24 @@ +package com.avaje.ebean.config.dbplatform; + +/** + * Adds ANSI based OFFSET FETCH NEXT clauses to a SQL query. + */ +public class BasicSqlAnsiLimiter implements BasicSqlLimiter { + + @Override + public String limit(String dbSql, int firstRow, int maxRows) { + + StringBuilder sb = new StringBuilder(50 + dbSql.length()); + + sb.append(dbSql); + if (firstRow > 0) { + sb.append(" ").append("offset"); + sb.append(" ").append(firstRow).append(" rows"); + } + if (maxRows > 0) { + sb.append(" fetch next ").append(maxRows).append(" rows only"); + } + return sb.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/BasicSqlLimitOffset.java b/src/main/java/com/avaje/ebean/config/dbplatform/BasicSqlLimitOffset.java new file mode 100644 index 000000000..c473e5acd --- /dev/null +++ b/src/main/java/com/avaje/ebean/config/dbplatform/BasicSqlLimitOffset.java @@ -0,0 +1,36 @@ +package com.avaje.ebean.config.dbplatform; + +/** + * Adds LIMIT OFFSET clauses to a SQL query. + */ +public class BasicSqlLimitOffset implements BasicSqlLimiter { + + /** + * LIMIT keyword. + */ + private static final String LIMIT = "limit"; + + /** + * OFFSET keyword. + */ + private static final String OFFSET = "offset"; + + @Override + public String limit(String dbSql, int firstRow, int maxRows) { + + StringBuilder sb = new StringBuilder(50 + dbSql.length()); + + sb.append(dbSql); + + if (maxRows > 0) { + sb.append(" ").append(LIMIT); + sb.append(" ").append(maxRows); + } + if (firstRow > 0) { + sb.append(" ").append(OFFSET).append(" "); + sb.append(firstRow); + } + return sb.toString(); + } + +} diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/BasicSqlLimiter.java b/src/main/java/com/avaje/ebean/config/dbplatform/BasicSqlLimiter.java new file mode 100644 index 000000000..4b1bfd19b --- /dev/null +++ b/src/main/java/com/avaje/ebean/config/dbplatform/BasicSqlLimiter.java @@ -0,0 +1,12 @@ +package com.avaje.ebean.config.dbplatform; + +/** + * Simple SQL limiter for use with SqlQuery. + */ +public interface BasicSqlLimiter { + + /** + * Add basic offset/limit clause to SqlQuery query. + */ + String limit(String dbSql, int firstRow, int maxRows); +} diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java index 6719dd879..f90af1725 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java @@ -71,6 +71,11 @@ public class DatabasePlatform { */ protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter(); + /** + * Limit/offset support for SqlQuery only. + */ + protected BasicSqlLimiter basicSqlLimiter = new BasicSqlLimitOffset(); + /** * Mapping of JDBC to Database types. */ @@ -454,6 +459,13 @@ public class DatabasePlatform { return sqlLimiter; } + /** + * Return the BasicSqlLimiter for limit/offset of SqlQuery queries. + */ + public BasicSqlLimiter getBasicSqlLimiter() { + return basicSqlLimiter; + } + /** * Set the DB TRUE literal (from the registered boolean ScalarType) */ diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/OraclePlatform.java b/src/main/java/com/avaje/ebean/config/dbplatform/OraclePlatform.java index a3be188ef..66c30aab9 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/OraclePlatform.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/OraclePlatform.java @@ -18,6 +18,7 @@ public class OraclePlatform extends DatabasePlatform { this.maxConstraintNameLength = 30; this.dbEncrypt = new OracleDbEncrypt(); this.sqlLimiter = new RownumSqlLimiter(); + this.basicSqlLimiter = new BasicSqlAnsiLimiter(); this.platformDdl = new Oracle10Ddl(this); this.historySupport = new OracleDbHistorySupport(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryRequest.java index 916f429cb..6a6d4aacb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryRequest.java @@ -31,8 +31,6 @@ import java.util.List; */ public final class RelationalQueryRequest { - private static final int GLOBAL_ROW_LIMIT = Integer.valueOf(System.getProperty("ebean.query.globallimit", "1000000")); - private static final Logger logger = LoggerFactory.getLogger(RelationalQueryRequest.class); private final SpiSqlQuery query; @@ -212,7 +210,18 @@ public final class RelationalQueryRequest { // convert any named parameters if required sql = BindParamsParser.parse(bindParams, sql); } - this.sql = sql; + this.sql = limitOffset(sql); + } + + private String limitOffset(String sql) { + + int firstRow = query.getFirstRow(); + int maxRows = query.getMaxRows(); + if (firstRow > 0 || maxRows > 0) { + return ebeanServer.getDatabasePlatform().getBasicSqlLimiter() + .limit(query.getQuery(), firstRow, maxRows); + } + return sql; } /** @@ -250,13 +259,6 @@ public final class RelationalQueryRequest { } - /** - * Return the maxRows allowed to fetch. - */ - public int getMaxRows() { - return query.getMaxRows() >= 1 ? query.getMaxRows() : GLOBAL_ROW_LIMIT; - } - /** * Return the SQL executed for this query. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java index 4a4c7074b..497e2f173 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultRelationalQueryEngine.java @@ -74,19 +74,9 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine { try { request.executeSql(binder); - int maxRows = request.getMaxRows(); - - int loadRowCount = 0; - List rows = new ArrayList(); - while (request.next()) { rows.add(readRow(request)); - loadRowCount++; - if (loadRowCount == maxRows) { - // break, as we have hit the max rows to fetch... - break; - } } logSummary(request, startTime); diff --git a/src/test/java/com/avaje/ebean/config/dbplatform/BasicSqlLimitOffsetTest.java b/src/test/java/com/avaje/ebean/config/dbplatform/BasicSqlLimitOffsetTest.java new file mode 100644 index 000000000..b61391bb1 --- /dev/null +++ b/src/test/java/com/avaje/ebean/config/dbplatform/BasicSqlLimitOffsetTest.java @@ -0,0 +1,34 @@ +package com.avaje.ebean.config.dbplatform; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BasicSqlLimitOffsetTest { + + private BasicSqlLimitOffset limiter = new BasicSqlLimitOffset(); + + @Test + public void limit_maxRows() throws Exception { + + String query = "select * from mytab order by id"; + String sql = limiter.limit(query, 0, 10); + assertThat(sql).isEqualTo(query+" limit 10"); + } + + @Test + public void limit_firstRowMaxRows() throws Exception { + + String query = "select * from mytab order by id"; + String sql = limiter.limit(query, 5, 10); + assertThat(sql).isEqualTo(query+" limit 10 offset 5"); + } + + @Test + public void limit_zeros() throws Exception { + + String query = "select * from mytab order by id"; + String sql = limiter.limit(query, 0, 0); + assertThat(sql).isEqualTo(query); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/config/dbplatform/BasicSqlStandardLimiterTest.java b/src/test/java/com/avaje/ebean/config/dbplatform/BasicSqlStandardLimiterTest.java new file mode 100644 index 000000000..e1723b1ab --- /dev/null +++ b/src/test/java/com/avaje/ebean/config/dbplatform/BasicSqlStandardLimiterTest.java @@ -0,0 +1,35 @@ +package com.avaje.ebean.config.dbplatform; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BasicSqlStandardLimiterTest { + + private BasicSqlAnsiLimiter limiter = new BasicSqlAnsiLimiter(); + + @Test + public void limit_maxRows() throws Exception { + + String query = "select * from mytab order by id"; + String sql = limiter.limit(query, 0, 10); + assertThat(sql).isEqualTo(query+" fetch next 10 rows only"); + } + + @Test + public void limit_firstRowMaxRows() throws Exception { + + String query = "select * from mytab order by id"; + String sql = limiter.limit(query, 5, 10); + assertThat(sql).isEqualTo(query+" offset 5 rows fetch next 10 rows only"); + } + + @Test + public void limit_zeros() throws Exception { + + String query = "select * from mytab order by id"; + String sql = limiter.limit(query, 0, 0); + assertThat(sql).isEqualTo(query); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/tests/basic/TestLimitQuery.java b/src/test/java/com/avaje/tests/basic/TestLimitQuery.java index 52a6c84c0..a18c76631 100644 --- a/src/test/java/com/avaje/tests/basic/TestLimitQuery.java +++ b/src/test/java/com/avaje/tests/basic/TestLimitQuery.java @@ -1,16 +1,16 @@ package com.avaje.tests.basic; -import java.util.List; - -import org.junit.Assert; -import org.junit.Test; - import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; import com.avaje.ebean.Query; -import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.tests.model.basic.Order; import com.avaje.tests.model.basic.ResetBasicData; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; public class TestLimitQuery extends BaseTestCase { @@ -26,9 +26,6 @@ public class TestLimitQuery extends BaseTestCase { ResetBasicData.reset(); - SpiEbeanServer server = (SpiEbeanServer) Ebean.getServer(null); - boolean h2Db = "h2".equals(server.getDatabasePlatform().getName()); - Query query = Ebean.find(Order.class) .setAutoTune(false) .fetch("details") @@ -40,21 +37,16 @@ public class TestLimitQuery extends BaseTestCase { query.findList(); String sql = query.getGeneratedSql(); - boolean hasLimit = sql.contains("limit 0"); - boolean hasOffset = sql.contains("offset 3"); - - if (h2Db) { - Assert.assertTrue(hasLimit); - Assert.assertTrue(hasOffset); + if (isH2()) { + assertThat(sql).contains("offset 3"); + assertThat(sql).contains("limit 0"); } } @Test public void testMaxRowsWithFirstRowZero() { - ResetBasicData.reset(); - SpiEbeanServer server = (SpiEbeanServer) Ebean.getServer(null); - boolean h2Db = "h2".equals(server.getDatabasePlatform().getName()); + ResetBasicData.reset(); Query query = Ebean.find(Order.class) .setAutoTune(false) @@ -69,7 +61,7 @@ public class TestLimitQuery extends BaseTestCase { boolean hasLimit = sql.contains("limit 3"); boolean hasOffset = sql.contains("offset"); - if (h2Db) { + if (isH2()) { Assert.assertTrue(sql, hasLimit); Assert.assertFalse(sql, hasOffset); } @@ -79,9 +71,6 @@ public class TestLimitQuery extends BaseTestCase { public void testDefaults() { ResetBasicData.reset(); - SpiEbeanServer server = (SpiEbeanServer) Ebean.getServer(null); - boolean h2Db = "h2".equals(server.getDatabasePlatform().getName()); - Query query = Ebean.find(Order.class) .setAutoTune(false) .fetch("details") @@ -94,7 +83,7 @@ public class TestLimitQuery extends BaseTestCase { boolean hasLimit = sql.contains("limit"); boolean hasOffset = sql.contains("offset"); - if (h2Db) { + if (isH2()) { Assert.assertFalse(hasLimit); Assert.assertFalse(hasOffset); } @@ -103,9 +92,6 @@ public class TestLimitQuery extends BaseTestCase { private void rob() { ResetBasicData.reset(); - SpiEbeanServer server = (SpiEbeanServer) Ebean.getServer(null); - boolean h2Db = "h2".equals(server.getDatabasePlatform().getName()); - Query query = Ebean.find(Order.class) .setAutoTune(false) .fetch("details") @@ -126,7 +112,7 @@ public class TestLimitQuery extends BaseTestCase { Assert.assertTrue(hasDetailsJoin); Assert.assertFalse(hasSelectedDetails); Assert.assertTrue(hasDistinct); - if (h2Db) { + if (isH2()) { Assert.assertTrue(hasLimit); } @@ -146,7 +132,7 @@ public class TestLimitQuery extends BaseTestCase { Assert.assertFalse("no join with maxRows", hasDetailsJoin); Assert.assertFalse(hasSelectedDetails); Assert.assertFalse(hasDistinct); - if (h2Db) { + if (isH2()) { Assert.assertTrue(hasLimit); } } diff --git a/src/test/java/com/avaje/tests/compositekeys/ModifiedH2Platform.java b/src/test/java/com/avaje/tests/compositekeys/ModifiedH2Platform.java deleted file mode 100644 index e9ac6a226..000000000 --- a/src/test/java/com/avaje/tests/compositekeys/ModifiedH2Platform.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.avaje.tests.compositekeys; - -import com.avaje.ebean.config.dbplatform.H2Platform; - -public class ModifiedH2Platform extends H2Platform { - - public ModifiedH2Platform() { - super(); - this.idInExpandedForm = true; - } -} diff --git a/src/test/java/com/avaje/tests/query/sqlquery/SqlQueryTests.java b/src/test/java/com/avaje/tests/query/sqlquery/SqlQueryTests.java index 235c2c1c6..214f52f05 100644 --- a/src/test/java/com/avaje/tests/query/sqlquery/SqlQueryTests.java +++ b/src/test/java/com/avaje/tests/query/sqlquery/SqlQueryTests.java @@ -1,5 +1,6 @@ package com.avaje.tests.query.sqlquery; +import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; import com.avaje.ebean.QueryEachConsumer; import com.avaje.ebean.QueryEachWhileConsumer; @@ -7,13 +8,87 @@ import com.avaje.ebean.SqlQuery; import com.avaje.ebean.SqlRow; import com.avaje.tests.model.basic.Order; import com.avaje.tests.model.basic.ResetBasicData; +import org.avaje.ebeantest.LoggedSqlCollector; import org.junit.Test; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; -public class SqlQueryTests { +public class SqlQueryTests extends BaseTestCase { + + @Test + public void firstRowMaxRows() { + + ResetBasicData.reset(); + + SqlQuery sqlQuery = Ebean.createSqlQuery("Select * from o_order"); + sqlQuery.setFirstRow(3); + sqlQuery.setMaxRows(10); + + LoggedSqlCollector.start(); + List list = sqlQuery.findList(); + + List sql = LoggedSqlCollector.stop(); + + assertThat(sql.get(0)).contains("Select * from o_order limit 10 offset 3; --bind()"); + assertThat(list).isNotEmpty(); + } + + @Test + public void firstRow() { + + if (isPostgres()) { + + ResetBasicData.reset(); + + SqlQuery sqlQuery = Ebean.createSqlQuery("Select * from o_order order by id"); + sqlQuery.setFirstRow(3); + + LoggedSqlCollector.start(); + sqlQuery.findList(); + List sql = LoggedSqlCollector.stop(); + + assertThat(sql.get(0)).contains("Select * from o_order order by id offset 3"); + } + } + + @Test + public void maxRows() { + + ResetBasicData.reset(); + + SqlQuery sqlQuery = Ebean.createSqlQuery("Select * from o_order order by id"); + sqlQuery.setMaxRows(10); + + LoggedSqlCollector.start(); + sqlQuery.findList(); + List sql = LoggedSqlCollector.stop(); + + assertThat(sql.get(0)).contains("Select * from o_order order by id limit 10"); + } + + @Test + public void findEachMaxRows() { + + ResetBasicData.reset(); + + SqlQuery sqlQuery = Ebean.createSqlQuery("Select * from o_order"); + sqlQuery.setMaxRows(10); + + LoggedSqlCollector.start(); + sqlQuery.findEach(new QueryEachConsumer() { + @Override + public void accept(SqlRow bean) { + bean.get("id"); + } + }); + List sql = LoggedSqlCollector.stop(); + + assertThat(sql.get(0)).contains("limit 10"); + } @Test public void findEach() {