#278 - SqlQuery setFirstRow fail - modify SQL using LIMIT/OFFSET etc to support firstRows/maxRows

This commit is contained in:
Robin Bygrave
2016-05-12 20:59:52 +12:00
parent 58a36e6b67
commit 8cb8f9eabf
12 changed files with 256 additions and 60 deletions
@@ -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();
}
}
@@ -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();
}
}
@@ -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);
}
@@ -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)
*/
@@ -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();
@@ -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.
*/
@@ -74,19 +74,9 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
try {
request.executeSql(binder);
int maxRows = request.getMaxRows();
int loadRowCount = 0;
List<SqlRow> rows = new ArrayList<SqlRow>();
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);
@@ -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);
}
}
@@ -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);
}
}
@@ -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<Order> 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<Order> 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<Order> 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<Order> 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);
}
}
@@ -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;
}
}
@@ -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<SqlRow> list = sqlQuery.findList();
List<String> 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<String> 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<String> 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<SqlRow>() {
@Override
public void accept(SqlRow bean) {
bean.get("id");
}
});
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0)).contains("limit 10");
}
@Test
public void findEach() {