From a4095b4ae5904c7dcfe1d20490cf1cc6ddaea93b Mon Sep 17 00:00:00 2001
From: Rob Bygrave
Date: Thu, 12 Jun 2014 21:11:14 +1200
Subject: [PATCH] Fix for #142 - ENH: Add support for using RawSql with user
supplied ResultSet
---
src/main/java/com/avaje/ebean/RawSql.java | 54 ++++++++++++++-
.../java/com/avaje/ebean/RawSqlBuilder.java | 29 +++++++-
.../ebeaninternal/server/query/CQuery.java | 10 +++
.../server/query/CQueryBuilderRawSql.java | 7 +-
.../rawsql/TestRawSqlPositionedParams.java | 4 +-
.../tests/rawsql/TestRawSqlWithResultSet.java | 67 +++++++++++++++++++
6 files changed, 163 insertions(+), 8 deletions(-)
create mode 100644 src/test/java/com/avaje/tests/rawsql/TestRawSqlWithResultSet.java
diff --git a/src/main/java/com/avaje/ebean/RawSql.java b/src/main/java/com/avaje/ebean/RawSql.java
index ffc703aec..4dc5211fb 100644
--- a/src/main/java/com/avaje/ebean/RawSql.java
+++ b/src/main/java/com/avaje/ebean/RawSql.java
@@ -1,6 +1,7 @@
package com.avaje.ebean;
import java.io.Serializable;
+import java.sql.ResultSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@@ -135,18 +136,33 @@ import com.avaje.ebean.util.CamelCaseHelper;
* Note that lazy loading also works with object graphs built with RawSql.
*
*
- * @author rbygrave
- *
*/
public final class RawSql implements Serializable {
private static final long serialVersionUID = 1L;
+ private final ResultSet resultSet;
+
private final Sql sql;
private final ColumnMapping columnMapping;
- protected RawSql(Sql sql, ColumnMapping columnMapping) {
+ /**
+ * Construct with a ResultSet and properties that the columns map to.
+ *
+ * The properties listed in the propertyNames must be in the same order as the columns in the
+ * resultSet.
+ *
+ * When a query executes this RawSql object then it will close the resultSet.
+ */
+ public RawSql(ResultSet resultSet, String... propertyNames) {
+ this.resultSet = resultSet;
+ this.sql = null;
+ this.columnMapping = new ColumnMapping(propertyNames);
+ }
+
+ protected RawSql(ResultSet resultSet, Sql sql, ColumnMapping columnMapping) {
+ this.resultSet = resultSet;
this.sql = sql;
this.columnMapping = columnMapping;
}
@@ -158,6 +174,14 @@ public final class RawSql implements Serializable {
return sql;
}
+
+ /**
+ * Return the resultSet if this is a ResultSet based RawSql.
+ */
+ public ResultSet getResultSet() {
+ return resultSet;
+ }
+
/**
* Return the column mapping for the SQL columns to bean properties.
*/
@@ -169,6 +193,9 @@ public final class RawSql implements Serializable {
* Return the hash for this query.
*/
public int queryHash() {
+ if (resultSet != null) {
+ return 31 * columnMapping.queryHash();
+ }
return 31 * sql.queryHash() + columnMapping.queryHash();
}
@@ -329,6 +356,7 @@ public final class RawSql implements Serializable {
private final LinkedHashMap dbColumnMap;
private final Map propertyMap;
+
private final Map propertyColumnMap;
private final boolean parsed;
@@ -364,6 +392,26 @@ public final class RawSql implements Serializable {
this.propertyColumnMap = null;
this.dbColumnMap = new LinkedHashMap();
}
+
+ /**
+ * Construct for ResultSet use.
+ */
+ protected ColumnMapping(String... propertyNames) {
+ this.immutable = false;
+ this.parsed = false;
+ this.propertyMap = null;
+ //this.propertyColumnMap = null;
+ this.dbColumnMap = new LinkedHashMap();
+
+ int hc = 31;
+ int pos = 0;
+ for (String prop : propertyNames) {
+ hc = 31 * hc + prop.hashCode();
+ dbColumnMap.put(prop, new Column(pos++, prop, null, prop));
+ }
+ propertyColumnMap = dbColumnMap;
+ this.queryHashCode = hc;
+ }
/**
* Construct an immutable ColumnMapping based on collected information.
diff --git a/src/main/java/com/avaje/ebean/RawSqlBuilder.java b/src/main/java/com/avaje/ebean/RawSqlBuilder.java
index 7749829b2..b491083a7 100644
--- a/src/main/java/com/avaje/ebean/RawSqlBuilder.java
+++ b/src/main/java/com/avaje/ebean/RawSqlBuilder.java
@@ -1,5 +1,7 @@
package com.avaje.ebean;
+import java.sql.ResultSet;
+
import com.avaje.ebean.RawSql.ColumnMapping;
import com.avaje.ebean.RawSql.Sql;
@@ -10,8 +12,6 @@ import com.avaje.ebean.RawSql.Sql;
* named query.
*
*
- * @author rbygrave
- *
* @see RawSql
*/
public class RawSqlBuilder {
@@ -21,10 +21,23 @@ public class RawSqlBuilder {
*/
public static final String IGNORE_COLUMN = "$$_IGNORE_COLUMN_$$";
+ private final ResultSet resultSet;
+
private final Sql sql;
private final ColumnMapping columnMapping;
+ /**
+ * Create and return a RawSql object based on the resultSet and list of properties the columns in
+ * the resultSet map to.
+ *
+ * The properties listed in the propertyNames must be in the same order as the columns in the
+ * resultSet.
+ */
+ public static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
+ return new RawSql(resultSet, propertyNames);
+ }
+
/**
* Return an unparsed RawSqlBuilder. Unlike a parsed one this query can not be
* modified - so no additional WHERE or HAVING expressions can be added to
@@ -58,9 +71,17 @@ public class RawSqlBuilder {
return new RawSqlBuilder(sql2, mapping);
}
+
+ private RawSqlBuilder(ResultSet resultSet, ColumnMapping columnMapping) {
+ this.resultSet = resultSet;
+ this.columnMapping = columnMapping;
+ this.sql = null;
+ }
+
private RawSqlBuilder(Sql sql, ColumnMapping columnMapping) {
this.sql = sql;
this.columnMapping = columnMapping;
+ this.resultSet = null;
}
/**
@@ -92,7 +113,7 @@ public class RawSqlBuilder {
* has been defined.
*/
public RawSql create() {
- return new RawSql(sql, columnMapping.createImmutableCopy());
+ return new RawSql(resultSet, sql, columnMapping.createImmutableCopy());
}
/**
@@ -101,4 +122,6 @@ public class RawSqlBuilder {
protected Sql getSql() {
return sql;
}
+
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java
index 218940be3..68444a167 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java
@@ -357,6 +357,16 @@ public class CQuery implements DbReadContext, CancelableQuery {
SpiTransaction t = request.getTransaction();
Connection conn = t.getInternalConnection();
+ if (query.isRawSql()) {
+ ResultSet suppliedResultSet = query.getRawSql().getResultSet();
+ if (suppliedResultSet != null) {
+ // this is a user supplied ResultSet so use that
+ dataReader = queryPlan.createDataReader(suppliedResultSet);
+ bindLog = "";
+ return true;
+ }
+ }
+
if (forwardOnlyHint) {
// Use forward only hints for large resultset processing (Issue 56, MySql specific)
pstmt = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilderRawSql.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilderRawSql.java
index f9b42bd4d..9f5f31173 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilderRawSql.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilderRawSql.java
@@ -25,7 +25,12 @@ public class CQueryBuilderRawSql implements Constants {
* Build the full SQL Select statement for the request.
*/
public SqlLimitResponse buildSql(OrmQueryRequest> request, CQueryPredicates predicates, RawSql.Sql rsql) {
-
+
+ if (rsql == null) {
+ // this is a ResultSet based RawSql query - just use some placeholder for the SQL
+ return new SqlLimitResponse("--ResultSetBasedRawSql", false);
+ }
+
if (!rsql.isParsed()){
String sql = rsql.getUnparsedSql();
BindParams bindParams = request.getQuery().getBindParams();
diff --git a/src/test/java/com/avaje/tests/rawsql/TestRawSqlPositionedParams.java b/src/test/java/com/avaje/tests/rawsql/TestRawSqlPositionedParams.java
index cf6fca526..bb65bc0d9 100644
--- a/src/test/java/com/avaje/tests/rawsql/TestRawSqlPositionedParams.java
+++ b/src/test/java/com/avaje/tests/rawsql/TestRawSqlPositionedParams.java
@@ -35,13 +35,15 @@ public class TestRawSqlPositionedParams extends BaseTestCase {
Assert.assertNotNull(list);
}
+ @Test
public void test_unparsed() {
ResetBasicData.reset();
RawSql rawSql = RawSqlBuilder
.unparsed("select r.id, r.name from o_customer r where r.id >= ? and r.name like ?")
- .columnMapping("r.id", "id").columnMapping("r.name", "name").create();
+ .columnMapping("r.id", "id")
+ .columnMapping("r.name", "name").create();
Query query = Ebean.find(Customer.class);
query.setRawSql(rawSql);
diff --git a/src/test/java/com/avaje/tests/rawsql/TestRawSqlWithResultSet.java b/src/test/java/com/avaje/tests/rawsql/TestRawSqlWithResultSet.java
new file mode 100644
index 000000000..c84e906ef
--- /dev/null
+++ b/src/test/java/com/avaje/tests/rawsql/TestRawSqlWithResultSet.java
@@ -0,0 +1,67 @@
+package com.avaje.tests.rawsql;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.FetchConfig;
+import com.avaje.ebean.RawSql;
+import com.avaje.ebean.Transaction;
+import com.avaje.tests.model.basic.Customer;
+import com.avaje.tests.model.basic.ResetBasicData;
+
+public class TestRawSqlWithResultSet extends BaseTestCase {
+
+ @Test
+ public void test() throws SQLException {
+
+ ResetBasicData.reset();
+
+ // Transaction supplies our jdbc Connection
+ Transaction txn = Ebean.beginTransaction();
+
+ PreparedStatement pstmt = null;
+
+ try {
+ pstmt = txn.getConnection().prepareStatement("select id, name, billing_address_id from o_customer");
+
+ // ResultSet will be closed by Ebean
+ ResultSet resultSet = pstmt.executeQuery();
+
+ RawSql rawSql = new RawSql(resultSet, "id", "name", "billingAddress.id");
+
+ List list = Ebean.find(Customer.class)
+ .setRawSql(rawSql)
+ // also test a secondary query join
+ .fetch("billingAddress", new FetchConfig().query())
+ .findList();
+
+ for (Customer customer : list) {
+ System.out.println("id:"+customer.getId()+" name:"+customer.getName()+" billingAddress:"+customer.getBillingAddress());
+ }
+
+ } finally {
+ close(pstmt);
+ txn.end();
+ }
+
+ }
+
+ private static void close(Statement stmt) {
+
+ if (stmt != null) {
+ try {
+ stmt.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+}