Package org.datanucleus.store.mapped.scostore

Source Code of org.datanucleus.store.mapped.scostore.FKListStore

/**********************************************************************
Copyright (c) 2004 Erik Bengtson and others. All rights reserved.
Licensed 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.

Contributors:
2003 Andy Jefferson - updated retrieval of ColumnList's
2003 Andy Jefferson - updated to support inherited elements
2003 Andy Jefferson - fixed sizeStmt to ignore records with no index value
2004 Andy Jefferson - merged IteratorStmt and GetStmt into GetRangeStmt.
2004 Andy Jefferson - moved majority statement construction to AbstractListStore.
2004 Andy Jefferson - fixed use of shift statement
2004 Andy Jefferson - added support for inverse 1-N unidirectional
2005 Andy Jefferson - added dependent-element when removed from collection
    ...
**********************************************************************/
package org.datanucleus.store.mapped.scostore;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.ClassNameConstants;
import org.datanucleus.FetchPlan;
import org.datanucleus.ManagedConnection;
import org.datanucleus.ObjectManager;
import org.datanucleus.StateManager;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.CollectionMetaData;
import org.datanucleus.metadata.Relation;
import org.datanucleus.store.FieldValues;
import org.datanucleus.store.mapped.DatastoreClass;
import org.datanucleus.store.mapped.DatastoreContainerObject;
import org.datanucleus.store.mapped.DatastoreIdentifier;
import org.datanucleus.store.mapped.MappedStoreManager;
import org.datanucleus.store.mapped.exceptions.MappedDatastoreException;
import org.datanucleus.store.mapped.expression.LogicSetExpression;
import org.datanucleus.store.mapped.expression.QueryExpression;
import org.datanucleus.store.mapped.expression.ScalarExpression;
import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
import org.datanucleus.store.mapped.mapping.MappingConsumer;
import org.datanucleus.store.mapped.query.IncompatibleQueryElementTypeException;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

/**
* Representation of a List using a ForeignKey to form a relationship. This class is
* used where you have a 1-N and the tables are not joined via a join table.
* There is an owner table and an element table, and the element table has a
* column being the id of the owner table. This is in contrast to
* NormalListStore which represents 1-N relationships using a join table.
* There are 2 possible uses here
* <UL>
* <LI><B>bidirectional</B> - where the owner has a List of elements, and
* the element has an owner. In this case the element class will have an owner field
* and index which can be updated directly</LI>
* <LI><B>unidirectional</B> - where the owner has a List of elements, but
* the element knows nothing about the owner. In this case the element class has no
* owner field. In this case the owner/index columns in the element table have to be
* updated directly.</LI>
* </UL>
*/
public abstract class FKListStore extends AbstractListStore
{
    /** Field number of owner link in element class. */
    private final int ownerFieldNumber;

