@@ -1114,6 +1113,21 @@ public interface EbeanServer {
@Nullable
T findUnique(Query query, Transaction transaction);
+ /**
+ * Execute as a delete query deleting the 'root level' beans that match the predicates
+ * in the query.
+ *
+ * Note that if the query includes joins then the generated delete statement may not be
+ * optimal depending on the database platform.
+ *
+ *
+ * @param query the query used for the delete
+ * @param transaction the transaction to use (can be null)
+ * @param the type of entity bean to fetch.
+ * @return the number of beans/rows that were deleted
+ */
+ int delete(Query query, Transaction transaction);
+
/**
* Execute the sql query returning a list of MapBean.
*
diff --git a/src/main/java/com/avaje/ebean/ExpressionList.java b/src/main/java/com/avaje/ebean/ExpressionList.java
index 0a8ba54c2..bbec05217 100644
--- a/src/main/java/com/avaje/ebean/ExpressionList.java
+++ b/src/main/java/com/avaje/ebean/ExpressionList.java
@@ -107,6 +107,18 @@ public interface ExpressionList extends Serializable {
*/
Query asOf(Timestamp asOf);
+ /**
+ * Execute as a delete query deleting the 'root level' beans that match the predicates
+ * in the query.
+ *
+ * Note that if the query includes joins then the generated delete statement may not be
+ * optimal depending on the database platform.
+ *
+ *
+ * @return the number of beans/rows that were deleted.
+ */
+ int delete();
+
/**
* Execute the query iterating over the results.
*
diff --git a/src/main/java/com/avaje/ebean/Query.java b/src/main/java/com/avaje/ebean/Query.java
index 63f27f1df..ce1582255 100644
--- a/src/main/java/com/avaje/ebean/Query.java
+++ b/src/main/java/com/avaje/ebean/Query.java
@@ -743,6 +743,18 @@ public interface Query extends Serializable {
*/
List> findVersionsBetween(Timestamp start, Timestamp end);
+ /**
+ * Execute as a delete query deleting the 'root level' beans that match the predicates
+ * in the query.
+ *
+ * Note that if the query includes joins then the generated delete statement may not be
+ * optimal depending on the database platform.
+ *
+ *
+ * @return the number of beans/rows that were deleted.
+ */
+ int delete();
+
/**
* Return the count of entities this query should return.
*
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
index ec54a63a1..1f496d319 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
@@ -84,7 +84,12 @@ public interface SpiQuery extends Query {
/**
* A subquery used as part of a where clause.
*/
- SUBQUERY
+ SUBQUERY,
+
+ /**
+ * Delete query.
+ */
+ DELETE,
}
enum TemporalMode {
@@ -272,6 +277,11 @@ public interface SpiQuery extends Query {
*/
NaturalKeyBindParam getNaturalKeyBindParam();
+ /**
+ * Set the query to be a delete query.
+ */
+ void setDelete();
+
/**
* Set the query to select the id property only.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
index e38a555de..a716516c5 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
@@ -1338,6 +1338,17 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
+ public int delete(Query query, Transaction t) {
+
+ SpiOrmQueryRequest request = createQueryRequest(Type.DELETE, query, t);
+ try {
+ request.initTransIfRequired();
+ return request.delete();
+ } finally {
+ request.endTransIfRequired();
+ }
+ }
+
public FutureRowCount findFutureRowCount(Query q, Transaction t) {
SpiQuery copy = ((SpiQuery) q).copy();
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryEngine.java
index 425685f3e..75b04fa35 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryEngine.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryEngine.java
@@ -39,4 +39,8 @@ public interface OrmQueryEngine {
*/
BeanIdList findIds(OrmQueryRequest request);
+ /**
+ * Execute the query as a delete statement.
+ */
+ int delete(OrmQueryRequest request);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java
index aff6460bb..00ec3564d 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java
@@ -218,7 +218,14 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe
public boolean isFindById() {
return query.getType() == Type.BEAN;
}
-
+
+ /**
+ * Execute the query as a delete.
+ */
+ public int delete() {
+ return queryEngine.delete(this);
+ }
+
/**
* Execute the query as findById.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java
index 1204b3335..b463f2d13 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java
@@ -47,6 +47,11 @@ public interface SpiOrmQueryRequest {
*/
void endTransIfRequired();
+ /**
+ * Execute the query as a delete.
+ */
+ int delete();
+
/**
* Execute the query as findById.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
index 22cf7c42f..e73218217 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -677,6 +677,13 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType {
}
}
+ /**
+ * Return the "delete by id" sql.
+ */
+ public String getDeleteByIdInSql() {
+ return deleteByIdInSql;
+ }
+
/**
* Return SQL that can be used to delete a list of Id's without any optimistic
* concurrency checking.
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java
index 2312dc3c1..7c6abbb94 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java
@@ -218,6 +218,11 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr
throw new RuntimeException("filterMany not allowed on Junction expression list");
}
+ @Override
+ public int delete() {
+ return exprList.delete();
+ }
+
@Override
public Query asOf(Timestamp asOf) {
return exprList.asOf(asOf);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java
index 5cb8ed158..40350fa77 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java
@@ -83,6 +83,47 @@ public class CQueryBuilder {
return sb.toString();
}
+ /**
+ * Build the delete query.
+ */
+ public CQueryDelete buildDeleteQuery(OrmQueryRequest request) {
+
+ SpiQuery query = request.getQuery();
+ query.setDelete();
+
+ CQueryPredicates predicates = new CQueryPredicates(binder, request);
+ CQueryPlan queryPlan = request.getQueryPlan();
+ if (queryPlan != null) {
+ // skip building the SqlTree and Sql string
+ predicates.prepare(false);
+ String sql = queryPlan.getSql();
+ return new CQueryDelete(request, predicates, sql);
+ }
+
+ predicates.prepare(true);
+
+ SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query));
+
+ boolean includeJoins = sqlTree.isIncludeJoins();
+
+ String sql;
+ if (!includeJoins) {
+ // simple - delete from table ...
+ sql = buildSql("delete", request, predicates, sqlTree).getSql();
+
+ } else {
+ // wrap as - delete from table where id in (select id ...)
+ sql = buildSql(null, request, predicates, sqlTree).getSql();
+ sql = request.getBeanDescriptor().getDeleteByIdInSql() + "in (" + sql + ")";
+ }
+
+ // cache the query plan
+ queryPlan = new CQueryPlan(request, sql, sqlTree, false, false, predicates.getLogWhereSql());
+
+ request.putQueryPlan(queryPlan);
+ return new CQueryDelete(request, predicates, sql);
+ }
+
/**
* Build the row count query.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryDelete.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryDelete.java
new file mode 100644
index 000000000..a83362ffd
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryDelete.java
@@ -0,0 +1,135 @@
+package com.avaje.ebeaninternal.server.query;
+
+import com.avaje.ebeaninternal.api.SpiQuery;
+import com.avaje.ebeaninternal.api.SpiTransaction;
+import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+import com.avaje.ebeaninternal.server.type.DataBind;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * Executes the delete query.
+ */
+public class CQueryDelete {
+
+ private static final Logger logger = LoggerFactory.getLogger(CQueryDelete.class);
+
+ private final OrmQueryRequest> request;
+
+ private final BeanDescriptor> desc;
+
+ private final SpiQuery> query;
+
+ /**
+ * Where clause predicates.
+ */
+ private final CQueryPredicates predicates;
+
+ /**
+ * The final sql that is generated.
+ */
+ private final String sql;
+
+ /**
+ * The statement used to create the resultSet.
+ */
+ private PreparedStatement pstmt;
+
+ private String bindLog;
+
+ private int executionTimeMicros;
+
+ private int rowCount;
+
+ /**
+ * Create the Sql select based on the request.
+ */
+ public CQueryDelete(OrmQueryRequest> request, CQueryPredicates predicates, String sql) {
+
+ this.request = request;
+ this.query = request.getQuery();
+ this.sql = sql;
+ query.setGeneratedSql(sql);
+ this.desc = request.getBeanDescriptor();
+ this.predicates = predicates;
+ }
+
+ /**
+ * Return a summary description of this query.
+ */
+ public String getSummary() {
+ //noinspection StringBufferReplaceableByString
+ StringBuilder sb = new StringBuilder(80);
+ sb.append("Delete exeMicros[").append(executionTimeMicros)
+ .append("] rows[").append(rowCount)
+ .append("] type[").append(desc.getName())
+ .append("] predicates[").append(predicates.getLogWhereSql())
+ .append("] bind[").append(bindLog).append("]");
+
+ return sb.toString();
+ }
+
+ /**
+ * Return the bind log.
+ */
+ public String getBindLog() {
+ return bindLog;
+ }
+
+ /**
+ * Return the generated sql.
+ */
+ public String getGeneratedSql() {
+ return sql;
+ }
+
+ /**
+ * Execute the query returning the row count.
+ */
+ public int delete() throws SQLException {
+
+ long startNano = System.nanoTime();
+
+ try {
+
+ SpiTransaction t = request.getTransaction();
+ Connection conn = t.getInternalConnection();
+ pstmt = conn.prepareStatement(sql);
+
+ if (query.getTimeout() > 0) {
+ pstmt.setQueryTimeout(query.getTimeout());
+ }
+
+ bindLog = predicates.bind(new DataBind(pstmt));
+ rowCount = pstmt.executeUpdate();
+
+ long exeNano = System.nanoTime() - startNano;
+ executionTimeMicros = (int) exeNano / 1000;
+
+ return rowCount;
+
+ } finally {
+ close();
+ }
+ }
+
+ /**
+ * Close the resources.
+ */
+ private void close() {
+ try {
+ if (pstmt != null) {
+ pstmt.close();
+ pstmt = null;
+ }
+ } catch (SQLException e) {
+ logger.error(null, e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java
index 73bd7cf49..49fa7e74a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java
@@ -53,6 +53,27 @@ public class CQueryEngine {
return queryBuilder.buildQuery(request);
}
+ public int delete(OrmQueryRequest request) {
+
+ CQueryDelete query = queryBuilder.buildDeleteQuery(request);
+ try {
+ int rows = query.delete();
+
+ if (request.isLogSql()) {
+ String logSql = query.getGeneratedSql();
+ if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
+ logSql = Str.add(logSql, "; --bind(", query.getBindLog(), ") rows:", String.valueOf(rows));
+ }
+ request.logSql(logSql);
+ }
+
+ return rows;
+
+ } catch (SQLException e) {
+ throw CQuery.createPersistenceException(e, request.getTransaction(), query.getBindLog(), query.getGeneratedSql());
+ }
+ }
+
/**
* Build and execute the find Id's query.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java
index 55eecd392..2f70f8516 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java
@@ -47,6 +47,12 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
}
}
+ public int delete(OrmQueryRequest request) {
+
+ flushJdbcBatchOnQuery(request);
+ return queryEngine.delete(request);
+ }
+
public int findRowCount(OrmQueryRequest request) {
flushJdbcBatchOnQuery(request);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java
index a278c2463..06b2e6e40 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java
@@ -41,11 +41,13 @@ public class SqlTree {
*/
private final String inheritanceWhereSql;
+ private final boolean includeJoins;
+
/**
* Create the SqlSelectClause.
*/
public SqlTree(String summary, SqlTreeNode rootNode, String selectSql, String fromSql, String inheritanceWhereSql,
- BeanProperty[] encryptedProps, BeanPropertyAssocMany> manyProperty, Set includes) {
+ BeanProperty[] encryptedProps, BeanPropertyAssocMany> manyProperty, Set includes, boolean includeJoins) {
this.summary = summary;
this.rootNode = rootNode;
@@ -55,6 +57,7 @@ public class SqlTree {
this.encryptedProps = encryptedProps;
this.manyProperty = manyProperty;
this.includes = includes;
+ this.includeJoins = includeJoins;
}
/**
@@ -69,6 +72,14 @@ public class SqlTree {
this.encryptedProps = null;
this.manyProperty = null;
this.includes = null;
+ this.includeJoins = false; //not valid for rawSql
+ }
+
+ /**
+ * Return true if the query includes joins (not valid for rawSql).
+ */
+ public boolean isIncludeJoins() {
+ return includeJoins;
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeAlias.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeAlias.java
index 986845232..483c6ca66 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeAlias.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeAlias.java
@@ -205,4 +205,11 @@ public class SqlTreeAlias {
private String nextManyWhereTableAlias() {
return "u" + (++manyWhereCounter);
}
+
+ /**
+ * Return true if there are joins included in the query.
+ */
+ public boolean isIncludeJoins() {
+ return !aliasMap.isEmpty() || !manyWhereAliasMap.isEmpty();
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java
index 51f94ce4c..68304fffe 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java
@@ -1,17 +1,5 @@
package com.avaje.ebeaninternal.server.query;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.PropertyJoin;
import com.avaje.ebeaninternal.api.SpiQuery;
@@ -27,6 +15,17 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Factory for SqlTree.
@@ -138,8 +137,10 @@ public class SqlTreeBuilder {
encryptedProps = ctx.getEncryptedProps();
}
+ boolean includeJoins = (alias == null) ? false : alias.isIncludeJoins();
+
return new SqlTree(summary.toString(), rootNode, selectSql, fromSql, inheritanceWhereSql, encryptedProps,
- manyProperty, queryDetail.getIncludes());
+ manyProperty, queryDetail.getIncludes(), includeJoins);
}
private String buildSelectClause() {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
index a313db2e5..db2dfb342 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
@@ -413,6 +413,17 @@ public class DefaultOrmQuery implements SpiQuery {
detail.convertManyFetchJoinsToQueryJoins(beanDescriptor, lazyLoadManyPath, allowOne, queryBatch);
}
+ /**
+ * Setup to be a delete query.
+ */
+ public void setDelete() {
+ // unset any paging and select on the id in the case where the query
+ // includes joins and we use - delete ... where id in (...)
+ maxRows = 0;
+ firstRow = 0;
+ setSelectId();
+ }
+
/**
* Set the select clause to select the Id property.
*/
@@ -948,6 +959,11 @@ public class DefaultOrmQuery implements SpiQuery {
return this;
}
+ @Override
+ public int delete() {
+ return server.delete(this, null);
+ }
+
public List