All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.datanucleus.store.rdbms.sql.SQLStatementHelper Maven / Gradle / Ivy

There is a newer version: 6.0.8
Show newest version
/**********************************************************************
Copyright (c) 2009 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.sql;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.ExecutionContext;
import org.datanucleus.FetchPlan;
import org.datanucleus.NucleusContext;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.DiscriminatorMetaData;
import org.datanucleus.metadata.DiscriminatorStrategy;
import org.datanucleus.metadata.FieldMetaData;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.JoinMetaData;
import org.datanucleus.metadata.MetaDataManager;
import org.datanucleus.metadata.RelationType;
import org.datanucleus.store.connection.ManagedConnection;
import org.datanucleus.store.rdbms.mapping.MappingHelper;
import org.datanucleus.store.rdbms.mapping.StatementClassMapping;
import org.datanucleus.store.rdbms.mapping.StatementMappingIndex;
import org.datanucleus.store.rdbms.mapping.java.DiscriminatorLongMapping;
import org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping;
import org.datanucleus.store.rdbms.mapping.java.PersistableIdMapping;
import org.datanucleus.store.rdbms.mapping.java.PersistableMapping;
import org.datanucleus.store.rdbms.mapping.java.ReferenceMapping;
import org.datanucleus.store.rdbms.RDBMSStoreManager;
import org.datanucleus.store.rdbms.SQLController;
import org.datanucleus.store.rdbms.adapter.DatastoreAdapter;
import org.datanucleus.store.rdbms.sql.SQLJoin.JoinType;
import org.datanucleus.store.rdbms.sql.expression.BooleanExpression;
import org.datanucleus.store.rdbms.sql.expression.SQLExpression;
import org.datanucleus.store.rdbms.table.DatastoreClass;
import org.datanucleus.store.rdbms.table.DatastoreElementContainer;
import org.datanucleus.store.rdbms.table.JoinTable;
import org.datanucleus.store.rdbms.table.PersistableJoinTable;
import org.datanucleus.store.rdbms.table.SecondaryDatastoreClass;
import org.datanucleus.store.rdbms.table.Table;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.NucleusLogger;

/**
 * Series of convenience methods to help the process of generating SQLStatements.
 */
public class SQLStatementHelper
{
    /**
     * Convenience method to return a PreparedStatement for an SQLStatement.
     * @param sqlStmt The query expression
     * @param ec execution context
     * @param mconn The connection to use
     * @param resultSetType Type of result set (if any)
     * @param resultSetConcurrency result-set concurrency (if any)
     * @return The PreparedStatement
     * @throws SQLException If an error occurs in creation
     */
    public static PreparedStatement getPreparedStatementForSQLStatement(SQLStatement sqlStmt, 
            ExecutionContext ec, ManagedConnection mconn, String resultSetType, String resultSetConcurrency)
    throws SQLException
    {
        SQLText sqlText = sqlStmt.getSelectStatement();
        SQLController sqlControl = sqlStmt.getRDBMSManager().getSQLController();

        // Generate the statement using the statement text
        PreparedStatement ps = sqlControl.getStatementForQuery(mconn, sqlText.toString(), resultSetType, resultSetConcurrency);

        boolean done = false;
        try
        {
            // Apply any parameter values for the statement
            sqlText.applyParametersToStatement(ec, ps);
            done = true;
        }
        finally
        {
            if (!done)
            {
                sqlControl.closeStatement(mconn, ps);
            }
        }

        return ps;
    }

