Package org.datanucleus.store.mapped.scostore

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

/**********************************************************************
Copyright (c) 2002 Mike Martin (TJDO) 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 - added comments
2003 Erik Bengtson - removed unused variable and import
2003 Erik Bengtson - fixed bug [832635] Imcompatiblities in casting
                    inherited classes in query
2003 Andy Jefferson - coding standards
2004 Andy Jefferson - updated retrieval of ColumnLists
2004 Andy Jefferson - addition of removeAll()
2004 Andy Jefferson - moved statements to AbstractSetStore
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 org.datanucleus.ClassLoaderResolver;
import org.datanucleus.FetchPlan;
import org.datanucleus.ObjectManager;
import org.datanucleus.StateManager;
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.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;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

import javax.jdo.spi.PersistenceCapable;

/**
* Representation of an Inverse Set as part of a relationship. This class is
* used where you have a 1-N and the tables are not joined via a link table.
* That is there is an owner table, and a collection table, and the collection
* table has a column being the id of the owner table. This is in contrast to
* NormalSetStore which represents 1-N relationships using a link table.
* There are 2 possible uses here
* <UL>
* <LI><B>bidirectional</B> - where the owner has a Collection of elements, and
* the element has an owner. In this case the element class will have an owner field
* which can be updated directly</LI>
* <LI><B>unidirectional</B> - where the owner has a Collection of elements, but
* the element knows nothing about the owner. In this case the element class has no
* owner field. In this case the column in the element table has to be updated
* directly.</LI>
* </UL>
*/
public abstract class FKSetStore extends AbstractSetStore
{
    /** Field number of owner link in element class. */
    private final int ownerFieldNumber;