    /**
     * Constructor.
     * @param fmd Field MetaData for the field that this represents
     * @param storeMgr The Store Manager in use
     * @param clr The ClassLoaderResolver
     * @param specialization The datastore-specific specialization
     */
    public FKListStore(AbstractMemberMetaData fmd, MappedStoreManager storeMgr, ClassLoaderResolver clr,
            FKListStoreSpecialization specialization)
    {
        super(storeMgr, clr, specialization);

        setOwner(fmd, clr);
        CollectionMetaData colmd = fmd.getCollection();
        if (colmd == null)
        {
            throw new NucleusUserException(LOCALISER.msg("056001", fmd.getFullFieldName()));
        }

        // Load the element class
        elementType = colmd.getElementType();
        Class element_class = clr.classForName(elementType);

        if (ClassUtils.isReferenceType(element_class))
        {
            // Take the metadata for the first implementation of the reference type
            emd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForImplementationOfReference(element_class,null,clr);
            if (emd != null)
            {
                // Pretend we have a relationship with this one implementation
                elementType = emd.getFullClassName();
            }
        }
        else
        {
            // Check that the element class has MetaData
            emd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForClass(element_class, clr);
        }
        if (emd == null)
        {
            throw new NucleusUserException(LOCALISER.msg("056003", element_class.getName(), fmd.getFullFieldName()));
        }

        elementInfo = getElementInformationForClass();
        if (elementInfo != null && elementInfo.length > 1)
        {
            throw new NucleusUserException(LOCALISER.msg("056031",
                ownerMemberMetaData.getFullFieldName()));
        }

        elementMapping = elementInfo[0].getDatastoreClass().getIDMapping(); // Just use the first element type as the guide for the element mapping
        elementsAreEmbedded = false; // Can't embed element when using FK relation
        elementsAreSerialised = false; // Can't serialise element when using FK relation

        // Get the field in the element table (if any)
        String mappedByFieldName = fmd.getMappedBy();
        if (mappedByFieldName != null)
        {
            // 1-N FK bidirectional
            // The element class has a field for the owner.
            AbstractMemberMetaData eofmd = emd.getMetaDataForMember(mappedByFieldName);
            if (eofmd == null)
            {
                throw new NucleusUserException(LOCALISER.msg("056024", fmd.getFullFieldName(),
                    mappedByFieldName, element_class.getName()));
            }

            // Check that the type of the element "mapped-by" field is consistent with the owner type
            if (!clr.isAssignableFrom(eofmd.getType(), fmd.getAbstractClassMetaData().getFullClassName()))
            {
                throw new NucleusUserException(LOCALISER.msg("056025", fmd.getFullFieldName(),
                    eofmd.getFullFieldName(), eofmd.getTypeName(), fmd.getAbstractClassMetaData().getFullClassName()));
            }

            String ownerFieldName = eofmd.getName();
            ownerFieldNumber = emd.getAbsolutePositionOfMember(ownerFieldName);
            ownerMapping = elementInfo[0].getDatastoreClass().getMemberMapping(eofmd);
            if (ownerMapping == null)
            {
                throw new NucleusUserException(LOCALISER.msg("056029",
                    fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), elementType, ownerFieldName));
            }
            if (isEmbeddedMapping(ownerMapping))
            {
                throw new NucleusUserException(LOCALISER.msg("056026",
                    ownerFieldName, elementType, eofmd.getTypeName(), fmd.getClassName()));
            }
        }
        else
        {
            // 1-N FK unidirectional
            // The element class knows nothing about the owner (but its table has external mappings)
            ownerFieldNumber = -1;
            ownerMapping = elementInfo[0].getDatastoreClass().getExternalMapping(fmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
            if (ownerMapping == null)
            {
                throw new NucleusUserException(LOCALISER.msg("056030",
                    fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), elementType));
            }
        }