    /**
     * Convenience method to apply parameter values to the provided statement.
     * @param ps The prepared statement
     * @param ec ExecutionContext
     * @param parameters The parameters
     * @param paramNameByPosition Optional map of parameter names keyed by the position
     * @param paramValuesByName Value of parameter keyed by name (or position)
     */
    public static void applyParametersToStatement(PreparedStatement ps, ExecutionContext ec,
            List parameters, Map paramNameByPosition, Map paramValuesByName)
    {
        if (parameters != null)
        {
            int num = 1;
            Map paramNumberByName = null;
            int nextParamNumber = 0;

            Iterator i = parameters.iterator();
            while (i.hasNext())
            {
                SQLStatementParameter param = i.next();
                JavaTypeMapping mapping = param.getMapping();
                RDBMSStoreManager storeMgr = mapping.getStoreManager();

                // Find the overall parameter value for this parameter
                Object value = null;
                if (paramNumberByName != null)
                {
                    // Parameters are numbered but the query has named, so use lookup
                    Integer position = paramNumberByName.get("" + param.getName());
                    if (position == null)
                    {
                        value = paramValuesByName.get(Integer.valueOf(nextParamNumber));
                        paramNumberByName.put(param.getName(), nextParamNumber);
                        nextParamNumber++;
                    }
                    else
                    {
                        value = paramValuesByName.get(position);
                    }
                }
                else
                {
                    if (paramValuesByName.containsKey(param.getName()))
                    {
                        // Named parameter has value in the input map
                        value = paramValuesByName.get(param.getName());
                    }
                    else
                    {
                        // Named parameter doesn't have value, so maybe using numbered input params
                        if (paramNameByPosition != null)
                        {
                            int paramPosition = -1;
                            Set paramNamesEncountered = new HashSet();
                            for (Map.Entry entry : paramNameByPosition.entrySet())
                            {
                                String paramName = entry.getValue();
                                if (!paramNamesEncountered.contains(paramName))
                                {
                                    paramPosition++;
                                    paramNamesEncountered.add(paramName);
                                }
                                if (paramName.equals(param.getName()))
                                {
                                    value = paramValuesByName.get(paramPosition);
                                    break;
                                }
                            }
                            paramNamesEncountered.clear();
                            paramNamesEncountered = null;
                        }
                        else
                        {
                            try
                            {
                                value = paramValuesByName.get(Integer.valueOf(param.getName()));
                            }
                            catch (NumberFormatException nfe)
                            {
                                value = paramValuesByName.get(Integer.valueOf(nextParamNumber));
                                paramNumberByName = new HashMap();
                                paramNumberByName.put(param.getName(), Integer.valueOf(nextParamNumber));
                                nextParamNumber++;
                            }
                        }
                    }
                }

                AbstractClassMetaData cmd = ec.getMetaDataManager().getMetaDataForClass(mapping.getType(), ec.getClassLoaderResolver());
                if (param.getColumnNumber() >= 0 && cmd != null)
                {
                    // Apply the value for this column of the mapping
                    Object colValue = null;
                    if (value != null)
                    {
                        if (cmd.getIdentityType() == IdentityType.DATASTORE)
                        {
                            colValue = mapping.getValueForDatastoreMapping(ec.getNucleusContext(), param.getColumnNumber(), value);
                        }
                        else if (cmd.getIdentityType() == IdentityType.APPLICATION)
                        {
                            colValue = getValueForPrimaryKeyIndexOfObjectUsingReflection(value, param.getColumnNumber(), cmd, storeMgr, ec.getClassLoaderResolver());
                        }
                    }
                    mapping.getDatastoreMapping(param.getColumnNumber()).setObject(ps, num, colValue);
                }
                else
                {
                    // Apply the value as a whole
                    if (ec.getApiAdapter().isPersistable(value))
                    {
                        if (!ec.getApiAdapter().isPersistent(value) && !ec.getApiAdapter().isDetached(value))
                        {
                            // Transient persistable object, so don't use since would cause its persistence
                            mapping.setObject(ec, ps, MappingHelper.getMappingIndices(num, mapping), null);
                        }
                        else if (ec.getApiAdapter().isDetached(value))
                        {
                            // Detached, so avoid re-attaching
                            Object id = ec.getApiAdapter().getIdForObject(value);
                            PersistableIdMapping idMapping = new PersistableIdMapping((PersistableMapping)mapping);
                            idMapping.setObject(ec, ps, MappingHelper.getMappingIndices(num, idMapping), id);
                        }
                        else
                        {
                            mapping.setObject(ec, ps, MappingHelper.getMappingIndices(num, mapping), value);
                        }
                    }
                    else
                    {
                        if (mapping.getNumberOfDatastoreMappings() == 1)
                        {
                            // Set whole object and only 1 column
                            mapping.setObject(ec, ps, MappingHelper.getMappingIndices(num, mapping), value);
                        }
                        else if (mapping.getNumberOfDatastoreMappings() > 1 && param.getColumnNumber() == (mapping.getNumberOfDatastoreMappings()-1))
                        {
                            // Set whole object and this is the last parameter entry for it, so set now
                            mapping.setObject(ec, ps, MappingHelper.getMappingIndices(num - mapping.getNumberOfDatastoreMappings()+1, mapping), value);
                        }
                    }
                }
                num++;
            }
        }
    }

