Package org.datanucleus.store.rdbms.scostore

Source Code of org.datanucleus.store.rdbms.scostore.RDBMSFKSetStore

/**********************************************************************
Copyright (c) 2007 Andy Jefferson 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:
    ...
**********************************************************************/

package org.datanucleus.store.rdbms.scostore;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.Transaction;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.DiscriminatorStrategy;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.ObjectProvider;
import org.datanucleus.store.connection.ManagedConnection;
import org.datanucleus.store.mapped.DatastoreClass;
import org.datanucleus.store.mapped.StatementClassMapping;
import org.datanucleus.store.mapped.StatementMappingIndex;
import org.datanucleus.store.mapped.StatementParameterMapping;
import org.datanucleus.store.mapped.exceptions.MappedDatastoreException;
import org.datanucleus.store.mapped.mapping.MappingHelper;
import org.datanucleus.store.mapped.scostore.FKSetStore;
import org.datanucleus.store.query.ResultObjectFactory;
import org.datanucleus.store.rdbms.RDBMSStoreManager;
import org.datanucleus.store.rdbms.SQLController;
import org.datanucleus.store.rdbms.mapping.RDBMSMapping;
import org.datanucleus.store.rdbms.sql.DiscriminatorStatementGenerator;
import org.datanucleus.store.rdbms.sql.SQLStatement;
import org.datanucleus.store.rdbms.sql.SQLStatementHelper;
import org.datanucleus.store.rdbms.sql.SQLTable;
import org.datanucleus.store.rdbms.sql.StatementGenerator;
import org.datanucleus.store.rdbms.sql.UnionStatementGenerator;
import org.datanucleus.store.rdbms.sql.expression.SQLExpression;
import org.datanucleus.store.rdbms.sql.expression.SQLExpressionFactory;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.NucleusLogger;

/**
* RDBMS-specific implementation of an {@link FKSetStore}.
*/
public class RDBMSFKSetStore extends FKSetStore
{
    /** Statement for updating a foreign key in a 1-N unidirectional */
    private String updateFkStmt;

    /** Statement for nullifying a FK in the element. */
    private String clearNullifyStmt;

    /** JDBC statement to use for retrieving keys of the map (locking). */
    private String iteratorStmtLocked = null;

    /** JDBC statement to use for retrieving keys of the map (not locking). */
    private String iteratorStmtUnlocked = null;

    private StatementClassMapping iteratorMappingDef = null;
    private StatementParameterMapping iteratorMappingParams = null;

    /**
     * Constructor for the backing store of a FK set for RDBMS.
     * @param mmd The MetaData for the field that this represents
     * @param storeMgr The MappedStoreManager managing the associated datastore.
     * @param clr The ClassLoaderResolver
     **/
    public RDBMSFKSetStore(AbstractMemberMetaData mmd, RDBMSStoreManager storeMgr, ClassLoaderResolver clr)
    {
        super(mmd, storeMgr, clr, new RDBMSAbstractSetStoreSpecialization(LOCALISER, clr, storeMgr));
    }

    private RDBMSStoreManager getStoreMgr()
    {
        return (RDBMSStoreManager) storeMgr;
    }