        orderMapping = elementInfo[0].getDatastoreClass().getExternalMapping(fmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_INDEX);
        if (fmd.getOrderMetaData() != null && !fmd.getOrderMetaData().isIndexedList())
        {
            indexedList = false;
        }
        if (orderMapping == null && indexedList)
        {
            // "Indexed List" but no order mapping present!
            throw new NucleusUserException(LOCALISER.msg("056041",
                fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), elementType));
        }

        relationDiscriminatorMapping =
            elementInfo[0].getDatastoreClass().getExternalMapping(fmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK_DISCRIM);
        if (relationDiscriminatorMapping != null)
        {
            relationDiscriminatorValue = fmd.getValueForExtension("relation-discriminator-value");
            if (relationDiscriminatorValue == null)
            {
                // No value defined so just use the field name
                relationDiscriminatorValue = fmd.getFullFieldName();
            }
        }

        // TODO Cater for multiple element tables
        containerTable = elementInfo[0].getDatastoreClass();
        if (fmd.getMappedBy() != null && ownerMapping.getDatastoreContainer() != containerTable)
        {
            // Element and owner don't have consistent tables so use the one with the mapping
            // e.g collection is of subclass, yet superclass has the link back to the owner
            containerTable = ownerMapping.getDatastoreContainer();
        }
    }

    private FKListStoreSpecialization getSpecialization()
    {
        return (FKListStoreSpecialization) specialization;
    }

    /**
     * Method to set an object in the List at a position.
     * @param sm The state manager
     * @param index The item index
     * @param element What to set it to.
     * @param allowDependentField Whether to enable dependent-field deletes during the set
     * @return The value before setting.
     */
    public Object set(StateManager sm, int index, Object element, boolean allowDependentField)
    {
        validateElementForWriting(sm, element, -1); // Last argument means dont set the position on any INSERT
        Object o = get(sm, index);
        return getSpecialization().set(sm, index, element, allowDependentField, this, o);
    }

    /**
     * Utility to update a foreign-key in the element in the case of a unidirectional 1-N relationship.
     * @param sm StateManager for the owner
     * @param element The element to update
     * @param owner The owner object to set in the FK
     * @param index The index position (or -1 if not known)
     * @return Whether it was performed successfully
     */
    private boolean updateElementFk(StateManager sm, Object element, Object owner, int index)
    {
        if (element == null)
        {
            return false;
        }

        ObjectManager om = sm.getObjectManager();
        return getSpecialization().updateElementFk(sm, element, owner, index, om, this);
    }

    /**
     * Method to update the collection to be the supplied collection of elements.
     * @param sm StateManager of the object
     * @param coll The collection to use
     */
    public void update(StateManager sm, Collection coll)
    {
        if (coll == null || coll.isEmpty())
        {
            clear(sm);
            return;
        }

        // Find existing elements, and remove any that are no longer present
        Collection existing = new ArrayList();
        Iterator elemIter = iterator(sm);
        while (elemIter.hasNext())
        {
            Object elem = elemIter.next();
            if (!coll.contains(elem))
            {
                remove(sm, elem, -1, true);
            }
            else
            {
                existing.add(elem);
            }
        }

        if (existing.equals(coll))
        {
            // Existing (after any removals) is same as the specified so job done
            return;
        }

        // TODO Improve this - need to allow for list element position changes etc
        clear(sm);
        addAll(sm, coll, 0);
    }

    /**
     * Internal method for adding an item to the List.
     * @param sm The state manager
     * @param startAt The start position
     * @param atEnd Whether to add at the end
     * @param c The Collection of elements to add.
     * @param size Current size of list (if known). -1 if not known
     * @return Whether it was successful
     */
    protected boolean internalAdd(StateManager sm, int startAt, boolean atEnd, Collection c, int size)
    {
        if (c == null || c.size() == 0)
        {
            return true;
        }

        // Check what we have persistent already
        int currentListSize = 0;
        if (size < 0)
        {
            // Get the current size from the datastore
            currentListSize = size(sm);
        }
        else
        {
            currentListSize = size;
        }

        boolean shiftingElements = true;
        if (atEnd || startAt == currentListSize)
        {
            shiftingElements = false;
            startAt = currentListSize; // Not shifting so we insert from the end
        }

        boolean elementsNeedPositioning = false;
        int position = startAt;
        Iterator elementIter = c.iterator();
        while (elementIter.hasNext())
        {
            // Persist any non-persistent objects optionally at their final list position (persistence-by-reachability)
            if (shiftingElements)
            {
                // We have to shift things so dont bother with positioning
                position = -1;
            }

            boolean inserted = validateElementForWriting(sm, elementIter.next(), position);
            if (!inserted || shiftingElements)
            {
                // This element wasnt positioned in the validate so we need to set the positions later
                elementsNeedPositioning = true;
            }
            if (!shiftingElements)
            {
                position++;
            }
        }

        if (shiftingElements)
        {
            // We need to shift existing elements before positioning the new ones
            try
            {
                // Calculate the amount we need to shift any existing elements by
                // This is used where inserting between existing elements and have to shift down all elements after the start point
                int shift = c.size();

                ObjectManager om = sm.getObjectManager();
                ManagedConnection mconn = storeMgr.getConnection(om);
                try
                {
                    // shift up existing elements after start position by "shift"
                    for (int i=currentListSize-1; i>=startAt; i--)
                    {
                        // Shift the index of this row by "shift"
                        getSpecialization().internalShift(sm, mconn, false, i, shift, true, this
                        );
                    }
                }
                finally
                {
                    mconn.release();
                }
            }
            catch (MappedDatastoreException e)
            {
                // An error was encountered during the shift process so abort here
                throw new NucleusDataStoreException(LOCALISER.msg("056009", e.getMessage()), e.getCause());
            }
        }

        if (shiftingElements || elementsNeedPositioning)
        {
            // Some elements have been shifted so the new elements need positioning now, or we already had some
            // of the new elements persistent and so they need their positions setting now
            elementIter = c.iterator();
            while (elementIter.hasNext())
            {
                Object element = elementIter.next();
                updateElementFk(sm, element, sm.getObject(), startAt);
                startAt++;
            }
        }

        return true;
    }


    /**
     * Convenience method to remove the specified element from the List.
     * @param sm StateManager of the owner
     * @param element The element
     * @return Whether the List was modified
     */
    protected boolean internalRemove(StateManager sm, Object element, int size)
    {
        if (indexedList)
        {
            // Indexed List
            // The element can be at one position only (no duplicates allowed in FK list)
            int index = indexOf(sm, element);
            if (index == -1)
            {
                return false;
            }
            removeAt(sm, index, size);
        }
        else
        {
            // Ordered List - no index so null the FK (if nullable) or delete the element
            if (ownerMapping.isNullable())
            {
                // Nullify the FK
                ObjectManager om = sm.getObjectManager();
                StateManager elementSM = om.findStateManager(element);
                if (relationType == Relation.ONE_TO_MANY_BI &&
                    om.getOMFContext().getPersistenceConfiguration().getBooleanProperty("datanucleus.manageRelationships"))
                {
                    // TODO Move this into manageRemovalOfElement
                    elementSM.replaceField(ownerMemberMetaData.getRelatedMemberMetaData(clr)[0].getAbsoluteFieldNumber(),
                        null, true);
                    if (sm.getObjectManager().isFlushing())
                    {
                        elementSM.flush();
                    }
                }
                // TODO Shouldn't we always null the FK in the datastore, not just when unidirectional?
                else
                {
                    updateElementFk(sm, element, null, -1);
                }
            }
            else
            {
                // Delete the element
                // TODO Log this
                sm.getObjectManager().deleteObjectInternal(element);
            }
        }

        return true;
    }

    /**
     * Convenience method to manage the removal of an element from the collection, performing
     * any necessary "managed relationship" updates when the field is bidirectional.
     * @param ownerSM StateManager for the collection owner
     * @param element The element
     */
    protected void manageRemovalOfElement(StateManager ownerSM, Object element)
    {
        // TODO Complete this
        /*ObjectManager om = ownerSM.getObjectManager();
        if (relationType == Relation.ONE_TO_MANY_BI &&
            om.getOMFContext().getPersistenceConfiguration().getBooleanProperty("datanucleus.manageRelationships"))
        {
            // Managed Relations : 1-N bidirectional so null the owner on the elements
            if (!om.getApiAdapter().isDeleted(element))
            {
                StateManager elementSM = om.findStateManager(element);
                if (elementSM != null)
                {
                    // Null the owner of the element
                    if (NucleusLogger.PERSISTENCE.isDebugEnabled())
                    {
                        NucleusLogger.PERSISTENCE.debug(
                            LOCALISER.msg("055010",
                                StringUtils.toJVMIDString(ownerSM.getObject()),
                                ownerMemberMetaData.getFullFieldName(),
                                StringUtils.toJVMIDString(element)));
                    }

                    elementSM.replaceField(getFieldNumberInElementForBidirectional(elementSM), null, true);
                    if (om.isFlushing())
                    {
                        // Make sure this change gets flushed
                        elementSM.flush();
                    }
                }
            }
        }*/
    }

    /**
     * Internal method to remove an object at a location in the List.
     * Differs from the JoinTable List in that it nulls out the owner FK.
     * @param sm The state manager.
     * @param index The location
     * @param size Current size of list (if known). -1 if not known
     */
    protected void removeAt(StateManager sm, int index, int size)
    {
        if (!indexedList)
        {
            throw new NucleusUserException("Cannot remove an element from a particular position with an ordered list since no indexes exist");
        }

        boolean nullify = false;
        if (ownerMapping.isNullable() && orderMapping != null && orderMapping.isNullable())
        {
            NucleusLogger.DATASTORE.debug(LOCALISER.msg("056043"));
            nullify = true;
        }
        else
        {
            NucleusLogger.DATASTORE.debug(LOCALISER.msg("056042"));
        }
        getSpecialization().removeAt(sm, index, size, nullify, this);
    }

    /**
     * Method to clear the List.
     * This is called by the List.clear() method, or when the container object is being deleted
     * and the elements are to be removed (maybe for dependent field), or also when updating a Collection
     * and removing all existing prior to adding all new.
     * @param ownerSM The state manager
     */
    public void clear(StateManager ownerSM)
    {
        boolean deleteElements = false;
        ObjectManager om = ownerSM.getObjectManager();
        if (ownerMemberMetaData.getCollection().isDependentElement())
        {
            // Elements are dependent and can't exist on their own, so delete them all
            NucleusLogger.DATASTORE.debug(LOCALISER.msg("056034"));
            deleteElements = true;
        }
        else
        {
            if (ownerMapping.isNullable() && orderMapping == null)
            {
                // Field is not dependent, and nullable so we null the FK
                NucleusLogger.DATASTORE.debug(LOCALISER.msg("056036"));
                deleteElements = false;
            }
            else if (ownerMapping.isNullable() && orderMapping != null && orderMapping.isNullable())
            {
                // Field is not dependent, and nullable so we null the FK
                NucleusLogger.DATASTORE.debug(LOCALISER.msg("056036"));
                deleteElements = false;
            }
            else
            {
                // Field is not dependent, and not nullable so we just delete the elements
                NucleusLogger.DATASTORE.debug(LOCALISER.msg("056035"));
                deleteElements = true;
            }
        }

        if (deleteElements)
        {
            // Find elements present in the datastore and delete them one-by-one
            Iterator elementsIter = iterator(ownerSM);
            if (elementsIter != null)
            {
                while (elementsIter.hasNext())
                {
                    Object element = elementsIter.next();
                    if (om.getApiAdapter().isPersistable(element) && om.getApiAdapter().isDeleted(element))
                    {
                        // Element is waiting to be deleted so flush it (it has the FK)
                        StateManager objSM = om.findStateManager(element);
                        objSM.flush();
                    }
                    else
                    {
                        // Element not yet marked for deletion so go through the normal process
                        om.deleteObjectInternal(element);
                    }
                }
            }
        }
        else
        {
            getSpecialization().clearWithoutDelete(om, ownerSM, this);
        }
    }

    /**
     * Method to validate that an element is valid for writing to the datastore.
     * TODO Minimise differences to super.validateElementForWriting()
     * @param sm StateManager for the List
     * @param element The element to validate
     * @param index The position that the element is being stored at in the list
     * @return Whether the element was inserted
     */
    protected boolean validateElementForWriting(final StateManager sm, Object element, final int index)
    {
        final Object newOwner = sm.getObject();

        // Check if element is ok for use in the datastore, specifying any external mappings that may be required
        boolean inserted = super.validateElementForWriting(sm, element, new FieldValues()
        {
            public void fetchFields(StateManager esm)
            {
                // Find the (element) table storing the FK back to the owner
                boolean isPersistentInterface = storeMgr.getOMFContext().getMetaDataManager().isPersistentInterface(elementType);
                DatastoreClass elementTable = null;
                if (isPersistentInterface)
                {
                    elementTable = storeMgr.getDatastoreClass(
                        storeMgr.getOMFContext().getMetaDataManager().getImplementationNameForPersistentInterface(elementType), clr);
                }
                else
                {
                    elementTable = storeMgr.getDatastoreClass(elementType, clr);
                }
                if (elementTable == null)
                {
                    // "subclass-table", persisted into table of other class
                    AbstractClassMetaData[] managingCmds = storeMgr.getClassesManagingTableForClass(emd, clr);
                    if (managingCmds != null && managingCmds.length > 0)
                    {
                        // Find which of these subclasses is appropriate for this element
                        for (int i=0;i<managingCmds.length;i++)
                        {
                            Class tblCls = clr.classForName(managingCmds[i].getFullClassName());
                            if (tblCls.isAssignableFrom(esm.getObject().getClass()))
                            {
                                elementTable = storeMgr.getDatastoreClass(managingCmds[i].getFullClassName(), clr);
                                break;
                            }
                        }
                    }
                }

                if (elementTable != null)
                {
                    JavaTypeMapping externalFKMapping = elementTable.getExternalMapping(ownerMemberMetaData, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
                    if (externalFKMapping != null)
                    {
                        // The element has an external FK mapping so set the value it needs to use in the INSERT
                        esm.setAssociatedValue(externalFKMapping, sm.getObject());
                    }
                    if (relationDiscriminatorMapping != null)
                    {
                        esm.setAssociatedValue(relationDiscriminatorMapping, relationDiscriminatorValue);
                    }
                    if (orderMapping != null && index >= 0)
                    {
                        if (ownerMemberMetaData.getOrderMetaData() != null && ownerMemberMetaData.getOrderMetaData().getMappedBy() != null)
                        {
                            // Order is stored in a field in the element so update it
                            // We support mapped-by fields of types int/long/Integer/Long currently
                            Object indexValue = null;
                            if (orderMapping.getMemberMetaData().getTypeName().equals(ClassNameConstants.JAVA_LANG_LONG) ||
                                    orderMapping.getMemberMetaData().getTypeName().equals(ClassNameConstants.LONG))
                            {
                                indexValue = new Long(index);
                            }
                            else
                            {
                                indexValue = new Integer(index);
                            }
                            esm.replaceField(orderMapping.getMemberMetaData().getAbsoluteFieldNumber(), indexValue, true);
                        }
                        else
                        {
                            // Order is stored in a surrogate column so save its vaue for the element to use later
                            esm.setAssociatedValue(orderMapping, new Integer(index));
                        }
                    }
                }
                if (ownerFieldNumber >= 0 &&
                    sm.getObjectManager().getOMFContext().getPersistenceConfiguration().getBooleanProperty("datanucleus.manageRelationships"))
                {
                    // Managed Relations : 1-N bidir, so make sure owner is correct at persist
                    Object currentOwner = esm.provideField(ownerFieldNumber);
                    if (currentOwner == null)
                    {
                        // No owner, so correct it
                        NucleusLogger.JDO.info(LOCALISER.msg("056037",
                            StringUtils.toJVMIDString(sm.getObject()), ownerMemberMetaData.getFullFieldName(),
                            StringUtils.toJVMIDString(esm.getObject())));
                        esm.replaceField(ownerFieldNumber, newOwner, true);
                    }
                    else if (currentOwner != newOwner && sm.getReferencedPC() == null)
                    {
                        // Owner of the element is neither this container nor is it being attached
                        // Inconsistent owner, so throw exception
                        throw new NucleusUserException(LOCALISER.msg("056038",
                            StringUtils.toJVMIDString(sm.getObject()), ownerMemberMetaData.getFullFieldName(),
                            StringUtils.toJVMIDString(esm.getObject()),
                            StringUtils.toJVMIDString(currentOwner)));
                    }
                }
            }
            public void fetchNonLoadedFields(StateManager sm)
            {
            }
            public FetchPlan getFetchPlanForLoading()
            {
                return null;
            }
        });

        return inserted;
    }

    // ---------------------------- Query Methods ------------------------------

    /**
     * Method used in queries when contains() has been invoked.
     * @param stmt The Query Statement
     * @param parentStmt the parent Query Statement. If there is no parent, "parentStmt" must be equal to "stmt"
     * @param ownerMapping the mapping for the owner.
     * @param ownerTe Table Expression for the owner
     * @param filteredElementType The Class Type for the filtered element
     * @param elementExpr The Expression for the element
     * @param elementTableAlias The SQL alias to assign to the expression or to the element table.
     * @param listTableAlias The alias for the "List" table
     * @param existsQuery Whether this is joining for an EXISTS query
     * @return expression to the join
     */
    public ScalarExpression joinElementsTo(QueryExpression stmt,
            QueryExpression parentStmt,
            JavaTypeMapping ownerMapping,
            LogicSetExpression ownerTe,
            DatastoreIdentifier listTableAlias,
            Class filteredElementType,
            ScalarExpression elementExpr,
            DatastoreIdentifier elementTableAlias,
            boolean existsQuery)
    {
        ClassLoaderResolver clr = stmt.getClassLoaderResolver();
        if (!clr.isAssignableFrom(elementType, filteredElementType) &&
            !clr.isAssignableFrom(filteredElementType, elementType))
        {
            throw new IncompatibleQueryElementTypeException(elementType, filteredElementType.getName());
        }
        DatastoreContainerObject filteredElementTable = storeMgr.getDatastoreClass(filteredElementType.getName(), clr);

        if (stmt.getTableExpression(elementTableAlias) == null)
        {
            // Make sure we have the table of the element object in the statement
            stmt.newTableExpression(filteredElementTable, elementTableAlias);
        }

        DatastoreIdentifier containerRangeVar = listTableAlias;
        if (existsQuery)
        {
            // Part of EXISTS query. Why do we treat this differently ?????????????
            if (stmt.getTableExpression(containerRangeVar) == null)
            {
                // WHY????????????????????????
                containerRangeVar = elementTableAlias;
            }
            if (stmt.getTableExpression(containerRangeVar) == null)
            {
                // Create the container table if not yet present in the statement
                stmt.newTableExpression(containerTable, containerRangeVar);
            }
        }
        else
        {
            if (parentStmt != stmt)
            {
                // Subquery. Why do we treat differently ??????
                if (stmt.getTableExpression(containerRangeVar) == null)
                {
                    // WHY????????????????????????
                    containerRangeVar = elementTableAlias;
                }
                if (stmt.getTableExpression(containerRangeVar) == null)
                {
                    // Create the container table if not yet present in the statement
                    stmt.newTableExpression(containerTable, containerRangeVar);
                }

                // Reverse collection contains query so join back to the owner
                ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt, ownerTe);
                ScalarExpression ownerSetExpr = this.ownerMapping.newScalarExpression(stmt,
                    stmt.getTableExpression(containerRangeVar));
                stmt.andCondition(ownerExpr.eq(ownerSetExpr), true);
            }
            else
            {
                if (elementExpr.getLogicSetExpression().getMainTable() == filteredElementTable)
                {
                    // Element expression is of the container table
                    // Simple example is "SELECT FROM MyType WHERE field1.contains(this)"
                    // so candidate is MyType, element type is MyType and the element we are checking
                    // is MyType.
                    if (stmt.getTableExpression(containerRangeVar) == null)
                    {
                        // Why???????????????
                        containerRangeVar = elementTableAlias;
                    }
                    if (stmt.getTableExpression(containerRangeVar) == null)
                    {
                        // Create the container table if not yet present in the statement
                        stmt.newTableExpression(containerTable, containerRangeVar);
                    }
                    ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt, ownerTe);
                    ScalarExpression ownerSetExpr = this.ownerMapping.newScalarExpression(stmt,
                        stmt.getTableExpression(containerRangeVar));
                    stmt.andCondition(ownerExpr.eq(ownerSetExpr), true);
                }
                else
                {
                    // Element is of a different table
                    // Simple example is "SELECT FROM MyType WHERE field1.contains(field2)"
                    // so candidate is MyType, element type is MyElement, and the element we are checking
                    // is MyType.field2. This creates an INNER JOIN MYTYPES.ID -> MYELEMENT.FK
                    if (stmt.getTableExpression(containerRangeVar) == null)
                    {
                        // Create the container table if not yet present in the statement
                        stmt.newTableExpression(containerTable, containerRangeVar);
                    }

                    // Add a join from the owner table to the container table
                    ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt, ownerTe);
                    ScalarExpression ownerSetExpr = this.ownerMapping.newScalarExpression(stmt,
                        stmt.getTableExpression(containerRangeVar));
                    stmt.innerJoin(ownerExpr, ownerSetExpr, stmt.getTableExpression(containerRangeVar), true, true);
                }
            }
        }

        // Return the expression of the PK of the elements table for the element to be bound to
        JavaTypeMapping elementTableID = filteredElementTable.getIDMapping();
        return elementTableID.newScalarExpression(stmt, stmt.getTableExpression(containerRangeVar));
    }
}
TOP

Related Classes of org.datanucleus.store.mapped.scostore.FKListStore

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.