/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* ResultDesc.java
*
* Created on March 3, 2000
*
*/
package com.sun.jdo.spi.persistence.support.sqlstore.sql;
import com.sun.jdo.api.persistence.support.JDOFatalInternalException;
import com.sun.jdo.spi.persistence.support.sqlstore.*;
import com.sun.jdo.spi.persistence.support.sqlstore.model.ClassDesc;
import com.sun.jdo.spi.persistence.support.sqlstore.model.FieldDesc;
import com.sun.jdo.spi.persistence.support.sqlstore.model.ForeignFieldDesc;
import com.sun.jdo.spi.persistence.support.sqlstore.model.LocalFieldDesc;
import com.sun.jdo.spi.persistence.support.sqlstore.sql.generator.ColumnRef;
import com.sun.jdo.spi.persistence.utility.StringHelper;
import com.sun.jdo.spi.persistence.utility.FieldTypeEnumeration;
import com.sun.jdo.spi.persistence.utility.I18NHelper;
import com.sun.jdo.spi.persistence.utility.logging.Logger;
import java.io.*;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.*;
/**
* This class is used by the store to materialize objects from a
* JDBC resultset. Each ResultDesc binds values from the ResultSet
* to instances of a persistence capable class.
*/
public class ResultDesc {
/** List of ResultFieldDesc/ResultDesc. */
private List fields;
/** List of field names corresponding to <code>fields</code>. */
private List fieldNames;
/** Class descriptor. */
private ClassDesc config;
/** Indicates whether this ResultDesc is prefetching relationship fields. */
private boolean prefetching;
/** Array of ForeignFieldDesc. Holds prefetched collection relationship fields. */
private ArrayList prefetchedCollectionFields;
/** The field that is the recipient of the value from this ResultDesc. */
private ForeignFieldDesc parentField;
/** Holds the projected local field. */
private ResultFieldDesc fieldProjection;
/** Result type for aggregate queries. */
private int aggregateResultType = FieldTypeEnumeration.NOT_ENUMERATED;
/** The logger. */
private static Logger logger = LogHelperSQLStore.getLogger();
/** I18N message handler. */
private final static ResourceBundle messages = I18NHelper.loadBundle(
"com.sun.jdo.spi.persistence.support.sqlstore.Bundle", // NOI18N
ResultDesc.class.getClassLoader());
private boolean debug;
public ResultDesc(ClassDesc config, int aggregateResultType) {
fields = new ArrayList();
fieldNames = new ArrayList();
this.config = config;
this.aggregateResultType = aggregateResultType;
}
/** Create and add a ResultFieldDesc for the given fieldDesc and columnRef.
* @param fieldDesc - the field descriptor for the field that is the recipient of
* the result value indicated by the columnRef.
* @param columnRef - indicates which column in the resultset contains the value.
* @param projection - indicates whether the column is a projection
*/
public void addField(LocalFieldDesc fieldDesc, ColumnRef columnRef,
boolean projection) {
ResultFieldDesc rfd = new ResultFieldDesc(fieldDesc, columnRef);
// remember the projection
if (projection) {
this.fieldProjection = rfd;
}
fields.add(rfd);
fieldNames.add(fieldDesc.getName());
}
private void addField(ResultDesc rs) {
if (rs != null) {
fields.add(rs);
fieldNames.add(null);
}
}
public void setPrefetching() {
prefetching = true;
}
/** Set the field that is the recipient of the result of this ResultDesc.
* @param parentField - field descriptor for the recipient field of the value
* of this ResultDesc.
*/
public void setParentField(ForeignFieldDesc parentField) {
this.parentField = parentField;
}
/**
* Get the value to be bound to the field described by <code>fieldDesc</code>
* from the result set. The conversion to the correct field type might be done
* by the driver. If we can't get the correct type from the driver, the
* conversion in done in FieldDesc::convertValue.
*
* @param resultData Result set from the database.
* @param columnRef columnRef for the field.
* @param fieldDesc Field descriptor of the field to be bound.
* @param sm State manager for the persistent object being bound.
* @return
* Object with the correct type defined in <code>fieldDesc</code>.
*/
private static Object getConvertedObject(ResultSet resultData,
ColumnRef columnRef,
FieldDesc fieldDesc,
StateManager sm) {
Object retVal = null;
try {
retVal = getValueFromResultSet(resultData, columnRef, fieldDesc.getEnumType());
// Create an SCO object in case we want to populate a pc.
if (retVal != null) {
// Create a SCO instance in case we want to populate a pc.
Object scoVal = createSCO(retVal, sm, fieldDesc);
if (scoVal != null) {
retVal = scoVal;
}
}
} catch (SQLException sqlException) {
//The driver is not able to convert for us
//We would use resultData.getObject(index) below
//and let FieldDesc::convertValue() do the conversion
//Nothing to do here
try {
// Get the generic object and let FieldDesc::convertValue() deal with it.
// This will return an SCO as needed.
retVal = fieldDesc.convertValue(resultData.getObject(columnRef.getIndex()), sm);
}
catch (Exception e) {
//Resolve : The original code was returning null and not throwing any
//exception in this case. Should we also do that????
logger.log(Logger.WARNING,"sqlstore.exception.log",e);
}
}
return retVal;
}
/**
* Gets value at index from resultData. resultData is queried for passed resultType.
* @param resultData The resultset object.
* @param columnRef columnRef for the field.
* @param resultType Type of expected result.
* @return value from <code>resultData</code> at <code>index</code>.
*/
private static Object getValueFromResultSet(ResultSet resultData,
ColumnRef columnRef,
int resultType) throws SQLException {
int index = columnRef.getIndex();
int columnType = columnRef.getColumnType();
return getValueFromResultSet(resultData, index, resultType, columnType);
}
/**
* Gets value at index from <code>resultData</code>.<code>resultData</code>
* is queried for passed resultType.
*
* @param resultData The resultset object.
* @param index Index at which result needs to be obtained.
* @param resultType Type of expected result.
* @return value from <code>resultData</code> at <code>index</code>.
*/
private static Object getValueFromResultSet(ResultSet resultData,
int index,
int resultType) throws SQLException {
// Types.OTHER is passed as a placeholder here.
// It implies don't care for columnType.
return getValueFromResultSet(resultData, index, resultType, Types.OTHER);
}
/**
* Gets value at index from resultData. resultData is queried for passed resultType.
* @param resultData The resultset object.
* @param index Index at which result needs to be obtained.
* @param resultType Type of expected result.
* @param columnType Types of column at index <code>index</code> as represented by
* java.sql.Types.
* @return value from <code>resultData</code> at <code>index</code>.
*/
private static Object getValueFromResultSet(ResultSet resultData,
int index,
int resultType,
int columnType) throws SQLException {
Object retVal = null;
try {
switch(resultType) {
case FieldTypeEnumeration.BOOLEAN_PRIMITIVE :
case FieldTypeEnumeration.BOOLEAN :
boolean booleanValue = resultData.getBoolean(index);
if(!resultData.wasNull() )
retVal = new Boolean(booleanValue);
break;
case FieldTypeEnumeration.CHARACTER_PRIMITIVE :
case FieldTypeEnumeration.CHARACTER :
String strValue = resultData.getString(index);
if(strValue != null)
retVal = FieldDesc.getCharFromString(strValue);
break;
case FieldTypeEnumeration.BYTE_PRIMITIVE :
case FieldTypeEnumeration.BYTE :
byte byteValue = resultData.getByte(index);
if(!resultData.wasNull() )
retVal = new Byte(byteValue);
break;
case FieldTypeEnumeration.SHORT_PRIMITIVE :
case FieldTypeEnumeration.SHORT :
short shortValue = resultData.getShort(index);
if(!resultData.wasNull() )
retVal = new Short(shortValue);
break;
case FieldTypeEnumeration.INTEGER_PRIMITIVE :
case FieldTypeEnumeration.INTEGER :
int intValue = resultData.getInt(index);
if(!resultData.wasNull() )
retVal = new Integer(intValue);
break;
case FieldTypeEnumeration.LONG_PRIMITIVE :
case FieldTypeEnumeration.LONG :
long longValue = resultData.getLong(index);
if(!resultData.wasNull() )
retVal = new Long(longValue);
break;
case FieldTypeEnumeration.FLOAT_PRIMITIVE :
case FieldTypeEnumeration.FLOAT :
float floatValue = resultData.getFloat(index);
if(!resultData.wasNull() )
retVal = new Float(floatValue);
break;
case FieldTypeEnumeration.DOUBLE_PRIMITIVE :
case FieldTypeEnumeration.DOUBLE :
double doubleValue = resultData.getDouble(index);
if(!resultData.wasNull() )
retVal = new Double(doubleValue);
break;
case FieldTypeEnumeration.BIGDECIMAL :
case FieldTypeEnumeration.BIGINTEGER :
retVal = resultData.getBigDecimal(index);
if ((resultType == FieldTypeEnumeration.BIGINTEGER) && (retVal != null)) {
retVal = ( (java.math.BigDecimal) retVal).toBigInteger();
}
break;
case FieldTypeEnumeration.STRING :
if(LocalFieldDesc.isCharLobType(columnType) ) {
Reader reader = resultData.getCharacterStream(index);
retVal = readCharacterStreamToString(reader);
} else {
retVal = resultData.getString(index);
}
break;
case FieldTypeEnumeration.SQL_DATE :
retVal = resultData.getDate(index);
break;
case FieldTypeEnumeration.SQL_TIME :
retVal = resultData.getTime(index);
break;
case FieldTypeEnumeration.UTIL_DATE :
case FieldTypeEnumeration.SQL_TIMESTAMP :
//Variable ts is introduced to avoid cast
Timestamp ts;
ts = resultData.getTimestamp(index);
if (resultType == FieldTypeEnumeration.UTIL_DATE && ts != null) {
retVal = new Date(ts.getTime());
} else {
retVal = ts;
}
break;
case FieldTypeEnumeration.ARRAY_BYTE_PRIMITIVE :
InputStream is = resultData.getBinaryStream(index);
retVal = readInputStreamToByteArray(is);
break;
case FieldTypeEnumeration.NOT_ENUMERATED :
//RESOLVE:
//We should only get here for getting values for hidden fields.
//hiddenFields does not have their java type initialized. Its sort of difficult
//to initialize java type without major re-org of the code in ClassDesc :(.
//But once it is done, we should throw an exception if we reach here.
//
//For now retrieve value for hidden fields as object as they are any way
//stored as Object in SQLStatemanager.
retVal = resultData.getObject(index);
break;
default :
//If we reach here, a new type has been added to FieldTypeEnumeration.
//Please update this method to handle new type.
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"sqlstore.resultdesc.unknownfieldtype",resultType) );
} //switch
} catch (SQLException e) {
if(logger.isLoggable(Logger.WARNING) ) {
Object items[] =
{ new Integer(index), new Integer(resultType), new Integer(columnType), e};
logger.log(Logger.WARNING,"sqlstore.resultdesc.errorgettingvalefromresulset",items);
}
throw e;
}
// RESOLVE: Following is a workaround till we are able to initialize java type for hidden fields.
// When we are able to determine java type of hidden fields, this code should go back
// to case FieldTypeEnumeration.String
if (LocalFieldDesc.isFixedCharType(columnType)
// For Character fields, this method is expected to return
// Character. Do not convert them to String.
&& resultType != FieldTypeEnumeration.CHARACTER_PRIMITIVE
&& resultType != FieldTypeEnumeration.CHARACTER
&& retVal != null) {
// To support char columns, we rtrim fields mapped to fixedchar.
retVal = StringHelper.rtrim(retVal.toString());
}
return retVal;
}
/**
* Creates a SCO corresponding to <code>value</code>.
* Currently used for dates. The actual SCO conversion for dates is done in
* {@link com.sun.jdo.spi.persistence.support.sqlstore.model.FieldDesc#createSCO(java.lang.Object, com.sun.jdo.spi.persistence.support.sqlstore.StateManager)}.
*
* @param value Value to be converted.
* @param sm StateManager of the persistent object being populated.
* @param fieldDesc Field being bound.
* @return New SCO instance, null if no SCO was created.
*/
private static Object createSCO(Object value, StateManager sm, FieldDesc fieldDesc) {
Object retVal = null;
if (fieldDesc != null) {
int enumType = fieldDesc.getEnumType();
// Need to convert Date fields into their SCO equivalents
switch(enumType) {
case FieldTypeEnumeration.UTIL_DATE:
case FieldTypeEnumeration.SQL_DATE:
case FieldTypeEnumeration.SQL_TIME:
case FieldTypeEnumeration.SQL_TIMESTAMP:
retVal = fieldDesc.createSCO(value, sm);
break;
default:
}
}
return retVal;
}
/**
* Reads from input stream <CODE>is</CODE> into a byte array.
*
* @param is Input stream obtained from the database.
* @return A byte array read from the input stream.
* @see java.sql.ResultSet#getBinaryStream(int)
*/
private static byte[] readInputStreamToByteArray(InputStream is) {
byte[] byteArray = null;
if (is != null) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] chunk = new byte[2000];
int read = 0;
try {
while ((read = is.read(chunk)) != -1) {
bos.write(chunk, 0, read);
}
byteArray = bos.toByteArray();
} catch (IOException e) {
// log the exception and don't return any value
// Eating the exception here. As the caller also does not
// know how to deal with this exception.
logger.log(Logger.WARNING,"sqlstore.exception.log",e);
}
}
return byteArray;
}
/**
* Reads from the character stream <code>reader</code> into a String.
*
* @param reader Reader obtained from the database.
* @return A String read from the reader.
* @see java.sql.ResultSet#getCharacterStream(int)
*/
private static String readCharacterStreamToString(Reader reader) {
String retVal = null;
if(reader != null) {
BufferedReader buffReader = new BufferedReader(reader);
StringBuffer buff = new StringBuffer();
try {
int charRead;
while( (charRead = buffReader.read() ) != -1) {
buff.append( (char)charRead );
}
} catch (IOException e) {
// log the exception and don't return any value
// Eating the exception here. As the caller also does not
// know how to deal with this exception.
logger.log(Logger.WARNING,"sqlstore.exception.log",e);
}
retVal = buff.toString();
}
return retVal;
}
/**
* Materialize data from the result set into objects.
*
* @param pm - the PersistenceManager responsible for instantiating objects
* @param resultData - JDBC ResultSet containing the data to be materialized
* @return
* Collection containing the resulting objects. For aggregate queries,
* the returned object type is specified by the caller.
*/
public Object getResult(PersistenceManager pm, ResultSet resultData) throws SQLException {
Object result = null;
debug = logger.isLoggable(Logger.FINEST);
if (!isAggregate()) {
Collection resultCollection = new ArrayList();
// Fill in the data from the current row of resultData.
while (resultData.next()) {
Object resultObject = null;
if (fieldProjection != null) {
resultObject = getProjectedField(resultData);
} else {
resultObject = setFields(pm, resultData);
}
// resultCollection might contain resultObject if prefetch
// is enabled. Do not add duplicates. Duplicates are required
// for projection queries
if (!prefetching || !resultCollection.contains(resultObject)) {
resultCollection.add(resultObject);
}
}
//Iterate over the results obtained and handle deferred collection updates.
applyDeferredUpdatesToPrefetchedCollections(resultCollection);
result = resultCollection;
} else {
// Aggregate functions return an object instead of a collection.
result = getAggregateResult(resultData);
}
return result;
}
/**
* The prefetched collection fields might have deferred updates for
* @param resultCollection
*/
private void applyDeferredUpdatesToPrefetchedCollections(Collection resultCollection) {
if (prefetching && prefetchedCollectionFields != null && prefetchedCollectionFields.size() > 0) {
for (Iterator resultItr = resultCollection.iterator(); resultItr.hasNext();) {
// resultPC is guranted to be instance of PersistenceCapable
PersistenceCapable resultPC = (PersistenceCapable) resultItr.next();
// resultPC can be null if this is a projection query
if(resultPC != null) {
SQLStateManager sm = (SQLStateManager)resultPC.jdoGetStateManager();
Iterator prefetchedCollectionFieldsIter = prefetchedCollectionFields.iterator();
StateManager resultSM = resultPC.jdoGetStateManager();
while (prefetchedCollectionFieldsIter.hasNext()) {
ForeignFieldDesc prefetchedCollectionField =
(ForeignFieldDesc) prefetchedCollectionFieldsIter.next();
// process deferred updates for all prefetched collection relationship
if(prefetchedCollectionField.cardinalityUPB > 1) {
SCOCollection relationshipValue =
(SCOCollection) prefetchedCollectionField.getValue(sm);
if(relationshipValue.isDeferred()){
relationshipValue.applyDeferredUpdates(null);
}
// Presence mask bit for a prefetched collection field is
// not set in setFields. Set the bit here.
resultSM.setPresenceMaskBit(prefetchedCollectionField.absoluteID);
}
}
}
}
}
}
/**
* Get result for Aggregates. Since resultset containing result for aggregates would not
* contain any other columns, it is assumed that the result is available at index == 1.
* @param resultData The resultset from which result is to be extracted.
*/
private Object getAggregateResult(ResultSet resultData) throws SQLException {
Object result = null;
if (resultData.next() ) {
//Aggregate results are always at index 1;
result = getValueFromResultSet(resultData, 1, aggregateResultType);
}
return result;
}
/**
* Returns the projected field from the result set. This field is
* always a local field. Foreign fields are handled in setFields.
*
* We return the database value for projections on local fields.
* Unless we flush for queries in optimistic transactions the value
* from the database might be different from the value in memory.
*
* @param resultData The SQL result set.
* @return
* The projected value from the result set. This might be a local field
* or the result of an aggregate query.
* @see com.sun.jdo.spi.persistence.support.sqlstore.sql.ResultDesc#setFields(PersistenceManager, ResultSet)
*/
private Object getProjectedField(ResultSet resultData) {
//Field projection can never be null if this method gets called.
FieldDesc f = fieldProjection.getFieldDesc();
if (debug) {
logger.finest("sqlstore.resultdesc.returning_field", f.getName()); // NOI18N
}
return getConvertedObject(resultData, fieldProjection.getColumnRef(), f, null);
}
/**
* Bind the columns from this ResultSet row to the persistent object described
* by this ResultDesc. External queries always return only one type of objects
* and don't have nested ResultDescs. Internal queries can have nested ResultDescs.
* Run through all the fields of the field list and bind the values in
* that order. Nested ResultDescs are processed by recursive calls.
*
* @param pm The PersistenceManager responsible for instantiating objects.
* @param resultData JDBC ResultSet containing the data to be materialized.
* @return
* Persistent object corresponding to values from ResultSet row, can be null.
*/
private Object setFields(PersistenceManager pm, ResultSet resultData) {
Object pcObj = null;
// Get the Statemanager corresponding to the current row
SQLStateManager sm = (SQLStateManager) findOrCreateStateManager(resultData, pm);
if (sm != null) {
pcObj = sm.getPersistent();
sm.getLock();
try {
// Fields are read in the order in which they were placed in
// the sql select statement. This ordering is important while reading
// from streams corresponding to LONG columns on Oracle.
for (int i = 0; i < fields.size(); i++) {
Object temp = fields.get(i);
if (temp instanceof ResultFieldDesc) {
ResultFieldDesc rfd = (ResultFieldDesc) temp;
LocalFieldDesc f = rfd.getFieldDesc();
if (!sm.getPresenceMaskBit(f.absoluteID)) {
Object value = getConvertedObject(resultData, rfd.getColumnRef(), f, sm);
if (debug) {
logger.finest("sqlstore.resultdesc.marking_field", f.getName()); // NOI18N
}
// Set the field value and presence mask bit.
setFieldValue(sm, f, value);
}
} else {
ResultDesc frd = (ResultDesc) temp;
ForeignFieldDesc parentField = frd.parentField;
// Only try to fetch the field if it is not already present.
// If the field is already present, it should be in
// consistent state w.r.t. this transaction. Overwriting
// it with the value from database might corrupt consistency of data.
if (!sm.getPresenceMaskBit(parentField.absoluteID)) {
Object fobj = frd.setFields(pm, resultData);
if(parentField.cardinalityUPB > 1) { // parentField is a collection.
SCOCollection collection = (SCOCollection) parentField.getValue(sm);
if (collection == null) {
// Getting first member of the collection. Initialize the collection.
// Presence mask bit for a collection field can not set here.
// This is because the collection is not fully present till all the
// elements of the collection are added. If the bit is set here,
// next member of this collection from this resultset will not
// be processed(added) here.
// This bit is set after all the elements of this collection are
// processed. That is in applyDeferredUpdatesToPrefetchedCollections()
sm.replaceCollection(parentField, null);
// Get the newly created SCOCollection back.
collection = (SCOCollection) parentField.getValue(sm);
}
if(fobj != null ) {
collection.addToBaseCollection(fobj);
}
} else { // parentField is an object.
// Set the field value and presence mask bit.
setFieldValue(sm, parentField, fobj);
}
}
if (debug) {
logger.finest("sqlstore.resultdesc.marking_foreign_field", // NOI18N
parentField.getName());
}
}
}
sm.initialize(true);
} finally {
// Always release the lock.
sm.releaseLock();
}
} else {
// sm can be null if we can not find or create a statemanager from the result data.
// This is possible if we are projecting on a foreignfield and there is no
// result returned.
}
return pcObj;
}
/**
* Set given <code>value</code> for the given field <code>f</code> for
* given statemanager <code>sm</code>.
* Also set presence mask bit for the field in given <code>sm</code>.
* @param sm Given StateManager
* @param f Given field
* @param value Given value.
*/
private static void setFieldValue(StateManager sm, FieldDesc f, Object value) {
f.setValue(sm, value);
sm.setPresenceMaskBit(f.absoluteID);
}
/**
* Specifies, if this was an aggregate query.
*/
private boolean isAggregate() {
return aggregateResultType != FieldTypeEnumeration.NOT_ENUMERATED;
}
/**
* Returns a StateManager which PC instance to be populated with the values.
* If such instance exists in this PersistenceManager cache,
* it is returned, otherwise a new instance is created.
*/
private StateManager findOrCreateStateManager(ResultSet resultData,
PersistenceManager pm) {
try {
Class oidClass = config.getOidClass();
Object oid = oidClass.newInstance();
// Copy key field values
Field keyFields[] = config.getKeyFields();
String keyNames[] = config.getKeyFieldNames();
for (int i = 0; i < keyFields.length; i++) {
Field keyField = keyFields[i];
String keyName = keyNames[i];
FieldDesc fd = config.getField(keyName);
int index = fieldNames.indexOf(keyName);
ResultFieldDesc rfd = (ResultFieldDesc)fields.get(index);
Object v = getConvertedObject(resultData, rfd.getColumnRef(), fd, null);
if (debug) {
logger.finest("sqlstore.resultdesc.marking_key_field",keyName); // NOI18N
}
if (v == null ) {
return null;
}
keyField.set(oid, v);
}
return pm.findOrCreateStateManager(oid, config.getPersistenceCapableClass());
} catch (Exception e) {
// RESOLVE...
throw new JDOFatalInternalException(e.getMessage());
}
}
/**
* Joins foreignResult with this resultDesc
* @param foreignResult the foreign ResultDesc
* @param parentField parentField for the foreind ResultDesc
*/
public void doJoin(ResultDesc foreignResult, ForeignFieldDesc parentField) {
addField(foreignResult);
foreignResult.parentField = parentField;
// if foreign result correponds to a collection relationship being
// prefetched, remember it.
if(parentField.cardinalityUPB > 1) {
if(prefetchedCollectionFields == null) {
prefetchedCollectionFields = new ArrayList();
}
prefetchedCollectionFields.add(parentField);
}
}
}
|