This is an automated email from the ASF dual-hosted git repository.
ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/master by this push:
new efa1255 CAY-2543 Move ResultSetMapping generation from metadata to
translator
efa1255 is described below
commit efa12550f597c5b728ad8bb1d7c8720ed94f1391
Author: Nikita Timofeev <[email protected]>
AuthorDate: Thu Feb 21 14:24:02 2019 +0300
CAY-2543 Move ResultSetMapping generation from metadata to translator
---
RELEASE-NOTES.txt | 1 +
.../cayenne/access/IncrementalFaultList.java | 23 +-
.../access/MixedResultIncrementalFaultList.java | 4 +-
.../select/CustomColumnSetExtractor.java | 6 +-
.../translator/select/DefaultSelectTranslator.java | 1 +
.../select/DescriptorColumnExtractor.java | 45 ++-
.../translator/select/IdColumnExtractor.java | 12 +-
...{IdColumnExtractor.java => SQLResultStage.java} | 28 +-
.../select/TranslatableQueryWrapper.java | 3 +
.../translator/select/TranslatorContext.java | 20 ++
.../org/apache/cayenne/exp/parser/ASTDbPath.java | 11 +
.../org/apache/cayenne/exp/parser/ASTPath.java | 16 +-
.../cayenne/map/DefaultEntityResultSegment.java | 4 +-
.../apache/cayenne/query/BaseQueryMetadata.java | 9 +
.../apache/cayenne/query/ColumnSelectMetadata.java | 336 ++-------------------
.../apache/cayenne/query/ObjectSelectMetadata.java | 28 +-
.../org/apache/cayenne/query/QueryMetadata.java | 6 +
.../apache/cayenne/query/SelectQueryMetadata.java | 189 +-----------
.../select/CustomColumnSetExtractorTest.java | 1 +
.../translator/select/MockQueryWrapperBuilder.java | 11 +
20 files changed, 227 insertions(+), 527 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 8d553b1..22c3cf3 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -23,6 +23,7 @@ CAY-2518 Add method to append having qualifier expression to
ObjectSelect
CAY-2520 Split ObjectId into several specialized variants
CAY-2522 Make ObjectSelect a direct query
CAY-2540 Modeler: redesign dbRelationship editor dialog
+CAY-2543 Move ResultSetMapping generation from metadata to translator
Bug Fixes:
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/access/IncrementalFaultList.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/IncrementalFaultList.java
index 1c83d3c..f2cb27b 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/access/IncrementalFaultList.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/access/IncrementalFaultList.java
@@ -72,6 +72,7 @@ public class IncrementalFaultList<E> implements List<E>,
Serializable {
protected int idWidth;
IncrementalListHelper helper;
+ protected QueryMetadata metadata;
/**
* Defines the upper limit on the size of fetches. This is needed to
avoid
@@ -102,7 +103,7 @@ public class IncrementalFaultList<E> implements List<E>,
Serializable {
* maximum number of fetches in one query
*/
public IncrementalFaultList(DataContext dataContext, Query query, int
maxFetchSize) {
- QueryMetadata metadata =
query.getMetaData(dataContext.getEntityResolver());
+ this.metadata =
query.getMetaData(dataContext.getEntityResolver());
if (metadata.getPageSize() <= 0) {
throw new CayenneRuntimeException("Not a paginated
query; page size: " + metadata.getPageSize());
}
@@ -122,7 +123,6 @@ public class IncrementalFaultList<E> implements List<E>,
Serializable {
this.internalQuery.setFetchingDataRows(metadata.isFetchingDataRows());
this.internalQuery.setPrefetchTree(metadata.getPrefetchTree());
- this.helper = createHelper(metadata);
this.idWidth = metadata.getDbEntity().getPrimaryKeys().size();
List<Object> elementsUnsynced = new ArrayList<>();
@@ -143,6 +143,13 @@ public class IncrementalFaultList<E> implements List<E>,
Serializable {
}
}
+ IncrementalListHelper getHelper() {
+ if(helper == null) {
+ helper = createHelper(metadata);
+ }
+ return helper;
+ }
+
/**
* @since 1.2
*/
@@ -223,7 +230,7 @@ public class IncrementalFaultList<E> implements List<E>,
Serializable {
List<Object> ids = new ArrayList<>(pageSize);
for (int i = fromIndex; i < toIndex; i++) {
Object object = elements.get(i);
- if (helper.unresolvedSuspect(object)) {
+ if (getHelper().unresolvedSuspect(object)) {
quals.add(buildIdQualifier(object));
ids.add(object);
}
@@ -258,7 +265,7 @@ public class IncrementalFaultList<E> implements List<E>,
Serializable {
void updatePageWithResults(List<Object> objects, int fromIndex, int
toIndex) {
for (Object object : objects) {
- helper.updateWithResolvedObjectInRange(object,
fromIndex, toIndex);
+ getHelper().updateWithResolvedObjectInRange(object,
fromIndex, toIndex);
}
unfetchedObjects -= objects.size();
@@ -307,7 +314,7 @@ public class IncrementalFaultList<E> implements List<E>,
Serializable {
for (Object object : objects) {
- if (helper.replacesObject(object, id)) {
+ if (getHelper().replacesObject(object,
id)) {
found = true;
break;
}
@@ -506,7 +513,7 @@ public class IncrementalFaultList<E> implements List<E>,
Serializable {
synchronized (elements) {
Object o = elements.get(index);
- if (helper.unresolvedSuspect(o)) {
+ if (getHelper().unresolvedSuspect(o)) {
// read this page
int pageStart = pageIndex(index) * pageSize;
resolveInterval(pageStart, pageStart +
pageSize);
@@ -522,7 +529,7 @@ public class IncrementalFaultList<E> implements List<E>,
Serializable {
* @see java.util.List#indexOf(Object)
*/
public int indexOf(Object o) {
- return helper.indexOfObject(o);
+ return getHelper().indexOfObject(o);
}
/**
@@ -535,7 +542,7 @@ public class IncrementalFaultList<E> implements List<E>,
Serializable {
}
public int lastIndexOf(Object o) {
- return helper.lastIndexOfObject(o);
+ return getHelper().lastIndexOfObject(o);
}
public E remove(int index) {
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
index f8c02b8..65ae966 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
@@ -140,7 +140,7 @@ class MixedResultIncrementalFaultList<E> extends
IncrementalFaultList<E> {
int dataIdx = entry.getKey();
for (int i = fromIndex; i < toIndex; i++) {
Object[] object = (Object[])elements.get(i);
- if (helper.unresolvedSuspect(object[dataIdx])) {
+ if (getHelper().unresolvedSuspect(object[dataIdx])) {
Expression nextQualifier = buildIdQualifier(dataIdx,
object);
if(nextQualifier != null) {
quals.add(nextQualifier);
@@ -173,7 +173,7 @@ class MixedResultIncrementalFaultList<E> extends
IncrementalFaultList<E> {
}
void updatePageWithResults(List<Persistent> objects, int dataIndex) {
- MixedArrayListHelper helper = (MixedArrayListHelper)this.helper;
+ MixedArrayListHelper helper = (MixedArrayListHelper)getHelper();
for (Persistent object : objects) {
helper.updateWithResolvedObject(object, dataIndex);
}
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
index 534fd3a..ed2c06f 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
@@ -60,6 +60,8 @@ class CustomColumnSetExtractor implements ColumnExtractor {
private void extractSimpleProperty(BaseProperty<?> property) {
Node sqlNode = context.getQualifierTranslator().translate(property);
context.addResultNode(sqlNode, true, property, property.getAlias());
+ String name = property.getName() == null ?
property.getExpression().expName() : property.getName();
+ context.getSqlResult().addColumnResult(name);
}
private boolean isFullObjectProp(BaseProperty<?> property) {
@@ -108,14 +110,14 @@ class CustomColumnSetExtractor implements ColumnExtractor
{
private String calculatePrefix(String prefix, BaseProperty<?> property) {
Expression propertyExpression = property.getExpression();
- int expressionType = property.getExpression().getType();
+ int expressionType = propertyExpression.getType();
if(expressionType == Expression.FULL_OBJECT &&
propertyExpression.getOperandCount() > 0) {
Object op = propertyExpression.getOperand(0);
if(op instanceof ASTPath) {
prefix = ((ASTPath) op).getPath();
}
- } else if(propertyExpression instanceof ASTPath) {
+ } else if(expressionType == Expression.DB_PATH || expressionType ==
Expression.OBJ_PATH) {
prefix = ((ASTPath) propertyExpression).getPath();
}
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
index 7cc62bc..1213c38 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
@@ -49,6 +49,7 @@ public class DefaultSelectTranslator implements
SelectTranslator {
new LimitOffsetStage(),
new ColumnDescriptorStage(),
new TableTreeStage(),
+ new SQLResultStage(),
new SQLGenerationStage()
};
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractor.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractor.java
index 95a26ea..93da03b 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractor.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractor.java
@@ -22,9 +22,11 @@ package org.apache.cayenne.access.translator.select;
import java.util.HashSet;
import java.util.Set;
+import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResult;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjRelationship;
import org.apache.cayenne.reflect.AttributeProperty;
@@ -44,9 +46,11 @@ class DescriptorColumnExtractor extends BaseColumnExtractor
implements PropertyV
private final ClassDescriptor descriptor;
private final PathTranslator pathTranslator;
+ private final Set<String> columnTracker = new HashSet<>();
+ private EntityResult entityResult;
private String prefix;
- private Set<String> columnTracker = new HashSet<>();
+ private String labelPrefix;
DescriptorColumnExtractor(TranslatorContext context, ClassDescriptor
descriptor) {
super(context);
@@ -56,14 +60,31 @@ class DescriptorColumnExtractor extends BaseColumnExtractor
implements PropertyV
public void extract(String prefix) {
this.prefix = prefix;
- String labelPrefix = prefix;
+ boolean newEntityResult = false;
+ this.labelPrefix = null;
TranslatorContext.DescriptorType type =
TranslatorContext.DescriptorType.OTHER;
- if(prefix != null && prefix.length() > 2 &&
prefix.startsWith(PREFETCH_PREFIX)) {
+
+ if(prefix != null && prefix.startsWith(PREFETCH_PREFIX)) {
type = TranslatorContext.DescriptorType.PREFETCH;
labelPrefix = prefix.substring(2);
- } else if(descriptor.getEntity().getDbEntity() ==
context.getRootDbEntity()) {
- type = TranslatorContext.DescriptorType.ROOT;
+ if(context.getQuery().needsResultSetMapping()) {
+ entityResult = context.getRootEntityResult();
+ if (entityResult == null) {
+ throw new CayenneRuntimeException("Can't process prefetch
descriptor without root.");
+ }
+ newEntityResult = false;
+ }
+ } else {
+ if(context.getQuery().needsResultSetMapping()) {
+ entityResult = new EntityResult(descriptor.getObjectClass());
+ newEntityResult = true;
+ }
+ if(descriptor.getEntity().getDbEntity() ==
context.getRootDbEntity()){
+ type = TranslatorContext.DescriptorType.ROOT;
+ context.setRootEntityResult(entityResult);
+ }
}
+
context.markDescriptorStart(type);
descriptor.visitAllProperties(this);
@@ -75,8 +96,13 @@ class DescriptorColumnExtractor extends BaseColumnExtractor
implements PropertyV
String columnUniqueName = alias + '.' + dba.getName();
if(columnTracker.add(columnUniqueName)) {
addDbAttribute(prefix, labelPrefix, dba);
+ addEntityResultField(dba);
}
}
+
+ if(newEntityResult) {
+ context.getSqlResult().addEntityResult(entityResult);
+ }
context.markDescriptorEnd(type);
}
@@ -90,6 +116,7 @@ class DescriptorColumnExtractor extends BaseColumnExtractor
implements PropertyV
ResultNodeDescriptor resultNodeDescriptor =
processTranslationResult(result, i);
if(resultNodeDescriptor != null && i == count - 1) {
resultNodeDescriptor.setJavaType(oa.getType());
+ addEntityResultField(oa.getDbAttribute());
}
}
@@ -109,6 +136,7 @@ class DescriptorColumnExtractor extends BaseColumnExtractor
implements PropertyV
int count = result.getDbAttributes().size();
for(int i=0; i<count; i++) {
processTranslationResult(result, i);
+ addEntityResultField(result.getDbAttributes().get(i));
}
return true;
@@ -138,6 +166,13 @@ class DescriptorColumnExtractor extends
BaseColumnExtractor implements PropertyV
return null;
}
+ private void addEntityResultField(DbAttribute attribute) {
+ String name = labelPrefix == null ? attribute.getName() : labelPrefix
+ '.' + attribute.getName();
+ if(context.getQuery().needsResultSetMapping()) {
+ entityResult.addDbField(name, name);
+ }
+ }
+
@Override
public boolean visitToMany(ToManyProperty property) {
return true;
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/IdColumnExtractor.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/IdColumnExtractor.java
index a718a04..46feccd 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/IdColumnExtractor.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/IdColumnExtractor.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.access.translator.select;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResult;
import org.apache.cayenne.map.ObjEntity;
/**
@@ -29,6 +30,7 @@ import org.apache.cayenne.map.ObjEntity;
class IdColumnExtractor extends BaseColumnExtractor {
private final DbEntity dbEntity;
+ private EntityResult result;
IdColumnExtractor(TranslatorContext context, DbEntity dbEntity) {
super(context);
@@ -37,13 +39,21 @@ class IdColumnExtractor extends BaseColumnExtractor {
IdColumnExtractor(TranslatorContext context, ObjEntity objEntity) {
this(context, objEntity.getDbEntity());
+ if(context.getQuery().needsResultSetMapping()) {
+ this.result = new EntityResult(objEntity.getName());
+ }
}
@Override
public void extract(String prefix) {
for (DbAttribute dba : dbEntity.getPrimaryKeys()) {
addDbAttribute(prefix, prefix, dba);
+ if(result != null) {
+ result.addDbField(dba.getName(), prefix + dba.getName());
+ }
+ }
+ if(result != null) {
+ context.getSqlResult().addEntityResult(result);
}
}
-
}
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/IdColumnExtractor.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SQLResultStage.java
similarity index 62%
copy from
cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/IdColumnExtractor.java
copy to
cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SQLResultStage.java
index a718a04..b4656d0 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/IdColumnExtractor.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SQLResultStage.java
@@ -19,31 +19,21 @@
package org.apache.cayenne.access.translator.select;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjEntity;
+import java.util.List;
/**
* @since 4.2
*/
-class IdColumnExtractor extends BaseColumnExtractor {
-
- private final DbEntity dbEntity;
-
- IdColumnExtractor(TranslatorContext context, DbEntity dbEntity) {
- super(context);
- this.dbEntity = dbEntity;
- }
-
- IdColumnExtractor(TranslatorContext context, ObjEntity objEntity) {
- this(context, objEntity.getDbEntity());
- }
+public class SQLResultStage implements TranslationStage {
@Override
- public void extract(String prefix) {
- for (DbAttribute dba : dbEntity.getPrimaryKeys()) {
- addDbAttribute(prefix, prefix, dba);
+ public void perform(TranslatorContext context) {
+ if(context.getParentContext() != null ||
!context.getQuery().needsResultSetMapping()) {
+ return;
}
- }
+ // optimization, resolve metadata result components here too, as it
have same logic as this extractor...
+ List<Object> resultSetMapping =
context.getSqlResult().getResolvedComponents(context.getResolver());
+ context.getMetadata().setResultSetMapping(resultSetMapping);
+ }
}
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatableQueryWrapper.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatableQueryWrapper.java
index 5322847..d04031c 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatableQueryWrapper.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatableQueryWrapper.java
@@ -52,4 +52,7 @@ public interface TranslatableQueryWrapper {
Select<?> unwrap();
+ default boolean needsResultSetMapping() {
+ return getColumns() != null && !getColumns().isEmpty();
+ }
}
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
index ba03b72..bf89ceb 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
@@ -35,6 +35,8 @@ import org.apache.cayenne.dba.QuotingStrategy;
import org.apache.cayenne.exp.property.BaseProperty;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.EntityResult;
+import org.apache.cayenne.map.SQLResult;
import org.apache.cayenne.query.QueryMetadata;
/**
@@ -99,6 +101,9 @@ public class TranslatorContext implements
SQLGenerationContext {
// this flag can be removed if logic that converts result row into an
object tree allow random order of columns if a row.
private boolean appendResultToRoot;
+ private SQLResult sqlResult;
+ private EntityResult rootEntityResult;
+
TranslatorContext(TranslatableQueryWrapper query, DbAdapter adapter,
EntityResolver resolver, TranslatorContext parentContext) {
this.query = query;
this.adapter = adapter;
@@ -113,6 +118,9 @@ public class TranslatorContext implements
SQLGenerationContext {
this.qualifierTranslator = new QualifierTranslator(this);
this.quotingStrategy = adapter.getQuotingStrategy();
this.resultNodeList = new LinkedList<>();
+ if(query.needsResultSetMapping()) {
+ this.sqlResult = new SQLResult();
+ }
}
/**
@@ -236,6 +244,18 @@ public class TranslatorContext implements
SQLGenerationContext {
return skipSQLGeneration;
}
+ SQLResult getSqlResult() {
+ return sqlResult;
+ }
+
+ void setRootEntityResult(EntityResult rootEntityResult) {
+ this.rootEntityResult = rootEntityResult;
+ }
+
+ EntityResult getRootEntityResult() {
+ return rootEntityResult;
+ }
+
enum DescriptorType {
ROOT,
PREFETCH,
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDbPath.java
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDbPath.java
index 6503bf2..48484aa 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDbPath.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDbPath.java
@@ -35,6 +35,7 @@ import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.DbRelationship;
import org.apache.cayenne.map.Entity;
+import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.query.ObjectSelect;
import org.apache.cayenne.query.SelectById;
import org.apache.cayenne.util.CayenneMapEntry;
@@ -194,4 +195,14 @@ public class ASTDbPath extends ASTPath {
public int getType() {
return Expression.DB_PATH;
}
+
+ /**
+ * Helper method to evaluate path expression with Cayenne Entity.
+ */
+ protected CayenneMapEntry evaluateEntityNode(Entity entity) {
+ if(entity instanceof ObjEntity) {
+ entity = ((ObjEntity) entity).getDbEntity();
+ }
+ return super.evaluateEntityNode(entity);
+ }
}
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTPath.java
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTPath.java
index 8ab1a39..bae0533 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTPath.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTPath.java
@@ -22,7 +22,10 @@ package org.apache.cayenne.exp.parser;
import java.util.Iterator;
import java.util.Map;
+import org.apache.cayenne.map.Attribute;
import org.apache.cayenne.map.Entity;
+import org.apache.cayenne.map.PathComponent;
+import org.apache.cayenne.map.Relationship;
import org.apache.cayenne.util.CayenneMapEntry;
/**
@@ -91,13 +94,20 @@ public abstract class ASTPath extends SimpleNode {
* Helper method to evaluate path expression with Cayenne Entity.
*/
protected CayenneMapEntry evaluateEntityNode(Entity entity) {
- Iterator<CayenneMapEntry> path =
entity.resolvePathComponents(this);
- CayenneMapEntry next = null;
+ Iterator<PathComponent<Attribute, Relationship>> path =
entity.resolvePath(this, getPathAliases()).iterator();
+ PathComponent<Attribute, Relationship> next = null;
while (path.hasNext()) {
next = path.next();
}
- return next;
+ if(next == null) {
+ return null;
+ }
+
+ if(next.getRelationship() != null) {
+ return next.getRelationship();
+ }
+ return next.getAttribute();
}
@Override
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/map/DefaultEntityResultSegment.java
b/cayenne-server/src/main/java/org/apache/cayenne/map/DefaultEntityResultSegment.java
index a9bb0f0..49fbfba 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/map/DefaultEntityResultSegment.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/map/DefaultEntityResultSegment.java
@@ -26,13 +26,13 @@ import org.apache.cayenne.reflect.ClassDescriptor;
/**
* @since 3.0
*/
-class DefaultEntityResultSegment implements EntityResultSegment {
+public class DefaultEntityResultSegment implements EntityResultSegment {
private ClassDescriptor classDescriptor;
private Map<String, String> fields;
private int offset;
- DefaultEntityResultSegment(ClassDescriptor classDescriptor,
+ public DefaultEntityResultSegment(ClassDescriptor classDescriptor,
Map<String, String> fields, int offset) {
this.classDescriptor = classDescriptor;
this.fields = fields;
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
b/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
index e1cd167..40c24bc 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
@@ -251,6 +251,15 @@ class BaseQueryMetadata implements QueryMetadata,
Serializable {
}
/**
+ * used by select translator
+ * @since 4.2
+ */
+ @Override
+ public void setResultSetMapping(List<Object> resultSetMapping) {
+ this.resultSetMapping = resultSetMapping;
+ }
+
+ /**
* @since 4.0
*/
@Override
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelectMetadata.java
b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelectMetadata.java
index eb9a389..65f113c 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelectMetadata.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelectMetadata.java
@@ -18,56 +18,33 @@
****************************************************************/
package org.apache.cayenne.query;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
import java.util.Map;
-import java.util.Set;
-import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.exp.TraversalHandler;
-import org.apache.cayenne.exp.parser.ASTDbPath;
import org.apache.cayenne.exp.property.BaseProperty;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.DefaultEntityResultSegment;
+import org.apache.cayenne.map.DefaultScalarResultSegment;
import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.EntityResult;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
-import org.apache.cayenne.map.PathComponent;
-import org.apache.cayenne.map.SQLResult;
-import org.apache.cayenne.reflect.AttributeProperty;
-import org.apache.cayenne.reflect.ClassDescriptor;
-import org.apache.cayenne.reflect.PropertyVisitor;
-import org.apache.cayenne.reflect.ToManyProperty;
-import org.apache.cayenne.reflect.ToOneProperty;
-import org.apache.cayenne.util.CayenneMapEntry;
/**
* @since 4.2
*/
-class ColumnSelectMetadata extends BaseQueryMetadata {
+class ColumnSelectMetadata extends ObjectSelectMetadata {
private static final long serialVersionUID = -3622675304651257963L;
- private Map<String, String> pathSplitAliases;
+ private static final ScalarResultSegment SCALAR_RESULT_SEGMENT
+ = new DefaultScalarResultSegment(null, -1);
+ private static final EntityResultSegment ENTITY_RESULT_SEGMENT
+ = new DefaultEntityResultSegment(null, null, -1);
+
private boolean isSingleResultSetMapping;
private boolean suppressingDistinct;
- @Override
- void copyFromInfo(QueryMetadata info) {
- super.copyFromInfo(info);
- this.pathSplitAliases = new
HashMap<>(info.getPathSplitAliases());
- }
-
boolean resolve(Object root, EntityResolver resolver, ColumnSelect<?>
query) {
if (super.resolve(root, resolver)) {
@@ -77,93 +54,21 @@ class ColumnSelectMetadata extends BaseQueryMetadata {
}
resolveAutoAliases(query);
- buildResultSetMappingForColumns(query, resolver);
- isSingleResultSetMapping = query.isSingleColumn() &&
super.isSingleResultSetMapping();
-
+ buildResultSetMappingForColumns(query);
+ isSingleResultSetMapping = query.isSingleColumn();
return true;
}
return false;
}
- private String makeCacheKey(ColumnSelect<?> query, EntityResolver
resolver) {
-
- // create a unique key based on entity or columns, qualifier,
ordering, fetch offset and limit
-
- StringBuilder key = new StringBuilder();
- // handler to create string out of expressions, created lazily
- TraversalHandler traversalHandler = null;
-
- ObjEntity entity = getObjEntity();
- if (entity != null) {
- key.append(entity.getName());
- } else if (dbEntity != null) {
- key.append("db:").append(dbEntity.getName());
- }
-
- if(query.getColumns() != null && !query.getColumns().isEmpty())
{
- traversalHandler = new
ToCacheKeyTraversalHandler(resolver.getValueObjectTypeRegistry(), key);
- for(BaseProperty<?> property : query.getColumns()) {
- key.append("/c:");
-
property.getExpression().traverse(traversalHandler);
- }
- }
-
- if (query.getWhere() != null) {
- key.append('/');
- if(traversalHandler == null) {
- traversalHandler = new
ToCacheKeyTraversalHandler(resolver.getValueObjectTypeRegistry(), key);
- }
- query.getWhere().traverse(traversalHandler);
- }
-
- if (query.getOrderings() != null &&
!query.getOrderings().isEmpty()) {
- for (Ordering o : query.getOrderings()) {
- key.append('/').append(o.getSortSpecString());
- if (!o.isAscending()) {
- key.append(":d");
- }
-
- if (o.isCaseInsensitive()) {
- key.append(":i");
- }
- }
- }
-
- if (fetchOffset > 0 || fetchLimit > 0) {
- key.append('/');
- if (fetchOffset > 0) {
- key.append('o').append(fetchOffset);
- }
- if (fetchLimit > 0) {
- key.append('l').append(fetchLimit);
- }
- }
-
- // add prefetch to cache key per CAY-2349
- if(prefetchTree != null) {
- prefetchTree.traverse(new
ToCacheKeyPrefetchProcessor(key));
- }
-
- return key.toString();
- }
-
- private void resolveAutoAliases(ColumnSelect<?> query) {
- resolveQualifierAliases(query);
+ @Override
+ protected void resolveAutoAliases(FluentSelect<?> query) {
+ super.resolveAutoAliases(query);
resolveColumnsAliases(query);
- resolveOrderingAliases(query);
- resolveHavingQualifierAliases(query);
- // TODO: include aliases in prefetches? flattened attributes?
- }
-
- private void resolveQualifierAliases(ColumnSelect<?> query) {
- Expression qualifier = query.getWhere();
- if (qualifier != null) {
- resolveAutoAliases(qualifier);
- }
}
- private void resolveColumnsAliases(ColumnSelect<?> query) {
+ protected void resolveColumnsAliases(FluentSelect<?> query) {
Collection<BaseProperty<?>> columns = query.getColumns();
if(columns != null) {
for(BaseProperty<?> property : columns) {
@@ -175,225 +80,44 @@ class ColumnSelectMetadata extends BaseQueryMetadata {
}
}
- private void resolveOrderingAliases(ColumnSelect<?> query) {
- Collection<Ordering> orderings = query.getOrderings();
- if(orderings != null) {
- for(Ordering ordering : orderings) {
- Expression sortSpec = ordering.getSortSpec();
- if(sortSpec != null) {
- resolveAutoAliases(sortSpec);
- }
- }
- }
- }
-
- private void resolveHavingQualifierAliases(ColumnSelect<?> query) {
- Expression havingQualifier = query.getHaving();
- if(havingQualifier != null) {
- resolveAutoAliases(havingQualifier);
- }
- }
-
- private void resolveAutoAliases(Expression expression) {
- Map<String, String> aliases = expression.getPathAliases();
- if (!aliases.isEmpty()) {
- if (pathSplitAliases == null) {
- pathSplitAliases = new HashMap<>();
- }
-
- for(Map.Entry<String, String> entry :
aliases.entrySet()) {
- pathSplitAliases.compute(entry.getKey(), (key,
value) -> {
- if(value != null &&
!value.equals(entry.getValue())){
- throw new
CayenneRuntimeException("Can't add the same alias to different path segments.");
- } else {
- return entry.getValue();
- }
- });
- }
- }
-
- int len = expression.getOperandCount();
- for (int i = 0; i < len; i++) {
- Object operand = expression.getOperand(i);
- if (operand instanceof Expression) {
- resolveAutoAliases((Expression) operand);
- }
- }
- }
-
@Override
public Map<String, String> getPathSplitAliases() {
return pathSplitAliases != null ? pathSplitAliases :
Collections.emptyMap();
}
/**
- * Build DB result descriptor, that will be used to read and convert
raw result of ColumnSelect
+ * NOTE: this is a dirty logic, we calculate hollow resultSetMapping
here and later in translator
+ * (see ColumnExtractorStage and extractors) discard this and calculate
it with full info.
+ *
+ * This result set mapping required by paginated queries that need only
result type (entity/scalar) not
+ * full info. So we can optimize this a bit and pair calculation with
translation that do same thing to provide
+ * result column descriptors.
*/
- private void buildResultSetMappingForColumns(ColumnSelect<?> query,
EntityResolver resolver) {
+ private void buildResultSetMappingForColumns(ColumnSelect<?> query) {
if(query.getColumns() == null || query.getColumns().isEmpty()) {
return;
}
-
- SQLResult result = new SQLResult();
+
+ resultSetMapping = new ArrayList<>(query.getColumns().size());
for(BaseProperty<?> column : query.getColumns()) {
+ // for each column we need only to know if it's entity
or scalar
Expression exp = column.getExpression();
- String name = column.getName() == null ? exp.expName()
: column.getName();
boolean fullObject = false;
if(exp.getType() == Expression.OBJ_PATH) {
// check if this is toOne relation
- Expression dbPath =
this.getObjEntity().translateToDbPath(exp);
- DbRelationship rel =
findRelationByPath(dbEntity, dbPath);
- if(rel != null && !rel.isToMany()) {
- // it this path is toOne relation, than
select full object for it
- fullObject = true;
- }
+ Object rel = exp.evaluate(getObjEntity());
+ // it this path is toOne relation, than select
full object for it
+ fullObject = rel instanceof ObjRelationship &&
!((ObjRelationship) rel).isToMany();
} else if(exp.getType() == Expression.FULL_OBJECT) {
fullObject = true;
}
if(fullObject) {
- // detected full object column
- if(getPageSize() > 0) {
- // for paginated queries keep only IDs
-
result.addEntityResult(buildEntityIdResultForColumn(column, resolver));
- } else {
- // will unwrap to full set of
db-columns (with join prefetch optionally)
-
result.addEntityResult(buildEntityResultForColumn(query, column, resolver));
- }
+ resultSetMapping.add(ENTITY_RESULT_SEGMENT);
} else {
- // scalar column
- result.addColumnResult(name);
+ resultSetMapping.add(SCALAR_RESULT_SEGMENT);
}
}
- resultSetMapping = result.getResolvedComponents(resolver);
- }
-
- /**
- * Collect metadata for result with ObjectId (used for paginated
queries with FullObject columns)
- *
- * @param column full object column
- * @param resolver entity resolver
- * @return Entity result
- */
- private EntityResult buildEntityIdResultForColumn(BaseProperty<?>
column, EntityResolver resolver) {
- EntityResult result = new EntityResult(column.getType());
- DbEntity entity =
resolver.getObjEntity(column.getType()).getDbEntity();
- for(DbAttribute attribute : entity.getPrimaryKeys()) {
- result.addDbField(attribute.getName(),
attribute.getName());
- }
- return result;
- }
-
- private DbRelationship findRelationByPath(DbEntity entity, Expression
exp) {
- DbRelationship r = null;
- for (PathComponent<DbAttribute, DbRelationship> component :
entity.resolvePath(exp, getPathSplitAliases())) {
- r = component.getRelationship();
- }
- return r;
- }
-
- /**
- * Collect metadata for column that will be unwrapped to full entity in
the final SQL
- * (possibly including joint prefetch).
- * This information will be used to correctly create Persistent object
back from raw result.
- *
- * @param query original query
- * @param column full object column
- * @param resolver entity resolver to get ObjEntity and ClassDescriptor
- * @return Entity result
- */
- private EntityResult buildEntityResultForColumn(ColumnSelect<?> query,
BaseProperty<?> column, EntityResolver resolver) {
- // This method is actually repeating logic of
DescriptorColumnExtractor.
- // Here we don't care about intermediate joins and few other
things so it's shorter.
- // Logic of these methods should be unified and simplified,
possibly to a single source of metadata,
- // generated only once and used everywhere.
-
- final EntityResult result = new EntityResult(column.getType());
-
- // Collecting visitor for ObjAttributes and toOne relationships
- PropertyVisitor visitor = new PropertyVisitor() {
- public boolean visitAttribute(AttributeProperty
property) {
- ObjAttribute oa = property.getAttribute();
- Iterator<CayenneMapEntry> dbPathIterator =
oa.getDbPathIterator();
- while (dbPathIterator.hasNext()) {
- CayenneMapEntry pathPart =
dbPathIterator.next();
- if (pathPart instanceof DbAttribute) {
-
result.addDbField(pathPart.getName(), pathPart.getName());
- }
- }
- return true;
- }
-
- public boolean visitToMany(ToManyProperty property) {
- return true;
- }
-
- public boolean visitToOne(ToOneProperty property) {
- DbRelationship dbRel =
property.getRelationship().getDbRelationships().get(0);
- List<DbJoin> joins = dbRel.getJoins();
- for (DbJoin join : joins) {
- if(!join.getSource().isPrimaryKey()) {
-
result.addDbField(join.getSource().getName(), join.getSource().getName());
- }
- }
- return true;
- }
- };
-
- ObjEntity oe = resolver.getObjEntity(column.getType());
- DbEntity table = oe.getDbEntity();
-
- // Additionally collect PKs
- for (DbAttribute dba : table.getPrimaryKeys()) {
- result.addDbField(dba.getName(), dba.getName());
- }
-
- ClassDescriptor descriptor =
resolver.getClassDescriptor(oe.getName());
- descriptor.visitAllProperties(visitor);
-
- // Collection columns for joint prefetch
- if(prefetchTree != null) {
- for (PrefetchTreeNode prefetch :
prefetchTree.adjacentJointNodes()) {
- // for each prefetch add columns from the
target entity
- Expression prefetchExp =
ExpressionFactory.exp(prefetch.getPath());
- ASTDbPath dbPrefetch = (ASTDbPath)
oe.translateToDbPath(prefetchExp);
- DbRelationship r = findRelationByPath(table,
dbPrefetch);
- if (r == null) {
- throw new
CayenneRuntimeException("Invalid joint prefetch '%s' for entity: %s"
- , prefetch,
oe.getName());
- }
-
- // go via target OE to make sure that Java
types are mapped correctly...
- ObjRelationship targetRel = (ObjRelationship)
prefetchExp.evaluate(oe);
- ObjEntity targetEntity =
targetRel.getTargetEntity();
-
prefetch.setEntityName(targetRel.getSourceEntity().getName());
-
- String labelPrefix = dbPrefetch.getPath();
- Set<String> seenNames = new HashSet<>();
- for (ObjAttribute oa :
targetEntity.getAttributes()) {
- Iterator<CayenneMapEntry>
dbPathIterator = oa.getDbPathIterator();
- while (dbPathIterator.hasNext()) {
- Object pathPart =
dbPathIterator.next();
- if (pathPart instanceof
DbAttribute) {
- DbAttribute attribute =
(DbAttribute) pathPart;
-
if(seenNames.add(attribute.getName())) {
-
result.addDbField(labelPrefix + '.' + attribute.getName(), labelPrefix + '.' +
attribute.getName());
- }
- }
- }
- }
-
- // append remaining target attributes such as
keys
- DbEntity targetDbEntity = r.getTargetEntity();
- for (DbAttribute attribute :
targetDbEntity.getAttributes()) {
- if(seenNames.add(attribute.getName())) {
- result.addDbField(labelPrefix +
'.' + attribute.getName(), labelPrefix + '.' + attribute.getName());
- }
- }
- }
- }
-
- return result;
}
@Override
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelectMetadata.java
b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelectMetadata.java
index b23a4af..51a0576 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelectMetadata.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelectMetadata.java
@@ -26,6 +26,7 @@ import java.util.Map;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.TraversalHandler;
+import org.apache.cayenne.exp.property.BaseProperty;
import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.map.ObjEntity;
@@ -36,7 +37,7 @@ class ObjectSelectMetadata extends BaseQueryMetadata {
private static final long serialVersionUID = -4936484509363047672L;
- private Map<String, String> pathSplitAliases;
+ protected Map<String, String> pathSplitAliases;
@Override
void copyFromInfo(QueryMetadata info) {
@@ -59,7 +60,7 @@ class ObjectSelectMetadata extends BaseQueryMetadata {
return false;
}
- private String makeCacheKey(FluentSelect<?> query, EntityResolver
resolver) {
+ protected String makeCacheKey(FluentSelect<?> query, EntityResolver
resolver) {
// create a unique key based on entity or columns, qualifier,
ordering, fetch offset and limit
@@ -74,6 +75,14 @@ class ObjectSelectMetadata extends BaseQueryMetadata {
key.append("db:").append(dbEntity.getName());
}
+ if (query.getColumns() != null &&
!query.getColumns().isEmpty()) {
+ traversalHandler = new
ToCacheKeyTraversalHandler(resolver.getValueObjectTypeRegistry(), key);
+ for (BaseProperty<?> property : query.getColumns()) {
+ key.append("/c:");
+
property.getExpression().traverse(traversalHandler);
+ }
+ }
+
if (query.getWhere() != null) {
key.append('/');
traversalHandler = new
ToCacheKeyTraversalHandler(resolver.getValueObjectTypeRegistry(), key);
@@ -111,20 +120,27 @@ class ObjectSelectMetadata extends BaseQueryMetadata {
return key.toString();
}
- private void resolveAutoAliases(ObjectSelect<?> query) {
+ protected void resolveAutoAliases(FluentSelect<?> query) {
resolveQualifierAliases(query);
resolveOrderingAliases(query);
+ resolveHavingQualifierAliases(query);
}
- private void resolveQualifierAliases(ObjectSelect<?> query) {
+ protected void resolveQualifierAliases(FluentSelect<?> query) {
Expression qualifier = query.getWhere();
if (qualifier != null) {
resolveAutoAliases(qualifier);
}
}
+ protected void resolveHavingQualifierAliases(FluentSelect<?> query) {
+ Expression havingQualifier = query.getHaving();
+ if(havingQualifier != null) {
+ resolveAutoAliases(havingQualifier);
+ }
+ }
- private void resolveOrderingAliases(ObjectSelect<?> query) {
+ protected void resolveOrderingAliases(FluentSelect<?> query) {
Collection<Ordering> orderings = query.getOrderings();
if(orderings != null) {
for(Ordering ordering : orderings) {
@@ -136,7 +152,7 @@ class ObjectSelectMetadata extends BaseQueryMetadata {
}
}
- private void resolveAutoAliases(Expression expression) {
+ protected void resolveAutoAliases(Expression expression) {
Map<String, String> aliases = expression.getPathAliases();
if (!aliases.isEmpty()) {
if (pathSplitAliases == null) {
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java
b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java
index edb1118..603e8c6 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java
@@ -226,6 +226,12 @@ public interface QueryMetadata {
List<Object> getResultSetMapping();
/**
+ * @since 4.2
+ */
+ default void setResultSetMapping(List<Object> resultSetMapping) {
+ }
+
+ /**
* @return should the result be mapped to single object (scalar or entity)
* @see QueryMetadata#getResultSetMapping()
* @since 4.0
diff --git
a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
index 2dc74a9..1624eae 100644
---
a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
+++
b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
@@ -18,44 +18,22 @@
****************************************************************/
package org.apache.cayenne.query;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.ObjectId;
-import org.apache.cayenne.Persistent;
-import org.apache.cayenne.access.types.ValueObjectType;
-import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.TraversalHandler;
-import org.apache.cayenne.exp.parser.ASTDbPath;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.exp.parser.ASTScalar;
import org.apache.cayenne.exp.property.BaseProperty;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.DefaultEntityResultSegment;
+import org.apache.cayenne.map.DefaultScalarResultSegment;
import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.EntityResult;
-import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
-import org.apache.cayenne.map.PathComponent;
-import org.apache.cayenne.map.SQLResult;
-import org.apache.cayenne.reflect.AttributeProperty;
-import org.apache.cayenne.reflect.ClassDescriptor;
-import org.apache.cayenne.reflect.PropertyVisitor;
-import org.apache.cayenne.reflect.ToManyProperty;
-import org.apache.cayenne.reflect.ToOneProperty;
-import org.apache.cayenne.util.CayenneMapEntry;
/**
* @since 3.0
@@ -63,6 +41,11 @@ import org.apache.cayenne.util.CayenneMapEntry;
class SelectQueryMetadata extends BaseQueryMetadata {
private static final long serialVersionUID = 7465922769303943945L;
+
+ private static final ScalarResultSegment SCALAR_RESULT_SEGMENT
+ = new DefaultScalarResultSegment(null, -1);
+ private static final EntityResultSegment ENTITY_RESULT_SEGMENT
+ = new DefaultEntityResultSegment(null, null, -1);
private Map<String, String> pathSplitAliases;
private boolean isSingleResultSetMapping;
@@ -264,167 +247,27 @@ class SelectQueryMetadata extends BaseQueryMetadata {
if(query.getColumns() == null || query.getColumns().isEmpty()) {
return;
}
-
- SQLResult result = new SQLResult();
+
+ resultSetMapping = new ArrayList<>(query.getColumns().size());
for(BaseProperty<?> column : query.getColumns()) {
+ // for each column we need only to know if it's entity
or scalar
Expression exp = column.getExpression();
- String name = column.getName() == null ? exp.expName()
: column.getName();
boolean fullObject = false;
if(exp.getType() == Expression.OBJ_PATH) {
// check if this is toOne relation
- Expression dbPath =
this.getObjEntity().translateToDbPath(exp);
- DbRelationship rel =
findRelationByPath(dbEntity, dbPath);
- if(rel != null && !rel.isToMany()) {
- // it this path is toOne relation, than
select full object for it
- fullObject = true;
- }
+ Object rel = exp.evaluate(getObjEntity());
+ // it this path is toOne relation, than select
full object for it
+ fullObject = rel instanceof ObjRelationship &&
!((ObjRelationship) rel).isToMany();
} else if(exp.getType() == Expression.FULL_OBJECT) {
fullObject = true;
}
if(fullObject) {
- // detected full object column
- if(getPageSize() > 0) {
- // for paginated queries keep only IDs
-
result.addEntityResult(buildEntityIdResultForColumn(column, resolver));
- } else {
- // will unwrap to full set of
db-columns (with join prefetch optionally)
-
result.addEntityResult(buildEntityResultForColumn(query, column, resolver));
- }
+ resultSetMapping.add(ENTITY_RESULT_SEGMENT);
} else {
- // scalar column
- result.addColumnResult(name);
+ resultSetMapping.add(SCALAR_RESULT_SEGMENT);
}
}
- resultSetMapping = result.getResolvedComponents(resolver);
- }
-
- /**
- * Collect metadata for result with ObjectId (used for paginated
queries with FullObject columns)
- *
- * @param column full object column
- * @param resolver entity resolver
- * @return Entity result
- */
- private EntityResult buildEntityIdResultForColumn(BaseProperty<?>
column, EntityResolver resolver) {
- EntityResult result = new EntityResult(column.getType());
- DbEntity entity =
resolver.getObjEntity(column.getType()).getDbEntity();
- for(DbAttribute attribute : entity.getPrimaryKeys()) {
- result.addDbField(attribute.getName(),
attribute.getName());
- }
- return result;
- }
-
- private DbRelationship findRelationByPath(DbEntity entity, Expression
exp) {
- DbRelationship r = null;
- for (PathComponent<DbAttribute, DbRelationship> component :
entity.resolvePath(exp, getPathSplitAliases())) {
- r = component.getRelationship();
- }
- return r;
- }
-
- /**
- * Collect metadata for column that will be unwrapped to full entity in
the final SQL
- * (possibly including joint prefetch).
- * This information will be used to correctly create Persistent object
back from raw result.
- *
- * @param query original query
- * @param column full object column
- * @param resolver entity resolver to get ObjEntity and ClassDescriptor
- * @return Entity result
- */
- private EntityResult buildEntityResultForColumn(SelectQuery<?> query,
BaseProperty<?> column, EntityResolver resolver) {
- // This method is actually repeating logic of
DescriptorColumnExtractor.
- // Here we don't care about intermediate joins and few other
things so it's shorter.
- // Logic of these methods should be unified and simplified,
possibly to a single source of metadata,
- // generated only once and used everywhere.
-
- final EntityResult result = new EntityResult(column.getType());
-
- // Collecting visitor for ObjAttributes and toOne relationships
- PropertyVisitor visitor = new PropertyVisitor() {
- public boolean visitAttribute(AttributeProperty
property) {
- ObjAttribute oa = property.getAttribute();
- Iterator<CayenneMapEntry> dbPathIterator =
oa.getDbPathIterator();
- while (dbPathIterator.hasNext()) {
- CayenneMapEntry pathPart =
dbPathIterator.next();
- if (pathPart instanceof DbAttribute) {
-
result.addDbField(pathPart.getName(), pathPart.getName());
- }
- }
- return true;
- }
-
- public boolean visitToMany(ToManyProperty property) {
- return true;
- }
-
- public boolean visitToOne(ToOneProperty property) {
- DbRelationship dbRel =
property.getRelationship().getDbRelationships().get(0);
- List<DbJoin> joins = dbRel.getJoins();
- for (DbJoin join : joins) {
- if(!join.getSource().isPrimaryKey()) {
-
result.addDbField(join.getSource().getName(), join.getSource().getName());
- }
- }
- return true;
- }
- };
-
- ObjEntity oe = resolver.getObjEntity(column.getType());
- DbEntity table = oe.getDbEntity();
-
- // Additionally collect PKs
- for (DbAttribute dba : table.getPrimaryKeys()) {
- result.addDbField(dba.getName(), dba.getName());
- }
-
- ClassDescriptor descriptor =
resolver.getClassDescriptor(oe.getName());
- descriptor.visitAllProperties(visitor);
-
- // Collection columns for joint prefetch
- if(query.getPrefetchTree() != null) {
- for (PrefetchTreeNode prefetch :
query.getPrefetchTree().adjacentJointNodes()) {
- // for each prefetch add columns from the
target entity
- Expression prefetchExp =
ExpressionFactory.exp(prefetch.getPath());
- ASTDbPath dbPrefetch = (ASTDbPath)
oe.translateToDbPath(prefetchExp);
- DbRelationship r = findRelationByPath(table,
dbPrefetch);
- if (r == null) {
- throw new
CayenneRuntimeException("Invalid joint prefetch '%s' for entity: %s"
- , prefetch,
oe.getName());
- }
-
- // go via target OE to make sure that Java
types are mapped correctly...
- ObjRelationship targetRel = (ObjRelationship)
prefetchExp.evaluate(oe);
- ObjEntity targetEntity =
targetRel.getTargetEntity();
-
prefetch.setEntityName(targetRel.getSourceEntity().getName());
-
- String labelPrefix = dbPrefetch.getPath();
- Set<String> seenNames = new HashSet<>();
- for (ObjAttribute oa :
targetEntity.getAttributes()) {
- Iterator<CayenneMapEntry>
dbPathIterator = oa.getDbPathIterator();
- while (dbPathIterator.hasNext()) {
- Object pathPart =
dbPathIterator.next();
- if (pathPart instanceof
DbAttribute) {
- DbAttribute attribute =
(DbAttribute) pathPart;
-
if(seenNames.add(attribute.getName())) {
-
result.addDbField(labelPrefix + '.' + attribute.getName(), labelPrefix + '.' +
attribute.getName());
- }
- }
- }
- }
-
- // append remaining target attributes such as
keys
- DbEntity targetDbEntity = r.getTargetEntity();
- for (DbAttribute attribute :
targetDbEntity.getAttributes()) {
- if(seenNames.add(attribute.getName())) {
- result.addDbField(labelPrefix +
'.' + attribute.getName(), labelPrefix + '.' + attribute.getName());
- }
- }
- }
- }
-
- return result;
}
/**
diff --git
a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractorTest.java
b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractorTest.java
index 2836ab8..18d706a 100644
---
a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractorTest.java
+++
b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractorTest.java
@@ -46,6 +46,7 @@ public class CustomColumnSetExtractorTest extends
BaseColumnExtractorTest {
public void testExtractWithoutPrefix() {
DbEntity mockDbEntity = createMockDbEntity("mock");
TranslatableQueryWrapper wrapper = new MockQueryWrapperBuilder()
+ .withNeedsResultSetMapping(true)
.withMetaData(new MockQueryMetadataBuilder()
.withDbEntity(mockDbEntity)
.build())
diff --git
a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockQueryWrapperBuilder.java
b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockQueryWrapperBuilder.java
index 4361150..e1bcc5c 100644
---
a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockQueryWrapperBuilder.java
+++
b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/select/MockQueryWrapperBuilder.java
@@ -50,6 +50,7 @@ class MockQueryWrapperBuilder {
private Expression havingQualifier;
private Select<?> mockSelect;
+ private boolean needsResultSetMapping;
MockQueryWrapperBuilder withDistinct(boolean distinct) {
this.distinct = distinct;
@@ -91,6 +92,11 @@ class MockQueryWrapperBuilder {
return this;
}
+ MockQueryWrapperBuilder withNeedsResultSetMapping(boolean
needsResultSetMapping) {
+ this.needsResultSetMapping = needsResultSetMapping;
+ return this;
+ }
+
TranslatableQueryWrapper build() {
return new TranslatableQueryWrapper() {
@Override
@@ -127,6 +133,11 @@ class MockQueryWrapperBuilder {
public Select<?> unwrap() {
return mockSelect;
}
+
+ @Override
+ public boolean needsResultSetMapping() {
+ return needsResultSetMapping;
+ }
};
}
}