mirror of
https://gitee.com/jmix/cuba.git
synced 2024-12-04 20:28:00 +08:00
Ability to specify sorting properties for database sorting used by gui tables. #PL-5354
This commit is contained in:
parent
0720c9764e
commit
ecc18a659b
@ -140,13 +140,13 @@ public class QueryTransformerRegexTest extends TestCase
|
||||
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
|
||||
"group by c.level having c.level > 0 order by c.level",
|
||||
"sec$GroupHierarchy");
|
||||
transformer.replaceOrderBy("group", false);
|
||||
transformer.replaceOrderBy(false, "group");
|
||||
String res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
|
||||
"group by c.level having c.level > 0 order by h.group",
|
||||
res);
|
||||
transformer.replaceOrderBy("group", true);
|
||||
transformer.replaceOrderBy(true, "group");
|
||||
res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
|
||||
@ -161,7 +161,7 @@ public class QueryTransformerRegexTest extends TestCase
|
||||
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
|
||||
"group by c.level having c.level > 0 order by h.group asc",
|
||||
"sec$GroupHierarchy");
|
||||
transformer.replaceOrderBy("group", true);
|
||||
transformer.replaceOrderBy(true, "group");
|
||||
String res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
|
||||
@ -172,7 +172,7 @@ public class QueryTransformerRegexTest extends TestCase
|
||||
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
|
||||
"group by c.level having c.level > 0 order by h.group desc",
|
||||
"sec$GroupHierarchy");
|
||||
transformer.replaceOrderBy("group", false);
|
||||
transformer.replaceOrderBy(false, "group");
|
||||
res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
|
||||
@ -183,7 +183,7 @@ public class QueryTransformerRegexTest extends TestCase
|
||||
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
|
||||
"group by c.level having c.level > 0 order by h.group desc",
|
||||
"sec$GroupHierarchy");
|
||||
transformer.replaceOrderBy("group", true);
|
||||
transformer.replaceOrderBy(true, "group");
|
||||
res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from sec$GroupHierarchy h join h.parent.constraints c where h.group = ?1 " +
|
||||
@ -196,12 +196,12 @@ public class QueryTransformerRegexTest extends TestCase
|
||||
QueryTransformerRegex transformer = new QueryTransformerRegex(
|
||||
"select c from ref$Car c where c.deleteTs is null",
|
||||
"ref$Car");
|
||||
transformer.replaceOrderBy("model.numberOfSeats", false);
|
||||
transformer.replaceOrderBy(false, "model.numberOfSeats");
|
||||
String res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from ref$Car c left join c.model c_model where c.deleteTs is null order by c_model.numberOfSeats",
|
||||
res);
|
||||
transformer.replaceOrderBy("model.numberOfSeats", true);
|
||||
transformer.replaceOrderBy(true, "model.numberOfSeats");
|
||||
res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from ref$Car c left join c.model c_model where c.deleteTs is null order by c_model.numberOfSeats desc",
|
||||
@ -211,18 +211,49 @@ public class QueryTransformerRegexTest extends TestCase
|
||||
transformer = new QueryTransformerRegex(
|
||||
"select c from ref$Car c where c.deleteTs is null",
|
||||
"ref$Car");
|
||||
transformer.replaceOrderBy("model.manufacturer.name", false);
|
||||
transformer.replaceOrderBy(false, "model.manufacturer.name");
|
||||
res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from ref$Car c left join c.model.manufacturer c_model_manufacturer where c.deleteTs is null order by c_model_manufacturer.name",
|
||||
res);
|
||||
transformer.replaceOrderBy("model.manufacturer.name", true);
|
||||
transformer.replaceOrderBy(true, "model.manufacturer.name");
|
||||
res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from ref$Car c left join c.model.manufacturer c_model_manufacturer where c.deleteTs is null order by c_model_manufacturer.name desc",
|
||||
res);
|
||||
}
|
||||
|
||||
public void testOrderBySeveralProperties() throws Exception {
|
||||
QueryTransformerRegex transformer = new QueryTransformerRegex(
|
||||
"select c from ref$Car c where c.deleteTs is null",
|
||||
"ref$Car");
|
||||
|
||||
|
||||
transformer.replaceOrderBy(false, "createTs", "vin");
|
||||
String res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from ref$Car c where c.deleteTs is null order by c.createTs, c.vin",
|
||||
res);
|
||||
|
||||
transformer.replaceOrderBy(false, "vin", "model.numberOfSeats");
|
||||
res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from ref$Car c left join c.model c_model where c.deleteTs is null order by c.vin, c_model.numberOfSeats",
|
||||
res);
|
||||
|
||||
transformer.replaceOrderBy(true, "vin", "model.numberOfSeats");
|
||||
res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from ref$Car c left join c.model c_model where c.deleteTs is null order by c.vin desc, c_model.numberOfSeats desc",
|
||||
res);
|
||||
|
||||
transformer.replaceOrderBy(false, "model.numberOfSeats", "vin");
|
||||
res = transformer.getResult();
|
||||
assertEquals(
|
||||
"select c from ref$Car c left join c.model c_model where c.deleteTs is null order by c_model.numberOfSeats, c.vin",
|
||||
res);
|
||||
}
|
||||
|
||||
public void testRemoveDistinct() {
|
||||
QueryTransformerRegex transformer;
|
||||
String res;
|
||||
|
@ -7,14 +7,17 @@ package com.haulmont.chile.core.model.utils;
|
||||
import com.haulmont.chile.core.annotations.NamePattern;
|
||||
import com.haulmont.chile.core.datatypes.impl.EnumClass;
|
||||
import com.haulmont.chile.core.model.Instance;
|
||||
import com.haulmont.chile.core.model.MetaClass;
|
||||
import com.haulmont.chile.core.model.MetaProperty;
|
||||
import com.haulmont.cuba.core.global.AppBeans;
|
||||
import com.haulmont.cuba.core.global.DevelopmentException;
|
||||
import com.haulmont.cuba.core.global.IllegalEntityStateException;
|
||||
import com.haulmont.cuba.core.global.Messages;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
@ -31,7 +34,7 @@ import static com.haulmont.bali.util.Preconditions.checkNotNullArgument;
|
||||
*/
|
||||
public final class InstanceUtils {
|
||||
|
||||
private static final Pattern instanceNameSplitPattern = Pattern.compile("[,;]");
|
||||
private static final Pattern INSTANCE_NAME_SPLIT_PATTERN = Pattern.compile("[,;]");
|
||||
|
||||
private InstanceUtils() {
|
||||
}
|
||||
@ -228,36 +231,26 @@ public final class InstanceUtils {
|
||||
public static String getInstanceName(Instance instance) {
|
||||
checkNotNullArgument(instance, "instance is null");
|
||||
|
||||
String pattern = (String) instance.getMetaClass().getAnnotations().get(NamePattern.class.getName());
|
||||
if (StringUtils.isBlank(pattern)) {
|
||||
NamePatternRec rec = parseNamePattern(instance.getMetaClass());
|
||||
if (rec == null) {
|
||||
return instance.toString();
|
||||
} else {
|
||||
int pos = pattern.indexOf("|");
|
||||
if (pos < 0)
|
||||
throw new IllegalArgumentException("Invalid name pattern: " + pattern);
|
||||
|
||||
String format = StringUtils.substring(pattern, 0, pos);
|
||||
String trimmedFormat = format.trim();
|
||||
|
||||
if (trimmedFormat.startsWith("#")) {
|
||||
if (rec.methodName != null) {
|
||||
try {
|
||||
Method method = instance.getClass().getMethod(trimmedFormat.substring(1));
|
||||
Method method = instance.getClass().getMethod(rec.methodName);
|
||||
Object result = method.invoke(instance);
|
||||
return (String) result;
|
||||
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new RuntimeException("Error getting instance name", e);
|
||||
}
|
||||
}
|
||||
|
||||
String fieldsStr = StringUtils.substring(pattern, pos + 1);
|
||||
|
||||
// lazy initialized messages, used only for enum values
|
||||
Messages messages = null;
|
||||
|
||||
String[] fields = instanceNameSplitPattern.split(fieldsStr);
|
||||
Object[] values = new Object[fields.length];
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
Object value = instance.getValue(fields[i]);
|
||||
Object[] values = new Object[rec.fields.length];
|
||||
for (int i = 0; i < rec.fields.length; i++) {
|
||||
Object value = instance.getValue(rec.fields[i]);
|
||||
if (value == null) {
|
||||
values[i] = "";
|
||||
} else if (value instanceof Instance) {
|
||||
@ -273,7 +266,51 @@ public final class InstanceUtils {
|
||||
}
|
||||
}
|
||||
|
||||
return String.format(format, values);
|
||||
return String.format(rec.format, values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a name pattern defined by {@link NamePattern} annotation.
|
||||
* @param metaClass entity meta-class
|
||||
* @return record containing the name pattern properties, or null if the @NamePattern is not defined for the meta-class
|
||||
*/
|
||||
@Nullable
|
||||
public static NamePatternRec parseNamePattern(MetaClass metaClass) {
|
||||
String pattern = (String) metaClass.getAnnotations().get(NamePattern.class.getName());
|
||||
if (StringUtils.isBlank(pattern))
|
||||
return null;
|
||||
|
||||
int pos = pattern.indexOf("|");
|
||||
if (pos < 0)
|
||||
throw new DevelopmentException("Invalid name pattern: " + pattern);
|
||||
|
||||
String format = StringUtils.substring(pattern, 0, pos);
|
||||
String trimmedFormat = format.trim();
|
||||
String methodName = trimmedFormat.startsWith("#") ? trimmedFormat.substring(1) : null;
|
||||
String fieldsStr = StringUtils.substring(pattern, pos + 1);
|
||||
String[] fields = INSTANCE_NAME_SPLIT_PATTERN.split(fieldsStr);
|
||||
return new NamePatternRec(format, methodName, fields);
|
||||
}
|
||||
|
||||
public static class NamePatternRec {
|
||||
/**
|
||||
* Name pattern string format
|
||||
*/
|
||||
public final String format;
|
||||
/**
|
||||
* Formatting method name or null
|
||||
*/
|
||||
public final String methodName;
|
||||
/**
|
||||
* Array of property names
|
||||
*/
|
||||
public final String[] fields;
|
||||
|
||||
public NamePatternRec(String format, String methodName, String[] fields) {
|
||||
this.fields = fields;
|
||||
this.format = format;
|
||||
this.methodName = methodName;
|
||||
}
|
||||
}
|
||||
}
|
@ -534,7 +534,15 @@ public class MetadataTools {
|
||||
public List<String> getRelatedProperties(Class<?> entityClass, String property) {
|
||||
List<String> result = new ArrayList<>();
|
||||
MetaClass metaClass = metadata.getClassNN(entityClass);
|
||||
MetaProperty metaProperty = metaClass.getPropertyNN(property);
|
||||
return getRelatedProperties(metaClass.getPropertyNN(property));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of related properties defined in {@link com.haulmont.chile.core.annotations.MetaProperty#related()}
|
||||
* or empty list
|
||||
*/
|
||||
public List<String> getRelatedProperties(MetaProperty metaProperty) {
|
||||
List<String> result = new ArrayList<>();
|
||||
String relatedProperties = (String) metaProperty.getAnnotations().get("relatedProperties");
|
||||
if (relatedProperties != null) {
|
||||
result.addAll(Arrays.asList(relatedProperties.split(",")));
|
||||
|
@ -44,8 +44,12 @@ public interface QueryTransformer {
|
||||
*/
|
||||
boolean removeDistinct();
|
||||
|
||||
/** DEPRECATED: use {@link #replaceOrderBy(boolean, String...)} */
|
||||
@Deprecated
|
||||
void replaceOrderBy(String property, boolean desc);
|
||||
|
||||
/** Adds or replaces 'order by' clause */
|
||||
void replaceOrderBy(String property, boolean asc);
|
||||
void replaceOrderBy(boolean desc, String... properties);
|
||||
|
||||
/** Removes 'order by' clause */
|
||||
void removeOrderBy();
|
||||
|
@ -241,7 +241,7 @@ public class QueryTransformerRegex extends QueryParserRegex implements QueryTran
|
||||
Matcher distinctMatcher = DISTINCT_PATTERN.matcher(buffer);
|
||||
|
||||
buffer.replace(0, entityMatcher.start(),
|
||||
"select "+ (distinctMatcher.find() ? "distinct " : "") + alias + ".id ");
|
||||
"select " + (distinctMatcher.find() ? "distinct " : "") + alias + ".id ");
|
||||
|
||||
Matcher orderMatcher = ORDER_BY_PATTERN.matcher(buffer);
|
||||
if (orderMatcher.find()) {
|
||||
@ -260,34 +260,41 @@ public class QueryTransformerRegex extends QueryParserRegex implements QueryTran
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void replaceOrderBy(String property, boolean desc) {
|
||||
replaceOrderBy(desc, property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceOrderBy(boolean desc, String... properties) {
|
||||
Matcher entityMatcher = FROM_ENTITY_PATTERN.matcher(buffer);
|
||||
String alias = findAlias(entityMatcher);
|
||||
|
||||
int dotPos = property.lastIndexOf(".");
|
||||
if (dotPos > -1) {
|
||||
String path = property.substring(0, dotPos);
|
||||
String joinedAlias = alias + "_" + path.replace(".", "_");
|
||||
if (buffer.indexOf(" " + joinedAlias) == -1) {
|
||||
String join = "left join " + alias + "." + path + " " + joinedAlias;
|
||||
addJoinAsIs(join);
|
||||
}
|
||||
|
||||
String orderBy = joinedAlias + "." + property.substring(dotPos + 1) + (desc ? " desc" : "");
|
||||
Matcher matcher = ORDER_BY_PATTERN.matcher(buffer);
|
||||
if (matcher.find()) {
|
||||
buffer.replace(matcher.end(), buffer.length(), " " + orderBy);
|
||||
} else {
|
||||
buffer.append(" order by ").append(orderBy);
|
||||
}
|
||||
Matcher orderByMatcher = ORDER_BY_PATTERN.matcher(buffer);
|
||||
if (orderByMatcher.find()) {
|
||||
buffer.replace(orderByMatcher.end(), buffer.length(), "");
|
||||
} else {
|
||||
String orderBy = alias + "." + property + (desc ? " desc" : "");
|
||||
Matcher matcher = ORDER_BY_PATTERN.matcher(buffer);
|
||||
if (matcher.find()) {
|
||||
buffer.replace(matcher.end(), buffer.length(), " " + orderBy);
|
||||
buffer.append(" order by");
|
||||
}
|
||||
|
||||
String separator = " ";
|
||||
for (String property : properties) {
|
||||
int dotPos = property.lastIndexOf(".");
|
||||
if (dotPos > -1) {
|
||||
String path = property.substring(0, dotPos);
|
||||
String joinedAlias = alias + "_" + path.replace(".", "_");
|
||||
if (buffer.indexOf(" " + joinedAlias) == -1) {
|
||||
String join = "left join " + alias + "." + path + " " + joinedAlias;
|
||||
addJoinAsIs(join);
|
||||
}
|
||||
|
||||
String orderBy = joinedAlias + "." + property.substring(dotPos + 1) + (desc ? " desc" : "");
|
||||
buffer.append(separator).append(orderBy);
|
||||
} else {
|
||||
buffer.append(" order by ").append(orderBy);
|
||||
String orderBy = alias + "." + property + (desc ? " desc" : "");
|
||||
buffer.append(separator).append(orderBy);
|
||||
}
|
||||
separator = ", ";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,10 +175,17 @@ public class QueryTransformerAstBased implements QueryTransformer {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public void replaceOrderBy(String newOrderingFieldPath, boolean desc) {
|
||||
replaceOrderBy(desc, newOrderingFieldPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceOrderBy(boolean desc, String... properties) {
|
||||
EntityReferenceInferer inferer = new EntityReferenceInferer(entityName);
|
||||
EntityReference ref = inferer.infer(queryAnalyzer);
|
||||
queryAnalyzer.replaceOrderBy(ref.addFieldPath(newOrderingFieldPath), desc);
|
||||
queryAnalyzer.replaceOrderBy(ref.addFieldPath(properties[0]), desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -557,11 +557,36 @@ public abstract class AbstractCollectionDatasource<T extends Entity<K>, K>
|
||||
protected void setSortDirection(LoadContext.Query q) {
|
||||
boolean asc = Sortable.Order.ASC.equals(sortInfos[0].getOrder());
|
||||
MetaPropertyPath propertyPath = sortInfos[0].getPropertyPath();
|
||||
// Sort on DB only if the property is not transient and it is not an entity. Sorting by entity in JPQL
|
||||
// translates to order by entity's id in SQL, that makes no sense.
|
||||
String[] sortProperties = null;
|
||||
|
||||
if (metadata.getTools().isPersistent(propertyPath) && !propertyPath.getMetaProperty().getRange().isClass()) {
|
||||
// a scalar persistent attribute
|
||||
sortProperties = new String[1];
|
||||
sortProperties[0] = propertyPath.toString();
|
||||
} else if (propertyPath.getMetaProperty().getRange().isClass()) {
|
||||
// a reference attribute
|
||||
MetaClass metaClass = propertyPath.getMetaProperty().getRange().asClass();
|
||||
InstanceUtils.NamePatternRec rec = InstanceUtils.parseNamePattern(metaClass);
|
||||
if (rec != null) {
|
||||
sortProperties = new String[rec.fields.length];
|
||||
for (int i = 0; i < rec.fields.length; i++) {
|
||||
sortProperties[i] = propertyPath.toString() + "." + rec.fields[i];
|
||||
}
|
||||
} else {
|
||||
sortProperties = new String[1];
|
||||
sortProperties[0] = propertyPath.toString();
|
||||
}
|
||||
} else if (!metadata.getTools().isPersistent(propertyPath)) {
|
||||
// a non-persistent attribute
|
||||
List<String> list = metadata.getTools().getRelatedProperties(propertyPath.getMetaProperty());
|
||||
if (!list.isEmpty()) {
|
||||
sortProperties = list.toArray(new String[list.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
if (sortProperties != null) {
|
||||
QueryTransformer transformer = QueryTransformerFactory.createTransformer(q.getQueryString(), metaClass.getName());
|
||||
transformer.replaceOrderBy(propertyPath.toString(), !asc);
|
||||
transformer.replaceOrderBy(!asc, sortProperties);
|
||||
String jpqlQuery = transformer.getResult();
|
||||
q.setQueryString(jpqlQuery);
|
||||
}
|
||||
|
@ -725,7 +725,8 @@ public class CollectionPropertyDatasourceImpl<T extends Entity<K>, K>
|
||||
public K firstItemId() {
|
||||
Collection<T> collection = __getCollection();
|
||||
if (collection != null && !collection.isEmpty()) {
|
||||
return Iterables.getFirst(collection, null).getId();
|
||||
T first = Iterables.getFirst(collection, null);
|
||||
return first == null ? null : first.getId();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user