    protected void clearInternal(ObjectProvider ownerSM, ExecutionContext ec)
    {
        // Clear the FKs in the datastore
        // TODO This is likely not necessary in the 1-N bidir case since we've just set the owner FK to null above
        String stmt = getClearNullifyStmt();
        try
        {
            if (elementInfo.length > 1)
            {
                // TODO we should loop over all tables that has elements stored into and clear all them
                DatastoreClass table = getStoreMgr().getDatastoreClass(elementInfo[0].getClassName(), clr);
                if (table != null)
                {
                    stmt = stmt.replace("<TABLE NAME>", table.toString());
                }
                else
                {
                    NucleusLogger.PERSISTENCE.warn("FKSetStore.updateElementFK : " +
                        "need to set table in statement but dont know table where to store " +
                        elementInfo[0].getClassName());
                }
            }
            ManagedConnection mconn = getStoreMgr().getConnection(ec);
            SQLController sqlControl = getStoreMgr().getSQLController();
            try
            {
                PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, stmt, false);
                try
                {
                    int jdbcPosition = 1;
                    BackingStoreHelper.populateOwnerInStatement(ownerSM, ec, ps, jdbcPosition, this);
                    sqlControl.executeStatementUpdate(mconn, stmt, ps, true);
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException e)
        {
            throw new NucleusDataStoreException(LOCALISER.msg("056013",stmt),e);
        }
    }

    /**
     * Generates the statement for clearing items by nulling the owner link out.
     * The statement will be
     * <PRE>
     * UPDATE LISTTABLE SET OWNERCOL=NULL [,DISTINGUISHER=NULL]
     * WHERE OWNERCOL=?
     * </PRE>
     * when there is only one element table, and will be
     * <PRE>
     * UPDATE ? SET OWNERCOL=NULL [,DISTINGUISHER=NULL]
     * WHERE OWNERCOL=?
     * </PRE>
     * when there is more than 1 element table.
     * @return The Statement for clearing items for the owner.
     */
    protected String getClearNullifyStmt()
    {
        if (clearNullifyStmt == null)
        {
            StringBuffer stmt = new StringBuffer();
            stmt.append("UPDATE ");
            if (elementInfo.length > 1)
            {
                //CANNOT USE ? to replace. JDBC drivers does not accept, so we replace
                //the <TABLE NAME> right before firing the statement to the database
                stmt.append("<TABLE NAME>");
            }
            else
            {
                // Could use elementInfo[0].getDatastoreClass but need to allow for relation being in superclass table
                stmt.append(containerTable.toString());
            }
            stmt.append(" SET ");
            for (int i=0; i<ownerMapping.getNumberOfDatastoreMappings(); i++)
            {
                if (i > 0)
                {
                    stmt.append(", ");
                }
                stmt.append(ownerMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString() + " = NULL");
            }
            if (relationDiscriminatorMapping != null)
            {
                for (int i=0; i<relationDiscriminatorMapping.getNumberOfDatastoreMappings(); i++)
                {
                    stmt.append(", ");
                    stmt.append(relationDiscriminatorMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString() + " = NULL");
                }
            }
            stmt.append(" WHERE ");
            for (int i=0; i<ownerMapping.getNumberOfDatastoreMappings(); i++)
            {
                if (i > 0)
                {
                    stmt.append(" AND ");
                }
                stmt.append(ownerMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString());
                stmt.append(" = ");
                stmt.append(((RDBMSMapping)ownerMapping.getDatastoreMapping(i)).getUpdateInputParameter());
            }
            clearNullifyStmt = stmt.toString();
        }
        return clearNullifyStmt;
    }

    /**
     * Generate statement for updating a Foreign Key in an inverse 1-N.
     * The statement generated will be
     * <PRE>
     * UPDATE ELEMENTTABLE SET FK_COL_1=?, FK_COL_2=?, [DISTINGUISHER=?]
     * WHERE ELEMENT_ID = ?
     * </PRE>
     * where there is a single element table, and
     * <PRE>
     * UPDATE ? SET FK_COL_1=?, FK_COL_2=?, [DISTINGUISHER=?]
     * WHERE ELEMENT_ID=?
     * </PRE>
     * where there are more than 1 element tables
     * @return Statement for updating the FK in an inverse 1-N
     */
    private String getUpdateFkStmt()
    {
        if (updateFkStmt == null)
        {
            StringBuffer stmt = new StringBuffer();
            stmt.append("UPDATE ");
            if (elementInfo.length > 1)
            {
                //CANNOT USE ? to replace. JDBC drivers does not accept, so we replace
                //the <TABLE NAME> right before firing the statement to the database
                stmt.append("<TABLE NAME>");
            }
            else
            {
                // Could use elementInfo[0].getDatastoreClass but need to allow for relation being in superclass table
                stmt.append(containerTable.toString());
            }
            stmt.append(" SET ");
            for (int i=0; i<ownerMapping.getNumberOfDatastoreMappings(); i++)
            {
                if (i > 0)
                {
                    stmt.append(",");
                }
                stmt.append(ownerMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString());
                stmt.append(" = ");
                stmt.append(((RDBMSMapping)ownerMapping.getDatastoreMapping(i)).getUpdateInputParameter());
            }
            if (relationDiscriminatorMapping != null)
            {
                for (int i=0; i<relationDiscriminatorMapping.getNumberOfDatastoreMappings(); i++)
                {
                    stmt.append(",");
                    stmt.append(relationDiscriminatorMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString());
                    stmt.append(" = ");
                    stmt.append(((RDBMSMapping)relationDiscriminatorMapping.getDatastoreMapping(i)).getUpdateInputParameter());
                }
            }

            stmt.append(" WHERE ");
            for (int i=0; i<elementMapping.getNumberOfDatastoreMappings(); i++)
            {
                if (i > 0)
                {
                    stmt.append(" AND ");
                }
                stmt.append(elementMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString());
                stmt.append(" = ");
                stmt.append(((RDBMSMapping)elementMapping.getDatastoreMapping(i)).getUpdateInputParameter());
            }
            updateFkStmt = stmt.toString();
        }

        return updateFkStmt;
    }

    protected boolean updateElementFkInternal(ObjectProvider sm, Object element, Object owner)
    {
        boolean retval;
        ExecutionContext ec = sm.getExecutionContext();
        String stmt = getUpdateFkStmt();
        try
        {
            ManagedConnection mconn = getStoreMgr().getConnection(ec);
            SQLController sqlControl = getStoreMgr().getSQLController();
            try
            {
                int jdbcPosition = 1;
                if (elementInfo.length > 1)
                {
                    DatastoreClass table = getStoreMgr().getDatastoreClass(element.getClass().getName(), clr);
                    if (table != null)
                    {
                        stmt = stmt.replace("<TABLE NAME>", table.toString());
                    }
                    else
                    {
                        NucleusLogger.PERSISTENCE.warn("FKSetStore.updateElementFK : need to set table in statement but dont know table where to store " + element);
                    }
                }
                PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, stmt, false);
                try
                {
                    if (owner == null)
                    {
                        if (ownerMemberMetaData != null)
                        {
                            ownerMapping.setObject(ec, ps, MappingHelper.getMappingIndices(jdbcPosition, ownerMapping),
                                null, sm, ownerMemberMetaData.getAbsoluteFieldNumber());
                        }
                        else
                        {
                            ownerMapping.setObject(ec, ps, MappingHelper.getMappingIndices(jdbcPosition, ownerMapping),
                                null);
                        }
                    }
                    else
                    {
                        if (ownerMemberMetaData != null)
                        {
                            ownerMapping.setObject(ec, ps, MappingHelper.getMappingIndices(jdbcPosition, ownerMapping),
                                sm.getObject(), sm, ownerMemberMetaData.getAbsoluteFieldNumber());
                        }
                        else
                        {
                            ownerMapping.setObject(ec, ps, MappingHelper.getMappingIndices(jdbcPosition, ownerMapping),
                                sm.getObject());
                        }
                    }
                    jdbcPosition += ownerMapping.getNumberOfDatastoreMappings();

                    if (relationDiscriminatorMapping != null)
                    {
                        jdbcPosition = BackingStoreHelper.populateRelationDiscriminatorInStatement(ec, ps, jdbcPosition, this);
                    }
                    elementMapping.setObject(ec, ps, MappingHelper.getMappingIndices(jdbcPosition, elementMapping), element);
                    jdbcPosition += elementMapping.getNumberOfDatastoreMappings();

                    sqlControl.executeStatementUpdate(mconn, stmt, ps, true);
                    retval = true;
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException e)
        {
            throw new NucleusDataStoreException(LOCALISER.msg("056027", stmt), e);
        }

        return retval;
    }

    /**
     * Accessor for an iterator for the set.
     * @param ownerSM State Manager for the set.
     * @return Iterator for the set.
     */
    public Iterator iterator(ObjectProvider ownerSM)
    {
        ExecutionContext ec = ownerSM.getExecutionContext();
        if (iteratorStmtLocked == null)
        {
            synchronized (this) // Make sure this completes in case another thread needs the same info
            {
                // Generate the statement, and statement mapping/parameter information
                SQLStatement sqlStmt = getSQLStatementForIterator(ownerSM);
                iteratorStmtUnlocked = sqlStmt.getSelectStatement().toSQL();
                sqlStmt.addExtension("lock-for-update", true);
                iteratorStmtLocked = sqlStmt.getSelectStatement().toSQL();
            }
        }

        Transaction tx = ec.getTransaction();
        String stmt = (tx.lockReadObjects() ? iteratorStmtLocked : iteratorStmtUnlocked);
        try
        {
            ManagedConnection mconn = storeMgr.getConnection(ec);
            SQLController sqlControl = ((RDBMSStoreManager)storeMgr).getSQLController();
            try
            {
                // Create the statement and set the owner
                PreparedStatement ps = sqlControl.getStatementForQuery(mconn, stmt);
                StatementMappingIndex ownerIdx = iteratorMappingParams.getMappingForParameter("owner");
                int numParams = ownerIdx.getNumberOfParameterOccurrences();
                for (int paramInstance=0;paramInstance<numParams;paramInstance++)
                {
                    ownerIdx.getMapping().setObject(ec, ps,
                        ownerIdx.getParameterPositionsForOccurrence(paramInstance), ownerSM.getObject());
                }

                try
                {
                    ResultSet rs = sqlControl.executeStatementQuery(mconn, stmt, ps);
                    try
                    {
                        ResultObjectFactory rof = null;
                        if (elementsAreEmbedded || elementsAreSerialised)
                        {
                            throw new NucleusException("Cannot have FK set with non-persistent objects");
                        }
                        else
                        {
                            rof = storeMgr.newResultObjectFactory(emd, iteratorMappingDef, false, null,
                                clr.classForName(elementType));
                        }

                        return new RDBMSSetStoreIterator(ownerSM, rs, rof, this);
                    }
                    finally
                    {
                        rs.close();
                    }
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException e)
        {
            throw new NucleusDataStoreException(LOCALISER.msg("056006", stmt),e);
        }
        catch (MappedDatastoreException e)
        {
            throw new NucleusDataStoreException(LOCALISER.msg("056006", stmt),e);
        }
    }

    /**
     * Method to generate an SQLStatement for iterating through elements of the set.
     * Selects the element table.
     * Populates the iteratorMappingDef and iteratorMappingParams.
     * @param ownerSM StateManager for the owner object
     * @return The SQLStatement
     */
    protected SQLStatement getSQLStatementForIterator(ObjectProvider ownerSM)
    {
        if (elementInfo == null || elementInfo.length == 0)
        {
            return null;
        }

        SQLStatement sqlStmt = null;

        RDBMSStoreManager storeMgr = (RDBMSStoreManager)this.storeMgr;
        SQLExpressionFactory exprFactory = storeMgr.getSQLExpressionFactory();
        final ClassLoaderResolver clr = ownerSM.getExecutionContext().getClassLoaderResolver();
        iteratorMappingDef = new StatementClassMapping();
        if (elementInfo[0].getDatastoreClass().getDiscriminatorMetaData() != null &&
            elementInfo[0].getDatastoreClass().getDiscriminatorMetaData().getStrategy() != DiscriminatorStrategy.NONE)
        {
            // TODO Only caters for one elementInfo, but with subclass-table we can have multiple
            String elementType = ownerMemberMetaData.getCollection().getElementType();
            if (ClassUtils.isReferenceType(clr.classForName(elementType)))
            {
                String[] clsNames =
                    storeMgr.getNucleusContext().getMetaDataManager().getClassesImplementingInterface(elementType, clr);
                Class[] cls = new Class[clsNames.length];
                for (int i=0; i<clsNames.length; i++)
                {
                    cls[i] = clr.classForName(clsNames[i]);
                }
                sqlStmt = new DiscriminatorStatementGenerator(storeMgr, clr, cls, true, null, null).getStatement();
            }
            else
            {
                sqlStmt = new DiscriminatorStatementGenerator(storeMgr, clr,
                    clr.classForName(elementInfo[0].getClassName()), true, null, null).getStatement();
            }
            iterateUsingDiscriminator = true;

            // Select the required fields (of the element class)
            SQLStatementHelper.selectFetchPlanOfSourceClassInStatement(sqlStmt, iteratorMappingDef,
                ownerSM.getExecutionContext().getFetchPlan(), sqlStmt.getPrimaryTable(), emd, 0);
        }
        else
        {
            for (int i=0;i<elementInfo.length;i++)
            {
                final Class elementCls = clr.classForName(this.elementInfo[i].getClassName());
                UnionStatementGenerator stmtGen = new UnionStatementGenerator(storeMgr, clr, elementCls, true, null, null);
                stmtGen.setOption(StatementGenerator.OPTION_SELECT_NUCLEUS_TYPE);
                iteratorMappingDef.setNucleusTypeColumnName(UnionStatementGenerator.NUC_TYPE_COLUMN);
                SQLStatement subStmt = stmtGen.getStatement();

                // Select the required fields (of the element class)
                if (sqlStmt == null)
                {
                    SQLStatementHelper.selectFetchPlanOfSourceClassInStatement(subStmt, iteratorMappingDef,
                        ownerSM.getExecutionContext().getFetchPlan(), subStmt.getPrimaryTable(), emd, 0);
                }
                else
                {
                    SQLStatementHelper.selectFetchPlanOfSourceClassInStatement(subStmt, null,
                        ownerSM.getExecutionContext().getFetchPlan(), subStmt.getPrimaryTable(), emd, 0);
                }

                if (sqlStmt == null)
                {
                    sqlStmt = subStmt;
                }
                else
                {
                    sqlStmt.union(subStmt);
                }
            }
        }

        // Apply condition to filter by owner
        SQLTable ownerSqlTbl =
            SQLStatementHelper.getSQLTableForMappingOfTable(sqlStmt, sqlStmt.getPrimaryTable(), ownerMapping);
        SQLExpression ownerExpr = exprFactory.newExpression(sqlStmt, ownerSqlTbl, ownerMapping);
        SQLExpression ownerVal = exprFactory.newLiteralParameter(sqlStmt, ownerMapping, null, "OWNER");
        sqlStmt.whereAnd(ownerExpr.eq(ownerVal), true);

        if (relationDiscriminatorMapping != null)
        {
            // Apply condition on distinguisher field to filter by distinguisher (when present)
            SQLTable distSqlTbl =
                SQLStatementHelper.getSQLTableForMappingOfTable(sqlStmt, sqlStmt.getPrimaryTable(), relationDiscriminatorMapping);
            SQLExpression distExpr = exprFactory.newExpression(sqlStmt, distSqlTbl, relationDiscriminatorMapping);
            SQLExpression distVal = exprFactory.newLiteral(sqlStmt, relationDiscriminatorMapping, relationDiscriminatorValue);
            sqlStmt.whereAnd(distExpr.eq(distVal), true);
        }

        if (orderMapping != null)
        {
            // Order by the ordering column, when present
            SQLTable orderSqlTbl =
                SQLStatementHelper.getSQLTableForMappingOfTable(sqlStmt, sqlStmt.getPrimaryTable(), orderMapping);
            SQLExpression[] orderExprs = new SQLExpression[orderMapping.getNumberOfDatastoreMappings()];
            boolean descendingOrder[] = new boolean[orderMapping.getNumberOfDatastoreMappings()];
            orderExprs[0] = exprFactory.newExpression(sqlStmt, orderSqlTbl, orderMapping);
            sqlStmt.setOrdering(orderExprs, descendingOrder);
        }

        // Input parameter(s) - the owner
        int inputParamNum = 1;
        StatementMappingIndex ownerIdx = new StatementMappingIndex(ownerMapping);
        if (sqlStmt.getNumberOfUnions() > 0)
        {
            // Add parameter occurrence for each union of statement
            for (int j=0;j<sqlStmt.getNumberOfUnions()+1;j++)
            {
                int[] paramPositions = new int[ownerMapping.getNumberOfDatastoreMappings()];
                for (int k=0;k<ownerMapping.getNumberOfDatastoreMappings();k++)
                {
                    paramPositions[k] = inputParamNum++;
                }
                ownerIdx.addParameterOccurrence(paramPositions);
            }
        }
        else
        {
            int[] paramPositions = new int[ownerMapping.getNumberOfDatastoreMappings()];
            for (int k=0;k<ownerMapping.getNumberOfDatastoreMappings();k++)
            {
                paramPositions[k] = inputParamNum++;
            }
            ownerIdx.addParameterOccurrence(paramPositions);
        }
        iteratorMappingParams = new StatementParameterMapping();
        iteratorMappingParams.addMappingForParameter("owner", ownerIdx);

        return sqlStmt;
    }
}
TOP

Related Classes of org.datanucleus.store.rdbms.scostore.RDBMSFKSetStore

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.