beanType, String eql) {
+
+ return serverMgr.getDefaultServer().createQuery(beanType, eql);
+ }
+
/**
* Create a query for a type of entity bean.
*
diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java
index 9e2d4a77a..3392334a6 100644
--- a/src/main/java/com/avaje/ebean/EbeanServer.java
+++ b/src/main/java/com/avaje/ebean/EbeanServer.java
@@ -219,6 +219,43 @@ public interface EbeanServer {
*/
Query createQuery(Class beanType);
+ /**
+ * Parse the Ebean query language statement returning the query which can then
+ * be modified (add expressions, change order by clause, change maxRows, change
+ * fetch and select paths etc).
+ *
+ * Example
+ *
+ * {@code
+ *
+ * // Find order additionally fetching the customer, details and details.product name.
+ *
+ * String eql = "fetch customer fetch details fetch details.product (name) where id = :orderId ";
+ *
+ * Query query = Ebean.createQuery(Order.class, eql);
+ * query.setParameter("orderId", 2);
+ *
+ * Order order = query.findUnique();
+ *
+ * // This is the same as:
+ *
+ * Order order = Ebean.find(Order.class)
+ * .fetch("customer")
+ * .fetch("details")
+ * .fetch("detail.product", "name")
+ * .setId(2)
+ * .findUnique();
+ *
+ * }
+ *
+ * @param beanType The type of bean to fetch
+ * @param eql The Ebean query
+ * @param The type of the entity bean
+ *
+ * @return The query with expressions defined as per the parsed query statement
+ */
+ Query createQuery(Class beanType, String eql);
+
/**
* Create a query for a type of entity bean.
*
diff --git a/src/main/java/com/avaje/ebean/OrderBy.java b/src/main/java/com/avaje/ebean/OrderBy.java
index dcdb638c6..ff1b8cc1c 100644
--- a/src/main/java/com/avaje/ebean/OrderBy.java
+++ b/src/main/java/com/avaje/ebean/OrderBy.java
@@ -213,8 +213,9 @@ public final class OrderBy implements Serializable {
* order by clause and replace.
*
*/
- public void clear() {
+ public OrderBy clear() {
list.clear();
+ return this;
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
index dec547896..4216a17f8 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
@@ -296,6 +296,14 @@ public interface SpiQuery extends Query {
*/
void setLoadDescription(String loadMode, String loadDescription);
+ /**
+ * Check that the named parameters have had their values set.
+ */
+ void checkNamedParameters();
+
+ /**
+ * Create a named parameter placeholder.
+ */
SpiNamedParam createNamedParameter(String parameterName);
/**
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 30d2304fd..56c88112b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
@@ -40,6 +40,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
import com.avaje.ebeaninternal.server.el.ElFilter;
+import com.avaje.ebeaninternal.server.grammer.EqlParser;
import com.avaje.ebeaninternal.server.lib.ShutdownManager;
import com.avaje.ebeaninternal.server.query.CQuery;
import com.avaje.ebeaninternal.server.query.CQueryEngine;
@@ -902,6 +903,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
throw new PersistenceException("No named query called " + namedQuery + " for bean:" + beanType.getName());
}
+ @Override
+ public Query createQuery(Class beanType, String eql) {
+
+ DefaultOrmQuery query = createQuery(beanType);
+ EqlParser.parse(eql, query);
+ return query;
+ }
+
public DefaultOrmQuery createQuery(Class beanType) {
BeanDescriptor desc = getBeanDescriptor(beanType);
if (desc == null) {
@@ -954,6 +963,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery spiQuery = (SpiQuery) query;
spiQuery.setType(type);
+ spiQuery.checkNamedParameters();
return createQueryRequest(spiQuery, t);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/grammer/EqlParser.java b/src/main/java/com/avaje/ebeaninternal/server/grammer/EqlParser.java
index d5e5754e1..083c49e49 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/grammer/EqlParser.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/grammer/EqlParser.java
@@ -7,8 +7,14 @@ import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
+/**
+ * Parse EQL query language applying it to an ORM query object.
+ */
public class EqlParser {
+ /**
+ * Parse the raw EQL query and apply it to the supplied query.
+ */
public static void parse(String raw, SpiQuery query) {
EQLLexer lexer = new EQLLexer(new ANTLRInputStream(raw));
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 eb2a8deac..8e8caabc9 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
@@ -29,6 +29,7 @@ import com.avaje.ebeaninternal.server.query.CancelableQuery;
import java.sql.Timestamp;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -1395,6 +1396,16 @@ public class DefaultOrmQuery implements SpiQuery {
this.generatedSql = generatedSql;
}
+ @Override
+ public void checkNamedParameters() {
+ if (namedParams != null) {
+ Collection values = namedParams.values();
+ for (ONamedParam value : values) {
+ value.checkValueSet();
+ }
+ }
+ }
+
@Override
public SpiNamedParam createNamedParameter(String name) {
if (namedParams == null) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/ONamedParam.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/ONamedParam.java
index 04fa405e7..66975ab2d 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/ONamedParam.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/ONamedParam.java
@@ -2,21 +2,44 @@ package com.avaje.ebeaninternal.server.querydefn;
import com.avaje.ebeaninternal.api.SpiNamedParam;
+import javax.persistence.PersistenceException;
+
+/**
+ * Named parameter used as placeholder in expressions created by EQL language parsing.
+ */
public class ONamedParam implements SpiNamedParam {
private final String name;
private Object value;
+ /**
+ * Create with the given name.
+ */
public ONamedParam(String name) {
this.name = name;
}
+ /**
+ * Set the bind value for this named parameter.
+ */
public void setValue(Object value) {
this.value = value;
}
+ /**
+ * Return the bind value for this named parameter.
+ */
public Object getValue() {
return value;
}
+
+ /**
+ * Check the bind value has been set (so does not support null value).
+ */
+ public void checkValueSet() {
+ if (value == null) {
+ throw new PersistenceException("Named parameter ["+name+"] has not had it's value set.");
+ }
+ }
}
diff --git a/src/test/java/com/avaje/ebean/EbeanServer_eqlTest.java b/src/test/java/com/avaje/ebean/EbeanServer_eqlTest.java
new file mode 100644
index 000000000..c3479e470
--- /dev/null
+++ b/src/test/java/com/avaje/ebean/EbeanServer_eqlTest.java
@@ -0,0 +1,71 @@
+package com.avaje.ebean;
+
+import com.avaje.tests.model.basic.Customer;
+import com.avaje.tests.model.basic.ResetBasicData;
+import org.junit.Test;
+
+import javax.persistence.PersistenceException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EbeanServer_eqlTest extends BaseTestCase {
+
+
+ @Test
+ public void basic() {
+
+ ResetBasicData.reset();
+
+ Query query = server().createQuery(Customer.class, "order by id limit 10");
+ query.setMaxRows(100);
+ query.findList();
+
+ assertThat(query.getGeneratedSql()).contains("order by t0.id ");
+ }
+
+ @Test
+ public void basic_via_Ebean_defaultServer() {
+
+ ResetBasicData.reset();
+
+ Query query = Ebean.createQuery(Customer.class, "order by id limit 10");
+ query.findList();
+
+ assertThat(query.getGeneratedSql()).contains("order by t0.id ");
+ }
+
+ @Test
+ public void orderBy_override() {
+
+ ResetBasicData.reset();
+
+ Query query = server().createQuery(Customer.class, "order by id");
+
+ // use clear() and then effectively override the orderBy clause
+ query.orderBy().clear().asc("name");
+ query.findList();
+
+ assertThat(query.getGeneratedSql()).contains("order by t0.name");
+ }
+
+
+ @Test
+ public void namedParams() {
+
+ ResetBasicData.reset();
+
+ Query query = server().createQuery(Customer.class, "where name startsWith :name order by name");
+ query.setParameter("name", "Ro");
+ query.findList();
+
+ assertThat(query.getGeneratedSql()).contains("where t0.name like ? ");
+ }
+
+ @Test(expected = PersistenceException.class)
+ public void unboundNamedParams_expect_PersistenceException() {
+
+ Query query = server().createQuery(Customer.class, "where name = :name");
+ query.findUnique();
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java
index 1273014ad..97c93f13f 100644
--- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java
+++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java
@@ -290,6 +290,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return null;
}
+ @Override
+ public Query createQuery(Class beanType, String eql) {
+ return null;
+ }
+
@Override
public Query createQuery(Class beanType) {
return null;