#291 - ENH: Add ability to validate a query or property expression - Was Please check column exist on entity

This commit is contained in:
Robin Bygrave
2015-11-30 15:10:22 +13:00
parent debd8ad617
commit fae39fe650
26 changed files with 245 additions and 4 deletions
@@ -1937,4 +1937,12 @@ public interface EbeanServer {
*/
<T> List<T> draftRestore(Query<T> query);
/**
* Returns the set of properties/paths that are unknown (do not map to known properties or paths).
* <p>
* Validate the query checking the where and orderBy expression paths to confirm if
* they represent valid properties/path for the given bean type.
* </p>
*/
<T> Set<String> validateQuery(Query<T> query);
}
+9
View File
@@ -1318,4 +1318,13 @@ public interface Query<T> extends Serializable {
* </p>
*/
Query<T> setDisableLazyLoading(boolean disableLazyLoading);
/**
* Returns the set of properties or paths that are unknown (do not map to known properties or paths).
* <p>
* Validate the query checking the where and orderBy expression paths to confirm if
* they represent valid properties or paths for the given bean type.
* </p>
*/
Set<String> validate();
}
@@ -17,8 +17,13 @@ public interface SpiBeanType<T> {
Class<T> getBeanType();
/**
* Return the base table this bean type maps to.
* Return true if the property is a valid known property or path for the given bean type.
*/
boolean isValidExpression(String property);
/**
* Return the base table this bean type maps to.
*/
String getBaseTable();
/**
@@ -66,4 +66,9 @@ public interface SpiExpression extends Expression {
* the associated request.
*/
void addBindValues(SpiExpressionRequest request);
/**
* Validate all the properties/paths associated with this expression.
*/
void validate(SpiExpressionValidation validation);
}
@@ -67,4 +67,8 @@ public interface SpiExpressionList<T> extends ExpressionList<T> {
*/
void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder);
/**
* Validate all the properties/paths used in this expression list.
*/
void validate(SpiExpressionValidation validation);
}
@@ -0,0 +1,37 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.plugin.SpiBeanType;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Property expression validation request for a given root bean type.
*/
public class SpiExpressionValidation {
private final SpiBeanType<?> desc;
private final LinkedHashSet<String> unknown = new LinkedHashSet<String>();
public SpiExpressionValidation(SpiBeanType<?> desc) {
this.desc = desc;
}
/**
* Validate that the property expression (path) is valid.
*/
public void validate(String propertyName) {
if (!desc.isValidExpression(propertyName)) {
unknown.add(propertyName);
}
}
/**
* Return the set of properties considered as having unknown paths.
*/
public Set<String> getUnknownProperties() {
return unknown;
}
}
@@ -11,6 +11,7 @@ import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.event.readaudit.ReadEvent;
import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebeaninternal.server.autotune.ProfilingListener;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
@@ -22,6 +23,7 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import java.sql.Timestamp;
import java.util.List;
import java.util.Set;
/**
* Object Relational query - Internal extension to Query object.
@@ -694,4 +696,10 @@ public interface SpiQuery<T> extends Query<T> {
* Return root table alias set by {@link #alias(String)} command.
*/
String getAlias();
/**
* Validate the query returning the set of properties with unknown paths.
*/
Set<String> validate(SpiBeanType<T> desc);
}
@@ -908,8 +908,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
Class<T> beanType = (Class<T>) list.get(0).getClass();
BeanDescriptor<T> beanDescriptor = getBeanDescriptor(beanType);
if (beanDescriptor == null) {
String m = "BeanDescriptor not found, is [" + beanType + "] an entity bean?";
throw new PersistenceException(m);
throw new PersistenceException("BeanDescriptor not found, is [" + beanType + "] an entity bean?");
}
beanDescriptor.sort(list, sortByClause);
}
@@ -933,6 +932,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new DefaultOrmQuery<T>(beanType, this, expressionFactory, deployQuery);
}
@Override
public <T> Set<String> validateQuery(Query<T> query) {
BeanDescriptor<T> beanDescriptor = getBeanDescriptor(query.getBeanType());
if (beanDescriptor == null) {
throw new PersistenceException("BeanDescriptor not found, is [" + query.getBeanType() + "] an entity bean?");
}
return ((SpiQuery<T>)query).validate(beanDescriptor);
}
public <T> Filter<T> filter(Class<T> beanType) {
BeanDescriptor<T> desc = getBeanDescriptor(beanType);
if (desc == null) {
@@ -1602,6 +1602,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
return new ElComparatorProperty<T>(elGetValue, sortProp.isAscending(), nullsHigh);
}
@Override
public boolean isValidExpression(String propertyName) {
try {
return (getElGetValue(propertyName) != null);
} catch (PersistenceException e) {
return false;
}
}
/**
* Get an Expression language Value object.
*/
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
@@ -42,6 +43,11 @@ public abstract class AbstractExpression implements SpiExpression {
}
}
@Override
public void validate(SpiExpressionValidation validation) {
validation.validate(getPropertyName());
}
protected ElPropertyValue getElProp(SpiExpressionRequest request) {
String propertyName = getPropertyName();
@@ -8,6 +8,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
@@ -36,6 +37,13 @@ class AllEqualsExpression implements SpiExpression {
}
}
@Override
public void validate(SpiExpressionValidation validation) {
for (String propName: propMap.keySet()) {
validation.validate(propName);
}
}
public void addBindValues(SpiExpressionRequest request) {
if (propMap.isEmpty()) {
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
@@ -44,6 +45,12 @@ class BetweenPropertyExpression implements SpiExpression {
}
}
@Override
public void validate(SpiExpressionValidation validation) {
validation.validate(lowProperty);
validation.validate(highProperty);
}
public void addBindValues(SpiExpressionRequest request) {
request.addBindValue(value);
}
@@ -10,6 +10,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
@@ -121,6 +122,13 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
return this;
}
@Override
public void validate(SpiExpressionValidation validation) {
for (int i = 0; i < list.size(); i++) {
list.get(i).validate(validation);
}
}
/**
* Adds bind values to the request.
*/
@@ -8,6 +8,7 @@ import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.query.CQuery;
@@ -85,4 +86,9 @@ public class ExistsExpression implements SpiExpression {
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins) {
// Nothing to do for exists expression
}
@Override
public void validate(SpiExpressionValidation validation) {
// Nothing to do for exists expression
}
}
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.util.DefaultExpressionRequest;
@@ -28,6 +29,11 @@ class IdExpression implements SpiExpression {
}
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
}
public void addBindValues(SpiExpressionRequest request) {
// 'flatten' EmbeddedId and multiple Id cases
@@ -7,6 +7,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
import com.avaje.ebeaninternal.util.DefaultExpressionRequest;
@@ -27,6 +28,11 @@ public class IdInExpression implements SpiExpression {
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
}
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
}
public void addBindValues(SpiExpressionRequest request) {
// Bind the Id values including EmbeddedId and multiple Id
@@ -13,6 +13,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.util.DefaultExpressionList;
@@ -81,6 +82,11 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
}
}
@Override
public void validate(SpiExpressionValidation validation) {
exprList.validate(validation);
}
@Override
public Junction<T> add(Expression item) {
SpiExpression i = (SpiExpression) item;
@@ -6,6 +6,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
@@ -53,6 +54,12 @@ abstract class LogicExpression implements SpiExpression {
expTwo.containsMany(desc, manyWhereJoin);
}
@Override
public void validate(SpiExpressionValidation validation) {
expOne.validate(validation);
expTwo.validate(validation);
}
public void addBindValues(SpiExpressionRequest request) {
expOne.addBindValues(request);
expTwo.addBindValues(request);
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
@@ -19,6 +20,11 @@ class NoopExpression implements SpiExpression {
// nothing to do
}
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
}
@Override
public void queryAutoTuneHash(HashQueryPlanBuilder builder) {
builder.add(NoopExpression.class);
@@ -6,6 +6,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
final class NotExpression implements SpiExpression {
@@ -24,6 +25,11 @@ final class NotExpression implements SpiExpression {
exp.containsMany(desc, manyWhereJoin);
}
@Override
public void validate(SpiExpressionValidation validation) {
exp.validate(validation);
}
public void addBindValues(SpiExpressionRequest request) {
exp.addBindValues(request);
}
@@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
class RawExpression implements SpiExpression {
@@ -24,6 +25,11 @@ class RawExpression implements SpiExpression {
}
@Override
public void validate(SpiExpressionValidation validation) {
// always ignored
}
public void addBindValues(SpiExpressionRequest request) {
if (values != null) {
for (int i = 0; i < values.length; i++) {
@@ -9,6 +9,7 @@ import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.event.readaudit.ReadEvent;
import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.api.HashQuery;
@@ -17,6 +18,7 @@ import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.autotune.ProfilingListener;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
@@ -1413,4 +1415,28 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
}
@Override
public Set<String> validate() {
return server.validateQuery(this);
}
/**
* Validate all the expression properties/paths given the bean descriptor.
*/
public Set<String> validate(SpiBeanType<T> desc) {
SpiExpressionValidation validation = new SpiExpressionValidation(desc);
if (whereExpressions != null) {
whereExpressions.validate(validation);
}
if (havingExpressions != null) {
havingExpressions.validate(validation);
}
if (orderBy != null) {
for (Property property : orderBy.getProperties()) {
validation.validate(property.getProperty());
}
}
return validation.getUnknownProperties();
}
}
@@ -15,6 +15,7 @@ import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
@@ -98,6 +99,13 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
}
@Override
public void validate(SpiExpressionValidation validation) {
for (int i = 0; i < list.size(); i++) {
list.get(i).validate(validation);
}
}
@Override
public ExpressionList<T> endJunction() {
return parentExprList == null ? this : parentExprList;
@@ -26,6 +26,13 @@ public class SpiServerTest {
assertNull(beanType.getPersistListener());
assertNull(beanType.getQueryAdapter());
assertTrue(beanType.isValidExpression("name"));
assertTrue(beanType.isValidExpression("contacts.firstName"));
assertTrue(beanType.isValidExpression("contacts.group.name"));
assertFalse(beanType.isValidExpression("junk"));
assertFalse(beanType.isValidExpression("Name"));
assertFalse(beanType.isValidExpression("contacts.name"));
Customer customer = new Customer();
customer.setId(42);
@@ -292,6 +292,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return null;
}
@Override
public <T> Set<String> validateQuery(Query<T> query) {
return null;
}
@Override
public Object nextId(Class<?> beanType) {
return null;
@@ -1,7 +1,10 @@
package com.avaje.tests.query.orderby;
import java.util.List;
import java.util.Set;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.ResetBasicData;
import org.junit.Assert;
import org.junit.Test;
@@ -12,9 +15,29 @@ import com.avaje.tests.model.basic.MRole;
import com.avaje.tests.model.basic.MUser;
import com.avaje.tests.model.basic.MUserType;
import static org.assertj.core.api.Assertions.assertThat;
public class TestOrderByWithDistinct extends BaseTestCase {
@Test
public void testOrderByValidation() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
.where()
.eq("junk","blah")
.eq("name","jim")
.orderBy("id desc,path.that.does.not.exist,contacts.group.name asc");
Set<String> unknownProperties = query.validate();
assertThat(unknownProperties).isNotEmpty();
assertThat(unknownProperties).hasSize(2);
assertThat(unknownProperties).contains("junk","path.that.does.not.exist");
}
@Test
public void test() {
/*