    /**
     * Convenience method to use reflection to extract the value of a PK field of the provided object.
     * @param value The value of the overall object
     * @param index Index of the PK field whose value we need to return
     * @param cmd Metadata for the class
     * @param storeMgr Store manager
     * @param clr ClassLoader resolver
     * @return Value of this index of the PK field
     */
    public static Object getValueForPrimaryKeyIndexOfObjectUsingReflection(Object value, int index, AbstractClassMetaData cmd, RDBMSStoreManager storeMgr, ClassLoaderResolver clr)
    {
        if (cmd.getIdentityType() == IdentityType.DATASTORE)
        {
            throw new NucleusException("This method does not support datastore-identity");
        }

        int position = 0;
        int[] pkPositions = cmd.getPKMemberPositions();
        for (int i=0;i unionStmts = stmt.getUnions();
        if (unionStmts != null)
        {
            Iterator iter = unionStmts.iterator();
            while (iter.hasNext())
            {
                SQLStatement unionStmt = iter.next();
                selectIdentityOfCandidateInStatement(unionStmt, null, candidateCmd);
            }
        }
    }

    /**
     * Method to select all fetch plan members for the candidate class.
     * The supplied statement and mapping definition are updated during this method.
     * Shortcut to calling "selectFetchPlanOfSourceClassInStatement".
     * @param stmt The statement
     * @param mappingDefinition Mapping definition for result columns
     * @param candidateCmd The candidate class meta-data
     * @param fetchPlan FetchPlan in use
     * @param maxFetchDepth Max fetch depth from this point to select (0 implies no other objects)
     */
    public static void selectFetchPlanOfCandidateInStatement(SQLStatement stmt, StatementClassMapping mappingDefinition, AbstractClassMetaData candidateCmd,
            FetchPlan fetchPlan, int maxFetchDepth)
    {
        selectFetchPlanOfSourceClassInStatement(stmt, mappingDefinition, fetchPlan, stmt.getPrimaryTable(), candidateCmd, maxFetchDepth);
    }

    /**
     * Method to select all fetch plan members for the "source" class.
     * If the passed FetchPlan is null then the default fetch group fields will be selected.
     * The source class is defined by the supplied meta-data, and the SQLTable that we are selecting from.
     * The supplied statement and mapping definition are updated during this method.
     * @param stmt The statement
     * @param mappingDefinition Mapping definition for result columns (populated with column positions
     *                          of any selected mappings if provided as input)
     * @param fetchPlan FetchPlan in use
     * @param sourceSqlTbl SQLTable for the source class that we select from
     * @param sourceCmd Meta-data for the source class
     * @param maxFetchDepth Max fetch depth from this point to select (0 implies no other objects)
     */
    public static void selectFetchPlanOfSourceClassInStatement(SQLStatement stmt, StatementClassMapping mappingDefinition, FetchPlan fetchPlan,
            SQLTable sourceSqlTbl, AbstractClassMetaData sourceCmd, int maxFetchDepth)
    {
        DatastoreClass sourceTbl = (DatastoreClass)sourceSqlTbl.getTable();
        int[] fieldNumbers;
        if (fetchPlan != null)
        {
            // Use FetchPlan fields
            fieldNumbers = fetchPlan.getFetchPlanForClass(sourceCmd).getMemberNumbers();
        }
        else
        {
            // Use DFG fields
            fieldNumbers = sourceCmd.getDFGMemberPositions();
        }

        ClassLoaderResolver clr = stmt.getRDBMSManager().getNucleusContext().getClassLoaderResolver(null);
        for (int i=0;i 0)
        {
            selectSubobjects = true;
        }

        // Set table-group name for any related object we join to (naming based on member name)
        String tableGroupName = sourceSqlTbl.getGroupName() + "." + mmd.getName();
        JavaTypeMapping m = sourceSqlTbl.getTable().getMemberMapping(mmd);
        if (m != null && m.includeInFetchStatement())
        {
            RelationType relationType = mmd.getRelationType(clr);
            RDBMSStoreManager storeMgr = stmt.getRDBMSManager();
            DatastoreAdapter dba = storeMgr.getDatastoreAdapter();
            if (!dba.validToSelectMappingInStatement(stmt, m))
            {
                // Not valid to select this mapping for this statement so return
                return;
            }

            MetaDataManager mmgr = storeMgr.getMetaDataManager();
            StatementMappingIndex stmtMapping = new StatementMappingIndex(m);
            if (m.getNumberOfDatastoreMappings() > 0)
            {
                // Select of fields with columns in source table(s)
                // Adds inner/outer join to any required superclass/secondary tables
                SQLTable sqlTbl = SQLStatementHelper.getSQLTableForMappingOfTable(stmt, sourceSqlTbl, m);

                boolean selectFK = true;
                if (selectSubobjects && (relationType == RelationType.ONE_TO_ONE_UNI ||
                    (relationType == RelationType.ONE_TO_ONE_BI && mmd.getMappedBy() == null)) && !mmd.isSerialized() && !mmd.isEmbedded())
                {
                    // Related object with FK at this side
                    selectFK = selectFetchPlanFieldsOfFKRelatedObject(stmt, mappingDefinition, fetchPlan, sourceSqlTbl, mmd, clr, 
                        maxFetchPlanLimit, m, tableGroupName, stmtMapping, sqlTbl);
                }
                else if (selectSubobjects && (!mmd.isEmbedded() && !mmd.isSerialized()) && relationType == RelationType.MANY_TO_ONE_BI)
                {
                    AbstractMemberMetaData[] relatedMmds = mmd.getRelatedMemberMetaData(clr);
                    if (mmd.getJoinMetaData() != null || relatedMmds[0].getJoinMetaData() != null)
                    {
                        // N-1 bidirectional join table relation
                        // TODO Add left outer join from {sourceTable}.ID to {joinTable}.ELEM_FK
                        Table joinTable = storeMgr.getTable(relatedMmds[0]);
                        DatastoreElementContainer collTable = (DatastoreElementContainer)joinTable;
                        JavaTypeMapping selectMapping = collTable.getOwnerMapping();
                        SQLTable joinSqlTbl = null;
                        if (stmt.getPrimaryTable().getTable() != joinTable)
                        {
                            // Join to the join table
                            JavaTypeMapping referenceMapping = collTable.getElementMapping();
                            joinSqlTbl = stmt.leftOuterJoin(sourceSqlTbl, sourceSqlTbl.getTable().getIdMapping(), collTable, null, referenceMapping, null, tableGroupName);
                        }
                        else
                        {
                            // Main table of the statement is the join table so no need to join
                            joinSqlTbl = stmt.getPrimaryTable();
                        }

                        // Select the owner mapping of the join table
                        int[] colNumbers = stmt.select(joinSqlTbl, selectMapping, null);
                        stmtMapping.setColumnPositions(colNumbers);
                        // TODO Join to 1 side from join table?
                    }
                    else
                    {
                        // N-1 bidirectional FK relation
                        // Related object with FK at this side, so join/select related object as required
                        selectFK = selectFetchPlanFieldsOfFKRelatedObject(stmt, mappingDefinition, fetchPlan, sourceSqlTbl, mmd, clr, maxFetchPlanLimit, m, tableGroupName, stmtMapping, sqlTbl);
                    }
                }
                if (selectFK)
                {
                    int[] colNumbers = stmt.select(sqlTbl, m, null);
                    stmtMapping.setColumnPositions(colNumbers);
                }
            }
            else
            {
                // Select of related objects with FK in other table
                if (relationType == RelationType.ONE_TO_ONE_BI && mmd.getMappedBy() != null)
                {
                    // 1-1 bidirectional relation with FK in related table
                    AbstractMemberMetaData[] relatedMmds = mmd.getRelatedMemberMetaData(clr);
                    AbstractMemberMetaData relatedMmd = relatedMmds[0];
                    String[] clsNames = null;
                    if (mmd.getType().isInterface())
                    {
                        if (mmd.getFieldTypes() != null && mmd.getFieldTypes().length == 1)
                        {
                            // Use field-type since only one class specified
                            Class fldTypeCls = clr.classForName(mmd.getFieldTypes()[0]);
                            if (fldTypeCls.isInterface())
                            {
                                // User has specified an interface, so find its implementations
                                clsNames = mmgr.getClassesImplementingInterface(mmd.getFieldTypes()[0], clr);
                            }
                            else
                            {
                                // Use user-provided field-type
                                clsNames = new String[] {mmd.getFieldTypes()[0]};
                            }
                        }
                        if (clsNames == null)
                        {
                            clsNames = mmgr.getClassesImplementingInterface(mmd.getTypeName(), clr);
                        }
                    }
                    else
                    {
                        clsNames = new String[] { mmd.getTypeName() };
                    }

                    DatastoreClass relatedTbl = storeMgr.getDatastoreClass(clsNames[0], clr);
                    JavaTypeMapping relatedMapping = relatedTbl.getMemberMapping(relatedMmd);
                    JavaTypeMapping relatedDiscrimMapping = relatedTbl.getDiscriminatorMapping(true);
                    Object[] discrimValues = null;
                    JavaTypeMapping relatedTypeMapping = null;
                    AbstractClassMetaData relatedCmd = relatedMmd.getAbstractClassMetaData();
                    if (relatedDiscrimMapping != null && (relatedCmd.getSuperAbstractClassMetaData() != null || !relatedCmd.getFullClassName().equals(mmd.getTypeName())))
                    {
                        // Related table has a discriminator and the field can store other types
                        List discValueList = null;
                        for (int i=0;i 1)
                    {
                        NucleusLogger.QUERY.info("Relation (" + mmd.getFullFieldName() + ") with multiple related tables (using subclass-table). Not supported so selecting FK of related object only");
                        return true;
                    }

                    relatedTbl = storeMgr.getDatastoreClass(ownerParentCmds[0].getFullClassName(), clr);
                }

                String requiredGroupName = null;
                if (sourceSqlTbl.getGroupName() != null)
                {
                    // JPQL will have table groups defined already, named as per "alias.fieldName"
                    requiredGroupName = sourceSqlTbl.getGroupName() + "." + mmd.getName();
                }
                SQLTable relatedSqlTbl = stmt.getTable(relatedTbl, requiredGroupName);
                if (relatedSqlTbl == null)
                {
                    // Join the 1-1 relation
                    relatedSqlTbl = addJoinForOneToOneRelation(stmt, m, sqlTbl, relatedTbl.getIdMapping(), relatedTbl, null, null, tableGroupName, null);
                }

                StatementClassMapping subMappingDefinition =
                    new StatementClassMapping(mmd.getClassName(), mmd.getName());
                selectFetchPlanOfSourceClassInStatement(stmt, subMappingDefinition, fetchPlan, relatedSqlTbl, relatedCmd, maxFetchPlanLimit-1);
                if (mappingDefinition != null)
                {
                    if (relatedCmd.getIdentityType() == IdentityType.APPLICATION)
                    {
                        int[] pkFields = relatedCmd.getPKMemberPositions();
                        int[] pkCols = new int[m.getNumberOfDatastoreMappings()];
                        int pkColNo = 0;
                        for (int i=0;i subclasses = storeMgr.getSubClassesForClass(className, true, clr);
            if (subclasses != null && subclasses.size() > 0)
            {
                discrimValues.addAll(subclasses);
            }
        }
        else if (strategy == DiscriminatorStrategy.VALUE_MAP)
        {
            MetaDataManager mmgr = storeMgr.getMetaDataManager();
            AbstractClassMetaData cmd = mmgr.getMetaDataForClass(className, clr);
            Collection subclasses = storeMgr.getSubClassesForClass(className, true, clr);
            discrimValues.add(cmd.getInheritanceMetaData().getDiscriminatorMetaData().getValue());
            if (subclasses != null && subclasses.size() > 0)
            {
                Iterator subclassesIter = subclasses.iterator();
                while (subclassesIter.hasNext())
                {
                    String subclassName = subclassesIter.next();
                    AbstractClassMetaData subclassCmd = mmgr.getMetaDataForClass(subclassName, clr);
                    discrimValues.add(subclassCmd.getInheritanceMetaData().getDiscriminatorMetaData().getValue());
                }
            }
        }

        return discrimValues;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy