You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2017/03/21 09:16:31 UTC
[2/2] cayenne git commit: CAY-2255 ObjectSelect: columns as full
entities CAY-2271 ColumnSelect: support for prefetch and limit
CAY-2255 ObjectSelect: columns as full entities
CAY-2271 ColumnSelect: support for prefetch and limit
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/e098f236
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/e098f236
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/e098f236
Branch: refs/heads/master
Commit: e098f236032ee8cdd695993f7b71d1b44388318f
Parents: 05a7725
Author: Nikita Timofeev <st...@gmail.com>
Authored: Tue Mar 21 11:58:35 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Tue Mar 21 11:58:35 2017 +0300
----------------------------------------------------------------------
.../cayenne/access/DataContextQueryAction.java | 38 +-
.../cayenne/access/DataDomainQueryAction.java | 13 +-
.../cayenne/access/IncrementalFaultList.java | 45 +-
.../access/MixedResultIncrementalFaultList.java | 279 ++++++++++
.../jdbc/reader/DefaultRowReaderFactory.java | 4 +-
.../cayenne/access/jdbc/reader/IdRowReader.java | 13 +-
.../select/DefaultSelectTranslator.java | 131 ++++-
.../translator/select/QualifierTranslator.java | 8 +-
.../translator/select/QueryAssemblerHelper.java | 36 +-
.../java/org/apache/cayenne/exp/Expression.java | 5 +
.../apache/cayenne/exp/ExpressionFactory.java | 9 +
.../java/org/apache/cayenne/exp/Property.java | 42 ++
.../cayenne/exp/parser/ASTFullObject.java | 63 +++
.../org/apache/cayenne/query/ObjectSelect.java | 4 +-
.../cayenne/query/SelectQueryMetadata.java | 180 ++++++-
.../org/apache/cayenne/CayenneCompoundIT.java | 24 +
.../apache/cayenne/query/ColumnSelectIT.java | 508 ++++++++++++++++++-
.../cayenne/query/ObjectSelect_RunIT.java | 141 -----
docs/doc/src/main/resources/RELEASE-NOTES.txt | 2 +
19 files changed, 1311 insertions(+), 234 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
index 948671f..0ef2c0c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
@@ -28,6 +28,7 @@ import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.PersistenceState;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.query.EntityResultSegment;
import org.apache.cayenne.query.ObjectIdQuery;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.RefreshQuery;
@@ -75,8 +76,7 @@ class DataContextQueryAction extends ObjectContextQueryAction {
ObjectIdQuery oidQuery = (ObjectIdQuery) query;
if (!oidQuery.isFetchMandatory()) {
- Object object = polymorphicObjectFromCache(
- oidQuery.getObjectId());
+ Object object = polymorphicObjectFromCache(oidQuery.getObjectId());
if (object != null) {
// TODO: andrus, 10/14/2006 - obtaining a row from an object is the
@@ -104,23 +104,27 @@ class DataContextQueryAction extends ObjectContextQueryAction {
@Override
protected boolean interceptPaginatedQuery() {
if (metadata.getPageSize() > 0) {
-
- DbEntity dbEntity = metadata.getDbEntity();
- Integer maxIdQualifierSize = actingDataContext
- .getParentDataDomain()
- .getMaxIdQualifierSize();
+ Integer maxIdQualifierSize = actingDataContext.getParentDataDomain().getMaxIdQualifierSize();
List<?> paginatedList;
- if (dbEntity != null && dbEntity.getPrimaryKeys().size() == 1) {
- paginatedList = new SimpleIdIncrementalFaultList<Object>(
- actingDataContext,
- query,
- maxIdQualifierSize);
+ List<Object> rsMapping = metadata.getResultSetMapping();
+ boolean mixedResults = false;
+ if(rsMapping != null) {
+ if(rsMapping.size() > 1) {
+ mixedResults = true;
+ } else if(rsMapping.size() == 1) {
+ mixedResults = !(rsMapping.get(0) instanceof EntityResultSegment);
+ }
}
- else {
- paginatedList = new IncrementalFaultList<Object>(
- actingDataContext,
- query,
- maxIdQualifierSize);
+
+ if(mixedResults) {
+ paginatedList = new MixedResultIncrementalFaultList<>(actingDataContext, query, maxIdQualifierSize);
+ } else {
+ DbEntity dbEntity = metadata.getDbEntity();
+ if (dbEntity != null && dbEntity.getPrimaryKeys().size() == 1) {
+ paginatedList = new SimpleIdIncrementalFaultList<Object>(actingDataContext, query, maxIdQualifierSize);
+ } else {
+ paginatedList = new IncrementalFaultList<Object>(actingDataContext, query, maxIdQualifierSize);
+ }
}
response = new ListResponse(paginatedList);
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
index 933d616..6e9b9f8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
@@ -672,9 +672,18 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver {
@Override
void convert(List<DataRow> mainRows) {
- ClassDescriptor descriptor = metadata.getClassDescriptor();
PrefetchTreeNode prefetchTree = metadata.getPrefetchTree();
+ List<Object> rsMapping = metadata.getResultSetMapping();
+ EntityResultSegment resultSegment = null;
+ if(rsMapping != null && !rsMapping.isEmpty()) {
+ resultSegment = (EntityResultSegment)rsMapping.get(0);
+ }
+
+ ClassDescriptor descriptor = resultSegment == null
+ ? metadata.getClassDescriptor()
+ : resultSegment.getClassDescriptor();
+
PrefetchProcessorNode node = toResultsTree(descriptor, prefetchTree, mainRows);
List<Persistent> objects = node.getObjects();
updateResponse(mainRows, objects != null ? objects : new ArrayList<>(1));
@@ -714,7 +723,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver {
prefetchTreeNode = new PrefetchTreeNode();
}
PrefetchTreeNode addPath = prefetchTreeNode.addPath(prefetch.getPath());
- addPath.setSemantics(PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
+ addPath.setSemantics(prefetch.getSemantics());
addPath.setPhantom(false);
}
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/access/IncrementalFaultList.java
----------------------------------------------------------------------
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 1e13ea8..bdfce69 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
@@ -59,7 +59,7 @@ import java.util.NoSuchElementException;
public class IncrementalFaultList<E> implements List<E>, Serializable {
protected int pageSize;
- protected List elements;
+ protected final List elements;
protected DataContext dataContext;
protected ObjEntity rootEntity;
protected SelectQuery<?> internalQuery;
@@ -71,7 +71,7 @@ public class IncrementalFaultList<E> implements List<E>, Serializable {
*/
protected int idWidth;
- private IncrementalListHelper helper;
+ IncrementalListHelper helper;
/**
* Defines the upper limit on the size of fetches. This is needed to avoid
@@ -156,12 +156,11 @@ public class IncrementalFaultList<E> implements List<E>, Serializable {
*
* @since 3.0
*/
- protected void fillIn(final Query query, List elementsList) {
+ protected void fillIn(final Query query, List<Object> elementsList) {
elementsList.clear();
- try (ResultIterator it = dataContext.performIteratedQuery(query);) {
-
+ try (ResultIterator it = dataContext.performIteratedQuery(query)) {
while (it.hasNextRow()) {
elementsList.add(it.nextRow());
}
@@ -236,7 +235,6 @@ public class IncrementalFaultList<E> implements List<E>, Serializable {
}
// fetch the range of objects in fetchSize chunks
- boolean fetchesDataRows = internalQuery.isFetchingDataRows();
List<Object> objects = new ArrayList<>(qualsSize);
int fetchSize = maxFetchSize > 0 ? maxFetchSize : Integer.MAX_VALUE;
@@ -244,15 +242,7 @@ public class IncrementalFaultList<E> implements List<E>, Serializable {
int fetchEnd = Math.min(qualsSize, fetchSize);
int fetchBegin = 0;
while (fetchBegin < qualsSize) {
- SelectQuery<Object> query = new SelectQuery<>(rootEntity, ExpressionFactory.joinExp(
- Expression.OR, quals.subList(fetchBegin, fetchEnd)));
-
- query.setFetchingDataRows(fetchesDataRows);
-
- if (!query.isFetchingDataRows()) {
- query.setPrefetchTree(internalQuery.getPrefetchTree());
- }
-
+ SelectQuery<Object> query = createSelectQuery(quals.subList(fetchBegin, fetchEnd));
objects.addAll(dataContext.performQuery(query));
fetchBegin = fetchEnd;
fetchEnd += Math.min(fetchSize, qualsSize - fetchEnd);
@@ -262,13 +252,28 @@ public class IncrementalFaultList<E> implements List<E>, Serializable {
checkPageResultConsistency(objects, ids);
// replace ids in the list with objects
- Iterator it = objects.iterator();
- while (it.hasNext()) {
- helper.updateWithResolvedObjectInRange(it.next(), fromIndex, toIndex);
- }
+ updatePageWithResults(objects, fromIndex, toIndex);
+ }
+ }
- unfetchedObjects -= objects.size();
+ void updatePageWithResults(List<Object> objects, int fromIndex, int toIndex) {
+ for (Object object : objects) {
+ helper.updateWithResolvedObjectInRange(object, fromIndex, toIndex);
}
+
+ unfetchedObjects -= objects.size();
+ }
+
+ SelectQuery<Object> createSelectQuery(List<Expression> expressions) {
+ SelectQuery<Object> query = new SelectQuery<>(rootEntity,
+ ExpressionFactory.joinExp(Expression.OR, expressions));
+
+ query.setFetchingDataRows(internalQuery.isFetchingDataRows());
+ if (!query.isFetchingDataRows()) {
+ query.setPrefetchTree(internalQuery.getPrefetchTree());
+ }
+
+ return query;
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..9ca2136
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
@@ -0,0 +1,279 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.ResultIterator;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.query.ColumnSelect;
+import org.apache.cayenne.query.EntityResultSegment;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.util.Util;
+
+/**
+ * FaultList that is used for paginated {@link ColumnSelect} queries.
+ * It expects data as Object[] where ids are stored instead of Persistent objects (as raw value for single PK
+ * or Map for compound PKs).
+ * Scalar values that were fetched from ColumnSelect not processed in any way,
+ * if there is no Persistent objects in the result Collection it will be iterated as is, without faulting anything.
+ *
+ * @see QueryMetadata#getPageSize()
+ * @see org.apache.cayenne.access.translator.select.DefaultSelectTranslator
+ * @see org.apache.cayenne.query.SelectQueryMetadata
+ *
+ * @since 4.0
+ */
+class MixedResultIncrementalFaultList<E> extends IncrementalFaultList<E> {
+
+ /**
+ * Cached positions for entity results in elements array
+ */
+ private Map<Integer, ObjEntity> indexToEntity;
+
+ /**
+ * Whether result contains only scalars
+ */
+ private boolean scalarResult;
+
+ /**
+ * Creates a new IncrementalFaultList using a given DataContext and query.
+ *
+ * @param dataContext DataContext used by IncrementalFaultList to fill itself with
+ * objects.
+ * @param query Main query used to retrieve data. Must have "pageSize"
+ * property set to a value greater than zero.
+ */
+ MixedResultIncrementalFaultList(DataContext dataContext, Query query, int maxFetchSize) {
+ super(dataContext, query, maxFetchSize);
+
+ // this should generally be true, and may be it worth to do something if it's not
+ if(query instanceof ColumnSelect) {
+ this.internalQuery.setColumns(((ColumnSelect<?>) query).getColumns());
+ }
+ }
+
+ @Override
+ IncrementalListHelper createHelper(QueryMetadata metadata) {
+ // first compile some meta data about results
+ indexToEntity = new HashMap<>();
+ scalarResult = true;
+ for(Object next : metadata.getResultSetMapping()) {
+ if(next instanceof EntityResultSegment) {
+ EntityResultSegment resultSegment = (EntityResultSegment)next;
+ ObjEntity entity = resultSegment.getClassDescriptor().getEntity();
+ // store entity's PK position in result
+ indexToEntity.put(resultSegment.getColumnOffset(), entity);
+ scalarResult = false;
+ }
+ }
+
+ // if there is no entities in this results,
+ // than all data is already there and we don't need to resolve any objects
+ if(indexToEntity.isEmpty()) {
+ return new ScalarArrayListHelper();
+ } else {
+ return new MixedArrayListHelper();
+ }
+ }
+
+ @Override
+ protected void fillIn(final Query query, List<Object> elementsList) {
+ elementsList.clear();
+ try (ResultIterator it = dataContext.performIteratedQuery(query)) {
+ while (it.hasNextRow()) {
+ elementsList.add(it.nextRow());
+ }
+ }
+
+ unfetchedObjects = elementsList.size();
+ }
+
+ @Override
+ protected void resolveInterval(int fromIndex, int toIndex) {
+ if (fromIndex >= toIndex || scalarResult) {
+ return;
+ }
+
+ synchronized (elements) {
+ if (elements.size() == 0) {
+ return;
+ }
+
+ // perform bound checking
+ if (fromIndex < 0) {
+ fromIndex = 0;
+ }
+
+ if (toIndex > elements.size()) {
+ toIndex = elements.size();
+ }
+
+ for(Map.Entry<Integer, ObjEntity> entry : indexToEntity.entrySet()) {
+ List<Expression> quals = new ArrayList<>(pageSize);
+ int dataIdx = entry.getKey();
+ for (int i = fromIndex; i < toIndex; i++) {
+ Object[] object = (Object[])elements.get(i);
+ if (helper.unresolvedSuspect(object[dataIdx])) {
+ quals.add(buildIdQualifier(dataIdx, object));
+ }
+ }
+
+ int qualsSize = quals.size();
+ if (qualsSize == 0) {
+ continue;
+ }
+
+ // fetch the range of objects in fetchSize chunks
+ List<Persistent> objects = new ArrayList<>(qualsSize);
+
+ int fetchSize = maxFetchSize > 0 ? maxFetchSize : Integer.MAX_VALUE;
+ int fetchEnd = Math.min(qualsSize, fetchSize);
+ int fetchBegin = 0;
+ while (fetchBegin < qualsSize) {
+ SelectQuery<Persistent> query = createSelectQuery(entry.getValue(), quals.subList(fetchBegin, fetchEnd));
+ objects.addAll(dataContext.performQuery(query));
+ fetchBegin = fetchEnd;
+ fetchEnd += Math.min(fetchSize, qualsSize - fetchEnd);
+ }
+
+ // replace ids in the list with objects
+ updatePageWithResults(objects, dataIdx);
+ }
+ }
+ }
+
+ void updatePageWithResults(List<Persistent> objects, int dataIndex) {
+ MixedArrayListHelper helper = (MixedArrayListHelper)this.helper;
+ for (Persistent object : objects) {
+ helper.updateWithResolvedObject(object, dataIndex);
+ }
+ }
+
+ SelectQuery<Persistent> createSelectQuery(ObjEntity entity, List<Expression> expressions) {
+ SelectQuery<Persistent> query = new SelectQuery<>(entity, ExpressionFactory.joinExp(Expression.OR, expressions));
+ if (entity.equals(rootEntity)) {
+ query.setPrefetchTree(internalQuery.getPrefetchTree());
+ }
+ return query;
+ }
+
+ Expression buildIdQualifier(int index, Object[] data) {
+ Map<String, Object> map;
+ if(data[index] instanceof Map) {
+ map = (Map<String, Object>)data[index];
+ } else {
+ map = new HashMap<>();
+ int i = 0;
+ for (ObjAttribute attribute : indexToEntity.get(index).getPrimaryKeys()) {
+ map.put(attribute.getDbAttributeName(), data[index + i++]);
+ }
+ }
+ return ExpressionFactory.matchAllDbExp(map, Expression.EQUAL_TO);
+ }
+
+ /**
+ * Helper that operates on Object[] and checks for Persistent objects' presence in it.
+ */
+ class MixedArrayListHelper extends IncrementalListHelper {
+ @Override
+ boolean unresolvedSuspect(Object object) {
+ return !(object instanceof Persistent);
+ }
+
+ @Override
+ boolean objectsAreEqual(Object object, Object objectInTheList) {
+ if(!(object instanceof Object[])){
+ return false;
+ }
+ return Arrays.equals((Object[])object, (Object[])objectInTheList);
+ }
+
+ @Override
+ boolean replacesObject(Object object, Object objectInTheList) {
+ throw new UnsupportedOperationException();
+ }
+
+ boolean replacesObject(Persistent object, Object[] dataInTheList, int dataIdx) {
+ Map<?, ?> map = object.getObjectId().getIdSnapshot();
+
+ if(dataInTheList[dataIdx] instanceof Map) {
+ Map<?, ?> id = (Map<?, ?>) dataInTheList[dataIdx];
+ if (id.size() != map.size()) {
+ return false;
+ }
+
+ for (Map.Entry<?, ?> entry : id.entrySet()) {
+ if (!Util.nullSafeEquals(entry.getValue(), map.get(entry.getKey()))) {
+ return false;
+ }
+ }
+ } else {
+ for(Object id : map.values()) {
+ if (!dataInTheList[dataIdx++].equals(id)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ void updateWithResolvedObject(Persistent object, int dataIdx) {
+ synchronized (elements) {
+ for (Object element : elements) {
+ Object[] data = (Object[]) element;
+ if (replacesObject(object, data, dataIdx)) {
+ data[dataIdx] = object;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper that actually does nothing
+ */
+ class ScalarArrayListHelper extends IncrementalListHelper {
+ @Override
+ boolean unresolvedSuspect(Object object) {
+ return false;
+ }
+
+ @Override
+ boolean objectsAreEqual(Object object, Object objectInTheList) {
+ return objectInTheList.equals(object);
+ }
+
+ @Override
+ boolean replacesObject(Object object, Object objectInTheList) {
+ return false;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
index b9cb766..73a29e7 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
@@ -96,7 +96,7 @@ public class DefaultRowReaderFactory implements RowReaderFactory {
EntityResultSegment resultMetadata, PostprocessorFactory postProcessorFactory) {
if (queryMetadata.getPageSize() > 0) {
- return new IdRowReader<Object>(descriptor, queryMetadata, postProcessorFactory.get());
+ return new IdRowReader<Object>(descriptor, queryMetadata, resultMetadata, postProcessorFactory.get());
} else if (resultMetadata.getClassDescriptor() != null && resultMetadata.getClassDescriptor().hasSubclasses()) {
return new InheritanceAwareEntityRowReader(descriptor, resultMetadata, postProcessorFactory.get());
} else {
@@ -108,7 +108,7 @@ public class DefaultRowReaderFactory implements RowReaderFactory {
PostprocessorFactory postProcessorFactory) {
if (queryMetadata.getPageSize() > 0) {
- return new IdRowReader<Object>(descriptor, queryMetadata, postProcessorFactory.get());
+ return new IdRowReader<Object>(descriptor, queryMetadata, null, postProcessorFactory.get());
} else if (queryMetadata.getClassDescriptor() != null && queryMetadata.getClassDescriptor().hasSubclasses()) {
return new InheritanceAwareRowReader(descriptor, queryMetadata, postProcessorFactory.get());
} else {
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/IdRowReader.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/IdRowReader.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/IdRowReader.java
index a108dd7..7ad539d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/IdRowReader.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/IdRowReader.java
@@ -26,6 +26,7 @@ import org.apache.cayenne.access.jdbc.ColumnDescriptor;
import org.apache.cayenne.access.jdbc.RowDescriptor;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.query.EntityResultSegment;
import org.apache.cayenne.query.QueryMetadata;
import org.apache.cayenne.util.Util;
@@ -36,10 +37,12 @@ class IdRowReader<T> extends BaseRowReader<T> {
protected int[] pkIndices;
- public IdRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, DataRowPostProcessor postProcessor) {
+ public IdRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, EntityResultSegment resultMetadata, DataRowPostProcessor postProcessor) {
super(descriptor, queryMetadata, postProcessor);
- DbEntity dbEntity = queryMetadata.getDbEntity();
+ DbEntity dbEntity = resultMetadata == null
+ ? queryMetadata.getDbEntity()
+ : resultMetadata.getClassDescriptor().getEntity().getDbEntity();
if (dbEntity == null) {
throw new CayenneRuntimeException("Null root DbEntity, can't index PK");
}
@@ -99,13 +102,9 @@ class IdRowReader<T> extends BaseRowReader<T> {
DataRow idRow = new DataRow(2);
idRow.setEntityName(entityName);
- int len = pkIndices.length;
-
- for (int i = 0; i < len; i++) {
+ for (int index : pkIndices) {
// dereference column index
- int index = pkIndices[i];
-
// note: jdbc column indexes start from 1, not 0 as in arrays
Object val = converters[index].materializeObject(resultSet, index + 1, types[index]);
idRow.put(labels[index], val);
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
----------------------------------------------------------------------
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 affbe7a..42ac0b8 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
@@ -19,6 +19,7 @@
package org.apache.cayenne.access.translator.select;
import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.Persistent;
import org.apache.cayenne.access.jdbc.ColumnDescriptor;
import org.apache.cayenne.access.translator.DbAttributeBinding;
import org.apache.cayenne.dba.DbAdapter;
@@ -104,6 +105,11 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
boolean haveAggregate;
Map<ColumnDescriptor, List<DbAttributeBinding>> groupByColumns;
+ /**
+ * Callback for joins creation
+ */
+ AddJoinListener joinListener;
+
public DefaultSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
super(query, adapter, entityResolver);
@@ -278,14 +284,22 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
* @since 4.0
*/
protected void appendGroupByColumn(StringBuilder buffer, Map.Entry<ColumnDescriptor, List<DbAttributeBinding>> entry) {
+ String fullName;
+ if(entry.getKey().isExpression()) {
+ fullName = entry.getKey().getDataRowKey();
+ } else {
+ QuotingStrategy strategy = getAdapter().getQuotingStrategy();
+ fullName = strategy.quotedIdentifier(queryMetadata.getDataMap(),
+ entry.getKey().getNamePrefix(), entry.getKey().getName());
+ }
+
+ buffer.append(fullName);
+
if(entry.getKey().getDataRowKey().equals(entry.getKey().getName())) {
- buffer.append(entry.getKey().getName());
- for (DbAttributeBinding binding : entry.getValue()) {
- addToParamList(binding.getAttribute(), binding.getValue());
- }
- } else {
- buffer.append(entry.getKey().getDataRowKey());
- }
+ for (DbAttributeBinding binding : entry.getValue()) {
+ addToParamList(binding.getAttribute(), binding.getValue());
+ }
+ }
}
/**
@@ -360,9 +374,9 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
} else if (query.getRoot() instanceof DbEntity) {
appendDbEntityColumns(columns, query);
} else if (getQueryMetadata().getPageSize() > 0) {
- appendIdColumns(columns, query);
+ appendIdColumns(columns, queryMetadata.getClassDescriptor().getEntity());
} else {
- appendQueryColumns(columns, query);
+ appendQueryColumns(columns, query, queryMetadata.getClassDescriptor(), null);
}
return columns;
@@ -376,12 +390,60 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
QualifierTranslator qualifierTranslator = adapter.getQualifierTranslator(this);
AccumulatingBindingListener bindingListener = new AccumulatingBindingListener();
+ final String[] joinTableAliasForProperty = {null};
+ joinListener = new AddJoinListener() {
+ @Override
+ public void joinAdded() {
+ // capture last alias for joined table, will use it to resolve object columns
+ joinTableAliasForProperty[0] = getCurrentAlias();
+ }
+ };
setAddBindingListener(bindingListener);
for(Property<?> property : query.getColumns()) {
+ int expressionType = property.getExpression().getType();
+ boolean objectProperty = expressionType == Expression.FULL_OBJECT;
+ // evaluate ObjPath with Persistent type as toOne relations and use it as full object
+ if(Persistent.class.isAssignableFrom(property.getType())) {
+ if(expressionType == Expression.OBJ_PATH) {
+ objectProperty = true;
+ } else {
+ // should we warn or throw an error?
+ }
+ }
+
+ // forbid direct selection of toMany relationships columns
+ if((expressionType == Expression.OBJ_PATH || expressionType == Expression.DB_PATH) &&
+ (Collection.class.isAssignableFrom(property.getType()) ||
+ Map.class.isAssignableFrom(property.getType()))) {
+ throw new CayenneRuntimeException("Can't directly select toMany relationship columns. " +
+ "Either select it with aggregate functions like count() or with flat() function to select full related objects.");
+ }
+
+ // Qualifier Translator in case of Object Columns have side effect -
+ // it will create required joins, that we catch with listener above.
+ // And we force created join alias for all columns of Object we select.
qualifierTranslator.setQualifier(property.getExpression());
+ qualifierTranslator.setForceJoinForRelations(objectProperty);
StringBuilder builder = qualifierTranslator.appendPart(new StringBuilder());
+ // If we want full object, use appendQueryColumns method, to fully process class descriptor
+ if(objectProperty) {
+ List<ColumnDescriptor> classColumns = new ArrayList<>();
+ ObjEntity entity = entityResolver.getObjEntity(property.getType());
+ if(getQueryMetadata().getPageSize() > 0) {
+ appendIdColumns(classColumns, entity);
+ } else {
+ ClassDescriptor classDescriptor = entityResolver.getClassDescriptor(entity.getName());
+ appendQueryColumns(classColumns, query, classDescriptor, joinTableAliasForProperty[0]);
+ }
+ for(ColumnDescriptor descriptor : classColumns) {
+ columns.add(descriptor);
+ groupByColumns.put(descriptor, Collections.<DbAttributeBinding>emptyList());
+ }
+ continue;
+ }
+
int type = TypesMapping.getSqlTypeByJava(property.getType());
String alias = property.getAlias();
@@ -402,6 +464,8 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
}
setAddBindingListener(null);
+ qualifierTranslator.setForceJoinForRelations(false);
+ joinListener = null;
if(!haveAggregate) {
// if no expression with aggregation function found, we don't need this information
@@ -442,9 +506,9 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
* Appends columns needed for object SelectQuery to the provided columns
* list.
*/
- <T> List<ColumnDescriptor> appendQueryColumns(final List<ColumnDescriptor> columns, SelectQuery<T> query) {
+ <T> List<ColumnDescriptor> appendQueryColumns(final List<ColumnDescriptor> columns, SelectQuery<T> query, ClassDescriptor descriptor, final String tableAlias) {
- final Set<ColumnTracker> attributes = new HashSet<ColumnTracker>();
+ final Set<ColumnTracker> attributes = new HashSet<>();
// fetched attributes include attributes that are either:
//
@@ -452,8 +516,6 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
// * PK
// * FK used in relationship
// * joined prefetch PK
-
- ClassDescriptor descriptor = queryMetadata.getClassDescriptor();
ObjEntity oe = descriptor.getEntity();
PropertyVisitor visitor = new PropertyVisitor() {
@@ -474,7 +536,7 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
} else if (pathPart instanceof DbAttribute) {
DbAttribute dbAttr = (DbAttribute) pathPart;
- appendColumn(columns, oa, dbAttr, attributes, null);
+ appendColumn(columns, oa, dbAttr, attributes, null, tableAlias);
}
}
return true;
@@ -499,7 +561,7 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
List<DbJoin> joins = dbRel.getJoins();
for (DbJoin join : joins) {
DbAttribute src = join.getSource();
- appendColumn(columns, null, src, attributes, null);
+ appendColumn(columns, null, src, attributes, null, tableAlias);
}
}
};
@@ -511,9 +573,9 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
resetJoinStack();
// add remaining needed attrs from DbEntity
- DbEntity table = getQueryMetadata().getDbEntity();
+ DbEntity table = oe.getDbEntity();
for (DbAttribute dba : table.getPrimaryKeys()) {
- appendColumn(columns, null, dba, attributes, null);
+ appendColumn(columns, null, dba, attributes, null, tableAlias);
}
// special handling of a disjoint query...
@@ -571,6 +633,12 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
// handle joint prefetches directly attached to this query...
if (query.getPrefetchTree() != null) {
+ // Set entity name, in case MixedConversionStrategy will be used to select objects from this query
+ // Note: all prefetch nodes will point to query root, it is not a problem until select query can't
+ // perform some sort of union or sub-queries.
+ for(PrefetchTreeNode prefetch : query.getPrefetchTree().getChildren()) {
+ prefetch.setEntityName(oe.getName());
+ }
for (PrefetchTreeNode prefetch : query.getPrefetchTree().adjacentJointNodes()) {
@@ -633,14 +701,12 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
return columns;
}
- <T> List<ColumnDescriptor> appendIdColumns(final List<ColumnDescriptor> columns, SelectQuery<T> query) {
+ <T> List<ColumnDescriptor> appendIdColumns(final List<ColumnDescriptor> columns, ObjEntity objEntity) {
- Set<ColumnTracker> skipSet = new HashSet<ColumnTracker>();
+ Set<ColumnTracker> skipSet = new HashSet<>();
- ClassDescriptor descriptor = queryMetadata.getClassDescriptor();
- ObjEntity oe = descriptor.getEntity();
- DbEntity dbEntity = oe.getDbEntity();
- for (ObjAttribute attribute : oe.getPrimaryKeys()) {
+ DbEntity dbEntity = objEntity.getDbEntity();
+ for (ObjAttribute attribute : objEntity.getPrimaryKeys()) {
// synthetic objattributes can't reliably lookup their DbAttribute,
// so do it manually..
@@ -652,9 +718,17 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
}
private void appendColumn(List<ColumnDescriptor> columns, ObjAttribute objAttribute, DbAttribute attribute,
- Set<ColumnTracker> skipSet, String label) {
+ Set<ColumnTracker> skipSet, String label) {
+ appendColumn(columns, objAttribute, attribute, skipSet, label, null);
+ }
+
+ private void appendColumn(List<ColumnDescriptor> columns, ObjAttribute objAttribute, DbAttribute attribute,
+ Set<ColumnTracker> skipSet, String label, String alias) {
+
+ if(alias == null) {
+ alias = getCurrentAlias();
+ }
- String alias = getCurrentAlias();
if (skipSet.add(new ColumnTracker(alias, attribute))) {
ColumnDescriptor column = (objAttribute != null) ? new ColumnDescriptor(objAttribute, attribute, alias)
@@ -712,6 +786,9 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
}
getJoinStack().pushJoin(relationship, joinType, joinSplitAlias);
+ if(joinListener != null) {
+ joinListener.joinAdded();
+ }
}
/**
@@ -788,4 +865,8 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
return new ArrayList<>(bindings);
}
}
+
+ interface AddJoinListener {
+ void joinAdded();
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
index deb7d52..51ebf52 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
@@ -142,9 +142,7 @@ public class QualifierTranslator extends QueryAssemblerHelper implements Travers
}
}
- /**
- * Attaching root Db entity's qualifier
- */
+ // Attaching root Db entity's qualifier
if (getDbEntity() != null) {
Expression dbQualifier = getDbEntity().getQualifier();
if (dbQualifier != null) {
@@ -425,6 +423,10 @@ public class QualifierTranslator extends QueryAssemblerHelper implements Travers
return;
}
+ if(node.getType() == Expression.FULL_OBJECT && parentNode != null) {
+ throw new CayenneRuntimeException("Expression is not supported in where clause.");
+ }
+
int count = node.getOperandCount();
if (count == 2) {
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java
index decdaa7..2fc6078 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java
@@ -46,6 +46,12 @@ public abstract class QueryAssemblerHelper {
protected QuotingStrategy strategy;
/**
+ * Force joining tables for all relations, not only for toMany
+ * @since 4.0
+ */
+ private boolean forceJoinForRelations;
+
+ /**
* Creates QueryAssemblerHelper initializing with parent
* {@link QueryAssembler} and output buffer object.
*/
@@ -444,7 +450,7 @@ public abstract class QueryAssemblerHelper {
*/
protected void processRelTermination(DbRelationship rel, JoinType joinType, String joinSplitAlias) {
- if (rel.isToMany()) {
+ if (forceJoinForRelations || rel.isToMany()) {
// append joins
queryAssembler.dbRelationshipAdded(rel, joinType, joinSplitAlias);
}
@@ -452,33 +458,39 @@ public abstract class QueryAssemblerHelper {
// get last DbRelationship on the list
List<DbJoin> joins = rel.getJoins();
if (joins.size() != 1) {
- StringBuilder msg = new StringBuilder();
- msg.append("OBJ_PATH expressions are only supported ").append("for a single-join relationships. ")
- .append("This relationship has ").append(joins.size()).append(" joins.");
+ String msg = "OBJ_PATH expressions are only supported for a single-join relationships. " +
+ "This relationship has " + joins.size() + " joins.";
- throw new CayenneRuntimeException(msg.toString());
+ throw new CayenneRuntimeException(msg);
}
DbJoin join = joins.get(0);
- DbAttribute attribute = null;
+ DbAttribute attribute;
if (rel.isToMany()) {
- DbEntity ent = (DbEntity) join.getRelationship().getTargetEntity();
+ DbEntity ent = join.getRelationship().getTargetEntity();
Collection<DbAttribute> pk = ent.getPrimaryKeys();
if (pk.size() != 1) {
- StringBuilder msg = new StringBuilder();
- msg.append("DB_NAME expressions can only support ").append("targets with a single column PK. ")
- .append("This entity has ").append(pk.size()).append(" columns in primary key.");
+ String msg = "DB_NAME expressions can only support targets with a single column PK. " +
+ "This entity has " + pk.size() + " columns in primary key.";
- throw new CayenneRuntimeException(msg.toString());
+ throw new CayenneRuntimeException(msg);
}
attribute = pk.iterator().next();
} else {
- attribute = join.getSource();
+ attribute = forceJoinForRelations ? join.getTarget() : join.getSource();
}
processColumn(attribute);
}
+
+ /**
+ * Force joining tables for all relations, not only for toMany
+ * @since 4.0
+ */
+ protected void setForceJoinForRelations(boolean forceJoinForRelations) {
+ this.forceJoinForRelations = forceJoinForRelations;
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
index 8491e23..0d35cea 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
@@ -161,6 +161,11 @@ public abstract class Expression implements Serializable, XMLSerializable {
*/
public static final int ASTERISK = 46;
+ /**
+ * @since 4.0
+ */
+ public static final int FULL_OBJECT = 47;
+
protected int type;
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
index e9fa6b0..b2770bc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
@@ -33,6 +33,7 @@ import org.apache.cayenne.exp.parser.ASTDbPath;
import org.apache.cayenne.exp.parser.ASTDivide;
import org.apache.cayenne.exp.parser.ASTEqual;
import org.apache.cayenne.exp.parser.ASTFalse;
+import org.apache.cayenne.exp.parser.ASTFullObject;
import org.apache.cayenne.exp.parser.ASTGreater;
import org.apache.cayenne.exp.parser.ASTGreaterOrEqual;
import org.apache.cayenne.exp.parser.ASTIn;
@@ -1245,6 +1246,14 @@ public class ExpressionFactory {
return joinExp(Expression.OR, pairs);
}
+ public static Expression fullObjectExp() {
+ return new ASTFullObject();
+ }
+
+ public static Expression fullObjectExp(Expression exp) {
+ return new ASTFullObject(exp);
+ }
+
/**
* @since 4.0
*/
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
index 59eb17d..9ceaf6f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
@@ -18,6 +18,8 @@
****************************************************************/
package org.apache.cayenne.exp;
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.Persistent;
import org.apache.cayenne.exp.parser.ASTPath;
import org.apache.cayenne.query.Ordering;
import org.apache.cayenne.query.PrefetchTreeNode;
@@ -27,6 +29,7 @@ import org.apache.cayenne.reflect.PropertyUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
/**
* <p>
@@ -788,6 +791,24 @@ public class Property<E> {
return new Property<>(alias, this.getExpression(), this.getType());
}
+ /**
+ * <p>Create new "flat" property for toMany relationship.</p>
+ * <p>
+ * Example:
+ * <pre>{@code
+ * List<Object[]> result = ObjectSelect
+ * .columnQuery(Artist.class, Artist.ARTIST_NAME, Artist.PAINTING_ARRAY.flat(Painting.class))
+ * .select(context);
+ * }</pre>
+ * </p>
+ */
+ public <T extends Persistent> Property<T> flat(Class<? super T> tClass) {
+ if(!Collection.class.isAssignableFrom(type) && !Map.class.isAssignableFrom(type)) {
+ throw new CayenneRuntimeException("Can use flat() function only on Property mapped on toMany relationship.");
+ }
+ return create(ExpressionFactory.fullObjectExp(getExpression()), tClass);
+ }
+
public Class<? super E> getType() {
return type;
}
@@ -820,6 +841,27 @@ public class Property<E> {
}
/**
+ * <p>
+ * Creates "self" Property for persistent class.
+ * This property can be used to select full object along with some of it properties (or
+ * properties that can be resolved against query root)
+ * </p>
+ * <p>
+ * Here is sample code, that will select all Artists and count of their Paintings:
+ * <pre>{@code
+ * Property<Artist> artistFull = Property.createSelf(Artist.class);
+ * List<Object[]> result = ObjectSelect
+ * .columnQuery(Artist.class, artistFull, Artist.PAINTING_ARRAY.count())
+ * .select(context);
+ * }
+ * </pre>
+ * </p>
+ */
+ public static <T extends Persistent> Property<T> createSelf(Class<? super T> type) {
+ return new Property<>(null, ExpressionFactory.fullObjectExp(), type);
+ }
+
+ /**
* Since Expression is mutable we need to provide clean Expression for every getter call.
* So to keep Property itself immutable we use ExpressionProvider.
* @see Property#Property(String, Class)
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java
new file mode 100644
index 0000000..25f6d97
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java
@@ -0,0 +1,63 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.parser;
+
+import org.apache.cayenne.exp.Expression;
+
+/**
+ * @since 4.0
+ */
+public class ASTFullObject extends SimpleNode {
+
+ public ASTFullObject(Expression expression) {
+ this();
+ Node node = wrapChild(expression);
+ jjtAddChild(node, 0);
+ node.jjtSetParent(this);
+ }
+
+ public ASTFullObject() {
+ this(0);
+ }
+
+ protected ASTFullObject(int i) {
+ super(i);
+ }
+
+ @Override
+ protected String getExpressionOperator(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected Object evaluateNode(Object o) throws Exception {
+ return o;
+ }
+
+ @Override
+ public Expression shallowCopy() {
+ return new ASTFullObject(id);
+ }
+
+ @Override
+ public int getType() {
+ return Expression.FULL_OBJECT;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
index 262f18c..da03ccb 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
@@ -125,7 +125,7 @@ public class ObjectSelect<T> extends FluentSelect<T, ObjectSelect<T>> {
* @param entityType base persistent class that will be used as a root for this query
* @param column single column to select
*/
- protected static <E> ColumnSelect<E> columnQuery(Class<?> entityType, Property<E> column) {
+ public static <E> ColumnSelect<E> columnQuery(Class<?> entityType, Property<E> column) {
return new ColumnSelect<>().entityType(entityType).column(column);
}
@@ -136,7 +136,7 @@ public class ObjectSelect<T> extends FluentSelect<T, ObjectSelect<T>> {
* @param firstColumn column to select
* @param otherColumns columns to select
*/
- protected static ColumnSelect<Object[]> columnQuery(Class<?> entityType, Property<?> firstColumn, Property<?>... otherColumns) {
+ public static ColumnSelect<Object[]> columnQuery(Class<?> entityType, Property<?> firstColumn, Property<?>... otherColumns) {
return new ColumnSelect<Object[]>().entityType(entityType).columns(firstColumn, otherColumns);
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
----------------------------------------------------------------------
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 aba36c5..158fbea 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
@@ -21,14 +21,34 @@ package org.apache.cayenne.query;
import java.io.IOException;
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.Property;
+import org.apache.cayenne.exp.parser.ASTDbPath;
+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.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
@@ -185,6 +205,7 @@ class SelectQueryMetadata extends BaseQueryMetadata {
}
/**
+ * Build DB result descriptor, that will be used to read and convert raw result of ColumnSelect
* @since 4.0
*/
private void buildResultSetMappingForColumns(SelectQuery<?> query, EntityResolver resolver) {
@@ -194,13 +215,168 @@ class SelectQueryMetadata extends BaseQueryMetadata {
SQLResult result = new SQLResult();
for(Property<?> column : query.getColumns()) {
- String name = column.getName() == null ? column.getExpression().expName() : column.getName();
- result.addColumnResult(name);
+ 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;
+ }
+ } 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));
+ }
+ } else {
+ // scalar column
+ result.addColumnResult(name);
+ }
}
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(Property<?> 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.
+ *
+ * This method is actually repeating logic of
+ * {@link org.apache.cayenne.access.translator.select.DefaultSelectTranslator#appendQueryColumns}.
+ * 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.
+ *
+ * @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, Property<?> column, EntityResolver resolver) {
+ 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 '" + prefetch + "' for entity: "
+ + 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;
+ }
+
+ /**
* @since 4.0
*/
@Override
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/test/java/org/apache/cayenne/CayenneCompoundIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/CayenneCompoundIT.java b/cayenne-server/src/test/java/org/apache/cayenne/CayenneCompoundIT.java
index 83913b6..191c725 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/CayenneCompoundIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/CayenneCompoundIT.java
@@ -20,6 +20,8 @@
package org.apache.cayenne;
import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.query.ObjectSelect;
import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.test.jdbc.DBHelper;
import org.apache.cayenne.test.jdbc.TableHelper;
@@ -65,6 +67,12 @@ public class CayenneCompoundIT extends ServerCase {
tCompoundPKTest.insert("PK1", "PK2", "BBB");
}
+ private void createCompoundPKs(int size) throws Exception {
+ for(int i=0; i<size; i++) {
+ tCompoundPKTest.insert("PK"+i, "PK"+(2*i), "BBB"+i);
+ }
+ }
+
private void createOneCharPK() throws Exception {
tCharPKTest.insert("CPK", "AAAA");
}
@@ -157,4 +165,20 @@ public class CayenneCompoundIT extends ServerCase {
assertEquals("CPK", Cayenne.pkForObject(object));
}
+
+ @Test
+ public void testPaginatedColumnSelect() throws Exception {
+ createCompoundPKs(20);
+
+ List<Object[]> result = ObjectSelect.query(CompoundPkTestEntity.class)
+ .columns(CompoundPkTestEntity.NAME, Property.createSelf(CompoundPkTestEntity.class))
+ .pageSize(7)
+ .select(context);
+ assertEquals(20, result.size());
+ for(Object[] next : result) {
+ assertEquals(2, next.length);
+ assertEquals(String.class, next[0].getClass());
+ assertEquals(CompoundPkTestEntity.class, next[1].getClass());
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/e098f236/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
index 4319398..02102c5 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
@@ -23,15 +23,24 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Types;
import java.text.DateFormat;
+import java.util.List;
import java.util.Locale;
import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.Fault;
+import org.apache.cayenne.PersistenceState;
+import org.apache.cayenne.ResultBatchIterator;
+import org.apache.cayenne.ResultIteratorCallback;
import org.apache.cayenne.access.DataContext;
import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.FunctionExpressionFactory;
import org.apache.cayenne.exp.Property;
import org.apache.cayenne.test.jdbc.DBHelper;
import org.apache.cayenne.test.jdbc.TableHelper;
import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Gallery;
import org.apache.cayenne.testdo.testmap.Painting;
import org.apache.cayenne.unit.PostgresUnitDbAdapter;
import org.apache.cayenne.unit.UnitDbAdapter;
@@ -42,8 +51,10 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
-import static org.apache.cayenne.exp.FunctionExpressionFactory.substringExp;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
@@ -333,4 +344,499 @@ public class ColumnSelectIT extends ServerCase {
.selectOne(context);
assertEquals(count2, count3);
}
+
+ @Test
+ public void testSelectFirst_MultiColumns() throws Exception {
+ Object[] a = ObjectSelect.query(Artist.class)
+ .columns(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH)
+ .columns(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH)
+ .columns(Artist.ARTIST_NAME.alias("newName"))
+ .where(Artist.ARTIST_NAME.like("artist%"))
+ .orderBy("db:ARTIST_ID")
+ .selectFirst(context);
+ assertNotNull(a);
+ assertEquals("artist1", a[0]);
+ assertEquals("artist1", a[4]);
+ }
+
+ @Test
+ public void testSelectFirst_SingleValueInColumns() throws Exception {
+ Object[] a = ObjectSelect.query(Artist.class)
+ .columns(Artist.ARTIST_NAME)
+ .where(Artist.ARTIST_NAME.like("artist%"))
+ .orderBy("db:ARTIST_ID")
+ .selectFirst(context);
+ assertNotNull(a);
+ assertEquals("artist1", a[0]);
+ }
+
+ @Test
+ public void testSelectFirst_SubstringName() throws Exception {
+ Expression exp = FunctionExpressionFactory.substringExp(Artist.ARTIST_NAME.path(), 5, 3);
+ Property<String> substrName = Property.create("substrName", exp, String.class);
+ Object[] a = ObjectSelect.query(Artist.class)
+ .columns(Artist.ARTIST_NAME, substrName)
+ .where(substrName.eq("st3"))
+ .selectFirst(context);
+
+ assertNotNull(a);
+ assertEquals("artist3", a[0]);
+ assertEquals("st3", a[1]);
+ }
+
+ @Test
+ public void testSelectFirst_RelColumns() throws Exception {
+ // set shorter than painting_array.paintingTitle alias as some DBs doesn't support dot in alias
+ Property<String> paintingTitle = Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE).alias("paintingTitle");
+
+ Object[] a = ObjectSelect.query(Artist.class)
+ .columns(Artist.ARTIST_NAME, paintingTitle)
+ .orderBy(paintingTitle.asc())
+ .selectFirst(context);
+ assertNotNull(a);
+ assertEquals("painting1", a[1]);
+ }
+
+ @Test
+ public void testSelectFirst_RelColumn() throws Exception {
+ // set shorter than painting_array.paintingTitle alias as some DBs doesn't support dot in alias
+ Property<String> paintingTitle = Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE).alias("paintingTitle");
+
+ String a = ObjectSelect.query(Artist.class)
+ .column(paintingTitle)
+ .orderBy(paintingTitle.asc())
+ .selectFirst(context);
+ assertNotNull(a);
+ assertEquals("painting1", a);
+ }
+
+ @Test
+ public void testSelectFirst_RelColumnWithFunction() throws Exception {
+ Property<String> altTitle = Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE)
+ .substring(7, 3).concat(" ", Artist.ARTIST_NAME)
+ .alias("altTitle");
+
+ String a = ObjectSelect.query(Artist.class)
+ .column(altTitle)
+ .where(altTitle.like("ng1%"))
+ .and(Artist.ARTIST_NAME.like("%ist1"))
+// .orderBy(altTitle.asc()) // unsupported for now
+ .selectFirst(context);
+ assertNotNull(a);
+ assertEquals("ng1 artist1", a);
+ }
+
+ /*
+ * Test iterated select
+ */
+
+ @Test
+ public void testIterationSingleColumn() throws Exception {
+ ColumnSelect<String> columnSelect = ObjectSelect.query(Artist.class).column(Artist.ARTIST_NAME);
+
+ final int[] count = new int[1];
+ columnSelect.iterate(context, new ResultIteratorCallback<String>() {
+ @Override
+ public void next(String object) {
+ count[0]++;
+ assertTrue(object.startsWith("artist"));
+ }
+ });
+
+ assertEquals(20, count[0]);
+ }
+
+ @Test
+ public void testBatchIterationSingleColumn() throws Exception {
+ ColumnSelect<String> columnSelect = ObjectSelect.query(Artist.class).column(Artist.ARTIST_NAME);
+
+ try(ResultBatchIterator<String> it = columnSelect.batchIterator(context, 10)) {
+ List<String> next = it.next();
+ assertEquals(10, next.size());
+ assertTrue(next.get(0).startsWith("artist"));
+ }
+ }
+
+ @Test
+ public void testIterationMultiColumns() throws Exception {
+ ColumnSelect<Object[]> columnSelect = ObjectSelect.query(Artist.class).columns(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH);
+
+ final int[] count = new int[1];
+ columnSelect.iterate(context, new ResultIteratorCallback<Object[]>() {
+ @Override
+ public void next(Object[] object) {
+ count[0]++;
+ assertTrue(object[0] instanceof String);
+ assertTrue(object[1] instanceof java.util.Date);
+ }
+ });
+
+ assertEquals(20, count[0]);
+ }
+
+ @Test
+ public void testBatchIterationMultiColumns() throws Exception {
+ ColumnSelect<Object[]> columnSelect = ObjectSelect.query(Artist.class).columns(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH);
+
+ try(ResultBatchIterator<Object[]> it = columnSelect.batchIterator(context, 10)) {
+ List<Object[]> next = it.next();
+ assertEquals(10, next.size());
+ assertTrue(next.get(0)[0] instanceof String);
+ assertTrue(next.get(0)[1] instanceof java.util.Date);
+ }
+ }
+
+ /*
+ * Test select with page size
+ */
+
+ @Test
+ public void testPageSizeOneScalar() {
+ List<String> a = ObjectSelect.query(Artist.class)
+ .column(Artist.ARTIST_NAME.trim())
+ .pageSize(10)
+ .select(context);
+ assertNotNull(a);
+ assertEquals(20, a.size());
+ int idx = 0;
+ for(String next : a) {
+ assertNotNull(""+idx, next);
+ idx++;
+ }
+ }
+
+ @Test
+ public void testPageSizeScalars() {
+ List<Object[]> a = ObjectSelect.query(Artist.class)
+ .columns(Artist.ARTIST_NAME.trim(), Artist.DATE_OF_BIRTH, Artist.PAINTING_ARRAY.count())
+ .pageSize(10)
+ .select(context);
+ assertNotNull(a);
+ assertEquals(5, a.size());
+ int idx = 0;
+ for(Object[] next : a) {
+ assertNotNull(next);
+ assertTrue("" + idx, next[0] instanceof String);
+ assertTrue("" + idx, next[1] instanceof java.util.Date);
+ assertTrue("" + idx, next[2] instanceof Long);
+ idx++;
+ }
+ }
+
+ @Test
+ public void testPageSizeOneObject() {
+ Property<Artist> artistFull = Property.createSelf(Artist.class);
+ List<Artist> a = ObjectSelect.query(Artist.class)
+ .column(artistFull)
+ .pageSize(10)
+ .select(context);
+ assertNotNull(a);
+ assertEquals(20, a.size());
+ for(Artist next : a){
+ assertNotNull(next);
+ }
+ }
+
+ @Test
+ public void testPageSizeObjectAndScalars() {
+ Property<Artist> artistFull = Property.createSelf(Artist.class);
+ List<Object[]> a = ObjectSelect.query(Artist.class)
+ .columns(Artist.ARTIST_NAME, artistFull, Artist.PAINTING_ARRAY.count())
+ .pageSize(10)
+ .select(context);
+ assertNotNull(a);
+ assertEquals(5, a.size());
+ int idx = 0;
+ for(Object[] next : a) {
+ assertNotNull(next);
+ assertEquals("" + idx, String.class, next[0].getClass());
+ assertEquals("" + idx, Artist.class, next[1].getClass());
+ assertEquals("" + idx, Long.class, next[2].getClass());
+ idx++;
+ }
+ }
+
+ @Test
+ public void testPageSizeObjects() {
+ Property<Artist> artistFull = Property.createSelf(Artist.class);
+ List<Object[]> a = ObjectSelect.query(Artist.class)
+ .columns(Artist.ARTIST_NAME, artistFull, Artist.PAINTING_ARRAY.flat(Painting.class))
+ .pageSize(10)
+ .select(context);
+ assertNotNull(a);
+ assertEquals(21, a.size());
+ int idx = 0;
+ for(Object[] next : a) {
+ assertNotNull(next);
+ assertEquals("" + idx, String.class, next[0].getClass());
+ assertEquals("" + idx, Artist.class, next[1].getClass());
+ assertEquals("" + idx, Painting.class, next[2].getClass());
+ idx++;
+ }
+ }
+
+ /*
+ * Test prefetch
+ */
+
+ @Test
+ public void testObjectColumnWithJointPrefetch() {
+ Property<Artist> artistFull = Property.createSelf(Artist.class);
+
+ List<Object[]> result = ObjectSelect.query(Artist.class)
+ .columns(artistFull, Artist.DATE_OF_BIRTH, Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE))
+ .prefetch(Artist.PAINTING_ARRAY.joint())
+ .select(context);
+
+ checkPrefetchResults(result);
+ }
+
+ @Test
+ public void testObjectColumnWithDisjointPrefetch() {
+ Property<Artist> artistFull = Property.createSelf(Artist.class);
+
+ List<Object[]> result = ObjectSelect.query(Artist.class)
+ .columns(artistFull, Artist.DATE_OF_BIRTH, Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE))
+ .prefetch(Artist.PAINTING_ARRAY.disjoint())
+ .select(context);
+
+ checkPrefetchResults(result);
+ }
+
+ @Test
+ public void testObjectColumnWithDisjointByIdPrefetch() {
+ Property<Artist> artistFull = Property.createSelf(Artist.class);
+
+ List<Object[]> result = ObjectSelect.query(Artist.class)
+ .columns(artistFull, Artist.DATE_OF_BIRTH, Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE))
+ .prefetch(Artist.PAINTING_ARRAY.disjointById())
+ .select(context);
+
+ checkPrefetchResults(result);
+ }
+
+ private void checkPrefetchResults(List<Object[]> result) {
+ assertEquals(21, result.size());
+ for(Object[] next : result) {
+ assertTrue(next[0] instanceof Artist);
+ assertTrue(next[1] instanceof java.util.Date);
+ assertTrue(next[2] instanceof String);
+ Artist artist = (Artist)next[0];
+ assertEquals(PersistenceState.COMMITTED, artist.getPersistenceState());
+
+ Object paintingsArr = artist.readPropertyDirectly(Artist.PAINTING_ARRAY.getName());
+ assertFalse(paintingsArr instanceof Fault);
+ assertTrue(((List)paintingsArr).size() > 0);
+ }
+ }
+
+ @Test
+ public void testAggregateColumnWithJointPrefetch() {
+ Property<Artist> artistFull = Property.createSelf(Artist.class);
+
+ List<Object[]> result = ObjectSelect.query(Artist.class)
+ .columns(artistFull, Artist.PAINTING_ARRAY.count())
+ .prefetch(Artist.PAINTING_ARRAY.joint())
+ .select(context);
+
+ checkAggregatePrefetchResults(result);
+ }
+
+ @Test
+ public void testAggregateColumnWithDisjointPrefetch() {
+ Property<Artist> artistFull = Property.createSelf(Artist.class);
+
+ List<Object[]> result = ObjectSelect.query(Artist.class)
+ .columns(artistFull, Artist.PAINTING_ARRAY.count())
+ .prefetch(Artist.PAINTING_ARRAY.disjoint())
+ .select(context);
+
+ checkAggregatePrefetchResults(result);
+ }
+
+ @Test
+ public void testAggregateColumnWithDisjointByIdPrefetch() {
+ Property<Artist> artistFull = Property.createSelf(Artist.class);
+
+ List<Object[]> result = ObjectSelect.query(Artist.class)
+ .columns(artistFull, Artist.PAINTING_ARRAY.count())
+ .prefetch(Artist.PAINTING_ARRAY.disjointById())
+ .select(context);
+
+ checkAggregatePrefetchResults(result);
+ }
+
+ private void checkAggregatePrefetchResults(List<Object[]> result) {
+ assertEquals(5, result.size());
+ for(Object[] next : result) {
+ assertTrue(next[0] instanceof Artist);
+ assertTrue(next[1] instanceof Long);
+ Artist artist = (Artist)next[0];
+ assertEquals(PersistenceState.COMMITTED, artist.getPersistenceState());
+
+ Object paintingsArr = artist.readPropertyDirectly(Artist.PAINTING_ARRAY.getName());
+ assertFalse(paintingsArr instanceof Fault);
+ assertTrue(((List)paintingsArr).size() == (long)next[1]);
+ }
+ }
+
+ @Test
+ public void testObjectSelectWithJointPrefetch() {
+ List<Artist> result = ObjectSelect.query(Artist.class)
+ .column(Property.createSelf(Artist.class))
+ .prefetch(Artist.PAINTING_ARRAY.joint())
+ .select(context);
+ assertEquals(20, result.size());
+
+ for(Artist artist : result) {
+ assertEquals(PersistenceState.COMMITTED, artist.getPersistenceState());
+
+ Object paintingsArr = artist.readPropertyDirectly(Artist.PAINTING_ARRAY.getName());
+ assertFalse(paintingsArr instanceof Fault);
+ }
+ }
+
+ @Test
+ public void testObjectWithDisjointPrefetch() {
+ List<Artist> result = ObjectSelect.query(Artist.class)
+ .column(Property.createSelf(Artist.class))
+ .prefetch(Artist.PAINTING_ARRAY.disjoint())
+ .select(context);
+ assertEquals(20, result.size());
+ for(Artist artist : result) {
+ assertEquals(PersistenceState.COMMITTED, artist.getPersistenceState());
+
+ Object paintingsArr = artist.readPropertyDirectly(Artist.PAINTING_ARRAY.getName());
+ assertFalse(paintingsArr instanceof Fault);
+ }
+ }
+
+ @Test
+ public void testObjectWithDisjointByIdPrefetch() {
+ List<Artist> result = ObjectSelect.query(Artist.class)
+ .column(Property.createSelf(Artist.class))
+ .prefetch(Artist.PAINTING_ARRAY.disjointById())
+ .select(context);
+ assertEquals(20, result.size());
+ for(Artist artist : result) {
+ assertEquals(PersistenceState.COMMITTED, artist.getPersistenceState());
+
+ Object paintingsArr = artist.readPropertyDirectly(Artist.PAINTING_ARRAY.getName());
+ assertFalse(paintingsArr instanceof Fault);
+ }
+ }
+
+ /*
+ * Test Persistent object select
+ */
+
+ @Test
+ public void testObjectColumn() {
+ Property<Artist> artistFull = Property.createSelf(Artist.class);
+
+ List<Object[]> result = ObjectSelect.query(Artist.class)
+ .columns(artistFull, Artist.ARTIST_NAME, Artist.PAINTING_ARRAY.count())
+ .select(context);
+ assertEquals(5, result.size());
+
+ for(Object[] next : result) {
+ assertTrue(next[0] instanceof Artist);
+ assertTrue(next[1] instanceof String);
+ assertTrue(next[2] instanceof Long);
+ assertEquals(PersistenceState.COMMITTED, ((Artist)next[0]).getPersistenceState());
+ }
+ }
+
+ @Test
+ public void testObjectColumnToOne() {
+ Property<Artist> artistFull = Property.create(ExpressionFactory.fullObjectExp(Painting.TO_ARTIST.getExpression()), Artist.class);
+ Property<Gallery> galleryFull = Property.create(ExpressionFactory.fullObjectExp(Painting.TO_GALLERY.getExpression()), Gallery.class);
+
+ List<Object[]> result = ObjectSelect.query(Painting.class)
+ .columns(Painting.PAINTING_TITLE, artistFull, galleryFull)
+ .select(context);
+ assertEquals(21, result.size());
+
+ for(Object[] next : result) {
+ assertTrue(next[0] instanceof String);
+ assertTrue(next[1] instanceof Artist);
+ assertTrue(next[2] instanceof Gallery);
+ assertEquals(PersistenceState.COMMITTED, ((Artist)next[1]).getPersistenceState());
+ }
+ }
+
+ @Test
+ public void testObjectColumnToOneAsObjPath() {
+
+ List<Object[]> result = ObjectSelect.query(Painting.class)
+ .columns(Painting.PAINTING_TITLE, Painting.TO_ARTIST, Painting.TO_GALLERY)
+ .select(context);
+ assertEquals(21, result.size());
+
+ for(Object[] next : result) {
+ assertTrue(next[0] instanceof String);
+ assertTrue(next[1] instanceof Artist);
+ assertTrue(next[2] instanceof Gallery);
+ assertEquals(PersistenceState.COMMITTED, ((Artist)next[1]).getPersistenceState());
+ }
+ }
+
+ @Test
+ public void testObjectColumnToMany() throws Exception {
+ Property<Artist> artist = Property.createSelf(Artist.class);
+
+ List<Object[]> result = ObjectSelect.query(Artist.class)
+ .columns(artist, Artist.PAINTING_ARRAY.flat(Painting.class), Artist.PAINTING_ARRAY.dot(Painting.TO_GALLERY))
+ .select(context);
+ assertEquals(21, result.size());
+
+ for(Object[] next : result) {
+ assertTrue(next[0] instanceof Artist);
+ assertTrue(next[1] instanceof Painting);
+ assertTrue(next[2] instanceof Gallery);
+ assertEquals(PersistenceState.COMMITTED, ((Artist)next[0]).getPersistenceState());
+ assertEquals(PersistenceState.COMMITTED, ((Painting)(next[1])).getPersistenceState());
+ assertEquals(PersistenceState.COMMITTED, ((Gallery)(next[2])).getPersistenceState());
+ }
+ }
+
+ @Test(expected = CayenneRuntimeException.class)
+ public void testDirectRelationshipSelect() {
+ // We should fail here as actual result will be just distinct paintings' ids.
+ List<List<Painting>> result = ObjectSelect.query(Artist.class)
+ .column(Artist.PAINTING_ARRAY).select(context);
+ assertEquals(21, result.size());
+ }
+
+ @Test(expected = CayenneRuntimeException.class)
+ public void testSelfPropertyInOrderBy() {
+ Property<Artist> artistProperty = Property.createSelf(Artist.class);
+ ObjectSelect.query(Artist.class)
+ .column(artistProperty)
+ .orderBy(artistProperty.desc())
+ .select(context);
+ }
+
+ @Test(expected = CayenneRuntimeException.class)
+ public void testSelfPropertyInWhere() {
+ Artist artist = ObjectSelect.query(Artist.class).selectFirst(context);
+ Property<Artist> artistProperty = Property.createSelf(Artist.class);
+ List<Artist> result = ObjectSelect.query(Artist.class)
+ .column(artistProperty)
+ .where(artistProperty.eq(artist))
+ .select(context);
+ }
+
+ @Test
+ public void testObjPropertyInWhere() {
+ Artist artist = ObjectSelect.query(Artist.class, Artist.ARTIST_NAME.eq("artist1"))
+ .selectFirst(context);
+ Property<Painting> paintingProperty = Property.createSelf(Painting.class);
+ List<Painting> result = ObjectSelect.query(Painting.class)
+ .column(paintingProperty)
+ .where(Painting.TO_ARTIST.eq(artist))
+ .select(context);
+ assertEquals(4, result.size());
+ }
+
}