    /**
     * Constructor for the relationship representation.
     * @param fmd The MetaData for the field that this represents
     * @param storeMgr The MappedStoreManager managing the associated datastore.
     * @param clr The ClassLoaderResolver
     * @param specialization The datastore-specific specialization
     **/
    public FKSetStore(AbstractMemberMetaData fmd, MappedStoreManager storeMgr, ClassLoaderResolver clr,
                      AbstractSetStoreSpecialization 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))
        {
            elementIsPersistentInterface = storeMgr.getOMFContext().getMetaDataManager().isPersistentInterface(element_class.getName());
            if (elementIsPersistentInterface)
            {
                emd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForInterface(element_class,clr);
            }
            else
            {
                // 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();
        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)
        if (fmd.getMappedBy() != null)
        {
            // 1-N FK bidirectional
            // The element class has a field for the owner.
            AbstractMemberMetaData eofmd = emd.getMetaDataForMember(fmd.getMappedBy());
            if (eofmd == null)
            {
                throw new NucleusUserException(LOCALISER.msg("056024", fmd.getFullFieldName(),
                    fmd.getMappedBy(), element_class.getName()));
            }

            // Check that the type of the element "mapped-by" field is consistent with the owner type
            //TODO check does not work if mapped by has relation to super class. Enable this
            //and run the PersistentInterfacesTest to reproduce the issue
            //TODO there is equivalent code in FKList and FKMap that was
            //not commented out. When fixing, add tests for all types of Inverses
            /*
            if (!clr.isAssignableFrom(eofmd.getType(), mmd.getAbstractClassMetaData().getFullClassName()))
            {
                throw new JPOXUserException(LOCALISER.msg("056025", mmd.getFullFieldName(),
                    eofmd.getFullFieldName(), eofmd.getTypeName(), mmd.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));
            }
        }

        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 Remove use of containerTable - just use elementTable[0] or equivalent
        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();
        }
    }

    /**
     * Utility to update a foreign-key (and distinguisher) 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
     * @return Whether it was performed successfully
     */
    private boolean updateElementFk(StateManager sm, Object element, Object owner)
    {
        if (element == null)
        {
            return false;
        }
        validateElementForWriting(sm, element, null);

        return updateElementFkInternal(sm, element, owner);
    }

    /**
     * This seems to return the field number in the element of the relation when it is a bidirectional
     * relation.
     * @param sm StateManager of the owner
     * @return The field number in the element for this relation
     */
    protected int getFieldNumberInElementForBidirectional(StateManager sm)
    {
        if (ownerFieldNumber < 0)
        {
            // Unidirectional
            return -1;
        }
        // This gives a different result when using persistent interfaces.
        // For example with the JDO2 TCK, org.apache.jdo.tck.pc.company.PIDepartmentImpl.employees will
        // return 3, yet the ownerMemberMetaData.getRelatedMetaData returns 8 since the generated implementation
        // will have all fields in a single MetaData (numbering from 0), whereas in a normal inheritance
        // tree there will be multiple MetaData (the root starting from 0)
        return sm.getClassMetaData().getAbsolutePositionOfMember(ownerMemberMetaData.getMappedBy());
    }

    /**
     * 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
        Iterator elemIter = iterator(sm);
        Collection existing = new HashSet();
        while (elemIter.hasNext())
        {
            Object elem = elemIter.next();
            if (!coll.contains(elem))
            {
                remove(sm, elem, -1, true);
            }
            else
            {
                existing.add(elem);
            }
        }

        if (existing.size() != coll.size())
        {
            // Add any elements that aren't already present
            Iterator iter = coll.iterator();
            while (iter.hasNext())
            {
                Object elem = iter.next();
                if (!existing.contains(elem))
                {
                    add(sm, elem, 0);
                }
            }
        }
    }

    /**
     * Method to add an object to the relationship at the collection end.
     * @param sm StateManager of the owner of the Set
     * @param element Element to be added
     * @return Success indicator
     */
    public boolean add(final StateManager sm, Object element, int size)
    {
        if (element == null)
        {
            // Sets allow no duplicates
            throw new NucleusUserException(LOCALISER.msg("056039"));
        }

        // Make sure that the element is persisted in the datastore (reachability)
        final Object newOwner = sm.getObject();
        ObjectManager om = sm.getObjectManager();
        boolean inserted = 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)
                    {
                        // Element type has a shared FK so set the discriminator value for this relation
                        esm.setAssociatedValue(relationDiscriminatorMapping, relationDiscriminatorValue);
                    }
                }

                if (getFieldNumberInElementForBidirectional(esm) >= 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(getFieldNumberInElementForBidirectional(esm));
                    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(getFieldNumberInElementForBidirectional(esm), newOwner, true);
                    }
                    else if (currentOwner != newOwner && sm.getReferencedPC() == null)
                    {
                        // Owner of the element is neither this container and not 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;
            }
        });

        if (inserted)
        {
            // Element has just been persisted so the FK will be set
            return true;
        }
        else
        {
            // Element was already persistent so make sure the FK is in place
            StateManager elementSM = om.findStateManager(element);
            if (getFieldNumberInElementForBidirectional(elementSM) >= 0 &&
                om.getOMFContext().getPersistenceConfiguration().getBooleanProperty("datanucleus.manageRelationships"))
            {
                // Managed Relations : 1-N bidir, so update the owner of the element
                om.getApiAdapter().isLoaded(elementSM, getFieldNumberInElementForBidirectional(elementSM)); // Ensure is loaded
                Object oldOwner = elementSM.provideField(getFieldNumberInElementForBidirectional(elementSM));
                if (oldOwner != newOwner)
                {
                    if (NucleusLogger.PERSISTENCE.isDebugEnabled())
                    {
                        NucleusLogger.PERSISTENCE.debug(LOCALISER.msg("055009",
                            StringUtils.toJVMIDString(sm.getObject()),
                            ownerMemberMetaData.getFullFieldName(),
                            StringUtils.toJVMIDString(element)));
                    }
                    PersistenceCapable pcElement = (PersistenceCapable) element;
                    elementSM.setObjectField(pcElement, getFieldNumberInElementForBidirectional(elementSM), oldOwner, newOwner);
                    if (om.isFlushing())
                    {
                        elementSM.flush();
                    }
                }
                return oldOwner != newOwner;
            }
            else
            {
                // 1-N unidir so update the FK if not set to be contained in the set
                boolean contained = contains(sm, element);
                return (contained ? false : updateElementFk(sm, element, newOwner));
            }
        }
    }
    /**
     * Method to add a collection of object to the relationship at the
     * collection end.
     * @param sm StateManager of the Set
     * @param elements Elements to be added
     * @return Success indicator
     **/
    public boolean addAll(StateManager sm, Collection elements, int size)
    {
        if (elements == null || elements.size() == 0)
        {
            return false;
        }

        boolean success = false;

        Iterator iter = elements.iterator();
        while (iter.hasNext())
        {
            if (add(sm, iter.next(), -1))
            {
                success = true;
            }
        }

        return success;
    }

    /**
     * Method to remove the link to the collection object specified.
     * Depending on the column characteristics in the collection table, the id of the owner field may
     * be NULLed, or the record may be deleted completely (as per cascade-delete in EJB).
     * @param ownerSM The StateManager of the Set
     * @param element The element of the collection to be deleted.
     * @param allowDependentField Whether to allow any cascade deletes caused by this removal
     * @return A success indicator.
     */
    public boolean remove(StateManager ownerSM, Object element, int size, boolean allowDependentField)
    {
        if (element == null)
        {
            return false;
        }
        if (!validateElementForReading(ownerSM, element))
        {
            return false;
        }

        // Find the state manager for the element
        Object elementToRemove = element;
        ObjectManager om = ownerSM.getObjectManager();
        if (om.getApiAdapter().isDetached(element)) // User passed in detached object to collection.remove()!
        {
            // Find an attached equivalent of this detached object (DON'T attach the object itself)
            elementToRemove = om.findObject(om.getApiAdapter().getIdForObject(element), true, false, element.getClass().getName());
        }
        StateManager elementSM = om.findStateManager(elementToRemove);

        Object oldOwner = null;
        if (ownerFieldNumber >= 0)
        {
            if (!om.getApiAdapter().isDeleted(elementToRemove))
            {
                // Find the existing owner if the record hasn't already been deleted
                om.getApiAdapter().isLoaded(elementSM, ownerFieldNumber);
                oldOwner = elementSM.provideField(ownerFieldNumber);
            }
        }
        else
        {
            // TODO Check if the element is managed by a different owner now
        }

        // Owner of the element has been changed
        if (ownerFieldNumber >= 0 && oldOwner != ownerSM.getObject() && oldOwner != null)
        {
            return false;
        }

        boolean deleteElement = checkRemovalOfElementShouldDelete(ownerSM);
        if (deleteElement)
        {
            if (om.getApiAdapter().isPersistable(elementToRemove) && om.getApiAdapter().isDeleted(elementToRemove))
            {
                // Element is waiting to be deleted so flush it (it has the FK)
                elementSM.flush();
            }
            else
            {
                // Element not yet marked for deletion so go through the normal process
                om.deleteObjectInternal(elementToRemove);
            }
        }
        else
        {
            // Perform any necessary "managed relationships" updates on the element (when bidirectional)
            manageRemovalOfElement(ownerSM, elementToRemove);

            // Update the datastore FK
            updateElementFk(ownerSM, elementToRemove, null);
        }

        return true;
    }

    /**
     * Method to remove the links to a collection of elements specified.
     * Depending on the column characteristics in the collection table, the id
     * of the owner fields may be NULLed, or the records may be deleted
     * completely.
     *
     * @param sm The StateManager of the Set
     * @param elements The elements of the collection to be deleted.
     * @return A success indicator.
     **/
    public boolean removeAll(StateManager sm, Collection elements, int size)
    {
        if (elements == null || elements.size() == 0)
        {
            return false;
        }

        // Check the first element for whether we can null the column or
        // whether we have to delete
        boolean success = true;

        Iterator iter=elements.iterator();
        while (iter.hasNext())
        {
            if (remove(sm, iter.next(), -1, true))
            {
                success = false;
            }
        }

        return success;
    }

    /**
     * Convenience method to return if the removal of an element should delete the element.
     * @param ownerSM StateManager for the owner
     * @return Whether we should delete the element on removing from the collection
     */
    protected boolean checkRemovalOfElementShouldDelete(StateManager ownerSM)
    {
        boolean delete = false;
        if (ownerMemberMetaData.getCollection().isDependentElement())
        {
            // Elements are dependent and can't exist on their own, so delete them all
            if (NucleusLogger.DATASTORE.isDebugEnabled())
            {
                NucleusLogger.DATASTORE.debug(LOCALISER.msg("056034"));
            }
            delete = true;
        }
        else
        {
            if (ownerMapping.isNullable())
            {
                // Field is not dependent, but is nullable so we null the FK
                if (NucleusLogger.DATASTORE.isDebugEnabled())
                {
                    NucleusLogger.DATASTORE.debug(LOCALISER.msg("056036"));
                }
                delete = false;
            }
            else
            {
                // Field is not dependent, and is not nullable so we just delete the elements
                if (NucleusLogger.DATASTORE.isDebugEnabled())
                {
                    NucleusLogger.DATASTORE.debug(LOCALISER.msg("056035"));
                }
                delete = true;
            }
        }
        return delete;
    }

    /**
     * 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)
    {
        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();
                    }
                }
            }
        }
    }

    /**
     * Method to allow the Set relationship to be cleared out.
     * 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 StateManager of the Set
     */
    public void clear(StateManager ownerSM)
    {
        ObjectManager om = ownerSM.getObjectManager();
        boolean deleteElements = checkRemovalOfElementShouldDelete(ownerSM);
        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 elementSM = om.findStateManager(element);
                        elementSM.flush();
                    }
                    else
                    {
                        // Element not yet marked for deletion so go through the normal process
                        om.deleteObjectInternal(element);
                    }
                }
            }
        }
        else
        {
            // Perform any necessary "managed relationships" updates on the element
            om.getApiAdapter().isLoaded(ownerSM, ownerMemberMetaData.getAbsoluteFieldNumber()); // Make sure the field is loaded
            Collection value = (Collection) ownerSM.provideField(ownerMemberMetaData.getAbsoluteFieldNumber());
            Iterator elementsIter = null;
            if (value != null && !value.isEmpty())
            {
                elementsIter = value.iterator();
            }
            else
            {
                // Maybe deleting the owner with optimistic transactions so the elements are no longer cached
                elementsIter = iterator(ownerSM);
            }
            if (elementsIter != null)
            {
                while (elementsIter.hasNext())
                {
                    Object element = elementsIter.next();
                    manageRemovalOfElement(ownerSM, element);
                }
            }
            clearInternal(ownerSM, om);
        }
    }

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

    /**
     * Utility for use in building a query, joining the element table and the owner table.
     * @param stmt The Query Statement
     * @param parentStmt the parent Query Statement. If there is no parent, <code>parentStmt</code> must be equals to <code>stmt</code>
     * @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 setTableAlias The alias for the "Set" table
     * @param existsQuery Whether this is joining for an EXISTS query
     * @return Expression for the id of the elements table
     */
    public ScalarExpression joinElementsTo(QueryExpression stmt,
            QueryExpression parentStmt,
            JavaTypeMapping ownerMapping,
            LogicSetExpression ownerTe,
            DatastoreIdentifier setTableAlias,
            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 containing the elements in the statement
            stmt.newTableExpression(filteredElementTable, elementTableAlias);
        }

        DatastoreIdentifier containerRangeVar = setTableAlias;
        if (existsQuery)
        {
            // Part of EXISTS query. Why do we treat this differently ?????????????
            if (stmt.getTableExpression(containerRangeVar) == null)
            {
                // WHY????????????????????????
            }
            if (stmt.getTableExpression(containerRangeVar) == null)
            {
                // Create the container table if not yet present in the statement
                containerRangeVar = elementTableAlias;
                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));
    }

    protected abstract void clearInternal(StateManager ownerSM, ObjectManager om);
    protected abstract boolean updateElementFkInternal(StateManager sm, Object element, Object owner);

}
TOP

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

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.