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

org.datanucleus.store.rdbms.request.FetchRequest Maven / Gradle / Ivy

/**********************************************************************
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.request;

import java.lang.reflect.Modifier;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.List;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.ExecutionContext;
import org.datanucleus.FetchPlan;
import org.datanucleus.FetchPlanForClass;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.exceptions.NucleusObjectNotFoundException;
import org.datanucleus.identity.IdentityUtils;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.VersionMetaData;
import org.datanucleus.state.LockMode;
import org.datanucleus.state.DNStateManager;
import org.datanucleus.store.connection.ManagedConnection;
import org.datanucleus.store.rdbms.mapping.MappingCallbacks;
import org.datanucleus.store.rdbms.mapping.MappingHelper;
import org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping;
import org.datanucleus.store.rdbms.mapping.java.PersistableMapping;
import org.datanucleus.store.rdbms.mapping.java.ReferenceMapping;
import org.datanucleus.store.rdbms.mapping.java.SingleCollectionMapping;
import org.datanucleus.store.rdbms.query.StatementClassMapping;
import org.datanucleus.store.rdbms.query.StatementMappingIndex;
import org.datanucleus.store.rdbms.RDBMSStoreManager;
import org.datanucleus.store.rdbms.SQLController;
import org.datanucleus.store.rdbms.fieldmanager.ParameterSetter;
import org.datanucleus.store.rdbms.fieldmanager.ResultSetGetter;
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.SelectStatement;
import org.datanucleus.store.rdbms.sql.expression.InExpression;
import org.datanucleus.store.rdbms.sql.expression.NullLiteral;
import org.datanucleus.store.rdbms.sql.expression.ParameterLiteral;
import org.datanucleus.store.rdbms.sql.expression.SQLExpression;
import org.datanucleus.store.rdbms.sql.expression.SQLExpressionFactory;
import org.datanucleus.store.rdbms.table.AbstractClassTable;
import org.datanucleus.store.rdbms.table.DatastoreClass;
import org.datanucleus.store.schema.table.SurrogateColumnType;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

/**
 * Class to retrieve the fields of an object of a specified class from the datastore.
 * If some of those fields are themselves persistent objects then this can optionally
 * retrieve fields of those objects in the same fetch.
 * 

* Any surrogate version stored in this table will be fetched *if* the object being updated doesn't * already have a value for it. If the caller wants the surrogate version to be updated then * they should nullify the "transactional" version before calling. *

*/ public class FetchRequest extends Request { /** JDBC fetch statement without locking. */ private String statementUnlocked; /** JDBC fetch statement with locking. */ private String statementLocked; /** Absolute numbers of the fields/properties of the class to fetch. */ private int[] memberNumbersToFetch = null; /** Absolute numbers of the members of the class to store the value for. */ private int[] memberNumbersToStore = null; /** The mapping of the results of the SQL statement. */ private StatementClassMapping mappingDefinition; /** Callbacks for postFetch() operations, to be called after the fetch itself (relation fields). */ private final List mappingCallbacks; private int numberOfFieldsToFetch = 0; /** Convenience string listing the fields to be fetched by this request. */ private final String fieldsToFetch; /** Whether we are fetching a surrogate version in this fetch. */ private boolean fetchingSurrogateVersion = false; /** Name of the version field. Only applies if the class has a version field (not surrogate). */ private String versionFieldName = null; /** ALl non-null PK members */ private int[] nonNullPkMembers = null; /** * Constructor. Uses the structure of the datastore table to build a basic SELECT query. * @param classTable The Class Table representing the datastore table to retrieve * @param fpClass FetchPlan for class * @param clr ClassLoader resolver * @param cmd ClassMetaData of the candidate * @param mmds MetaData of the members to fetch * @param mmdsToStore MetaData of the members to store * @param nullPkFields Fields in PK that are null - only used if PC model has objects with nullable PK fields */ public FetchRequest(DatastoreClass classTable, FetchPlanForClass fpClass, ClassLoaderResolver clr, AbstractClassMetaData cmd, AbstractMemberMetaData[] mmds, AbstractMemberMetaData[] mmdsToStore, BitSet nullPkFields) { super(classTable); // Work out the real candidate table. // Instead of just taking the most derived table as the candidate we find the table closest to the root table necessary to retrieve the requested fields boolean found = false; DatastoreClass candidateTable = classTable; if (mmds != null) { while (candidateTable != null) { for (int i=0;i(); List memberNumbersToStoreTmp = new ArrayList<>(); numberOfFieldsToFetch = processMembersOfClass(sqlStatement, fpClass, mmds, mmdsToStore, table, sqlStatement.getPrimaryTable(), mappingDefinition, mappingCallbacks, clr, memberNumbersToStoreTmp); memberNumbersToFetch = mappingDefinition.getMemberNumbers(); if (memberNumbersToStoreTmp.size() > 0) { // Remove memberNumbersToStoreTmp from memberNumbersToFetch so we have distinct lists int[] memberNumberTmp = new int[memberNumbersToFetch.length - memberNumbersToStoreTmp.size()]; int j = 0; for (int i=0;i nullPkFields==null || !nullPkFields.get(pos)) .toArray(); int[] pkNums = cmd.getPKMemberPositions(); for (int i=0;i 0) { // Hardcode the IN clause with values SQLExpression[] readIdExprs = new SQLExpression[tenantReadIds.length]; for (int i=0;i 0) { str.append(','); } str.append(mmds[i].getName()); } } } if (fetchingSurrogateVersion) { // Add on surrogate version column if (str.length() > 0) { str.append(","); } str.append("[VERSION]"); } if (!fetchingSurrogateVersion && numberOfFieldsToFetch == 0) { fieldsToFetch = null; sqlStatement = null; mappingDefinition = null; } else { fieldsToFetch = str.toString(); // Generate the unlocked and locked JDBC statements statementUnlocked = sqlStatement.getSQLText().toSQL(); sqlStatement.addExtension(SQLStatement.EXTENSION_LOCK_FOR_UPDATE, Boolean.TRUE); statementLocked = sqlStatement.getSQLText().toSQL(); } } /* (non-Javadoc) * @see org.datanucleus.store.rdbms.request.Request#execute(org.datanucleus.state.DNStateManager) */ public void execute(DNStateManager sm) { if (fieldsToFetch != null && NucleusLogger.PERSISTENCE.isDebugEnabled()) { // Debug information about what we are retrieving NucleusLogger.PERSISTENCE.debug(Localiser.msg("052218", IdentityUtils.getPersistableIdentityForId(sm.getInternalObjectId()), fieldsToFetch, table)); } if (((fetchingSurrogateVersion || versionFieldName != null) && numberOfFieldsToFetch == 0) && sm.isVersionLoaded()) { // Fetching only the version and it is already loaded, so do nothing } else if (statementLocked != null) { ExecutionContext ec = sm.getExecutionContext(); RDBMSStoreManager storeMgr = table.getStoreManager(); // Override with pessimistic lock where specified LockMode lockType = ec.getLockManager().getLockMode(sm.getInternalObjectId()); boolean locked = (lockType == LockMode.LOCK_PESSIMISTIC_READ || lockType == LockMode.LOCK_PESSIMISTIC_WRITE) ? true : ec.getSerializeReadForClass(sm.getClassMetaData().getFullClassName()); String statement = (locked ? statementLocked : statementUnlocked); StatementClassMapping mappingDef = mappingDefinition; /*if ((sm.isDeleting() || sm.isDetaching()) && mappingDefinition.hasChildMappingDefinitions()) { // Don't fetch any children since the object is being deleted mappingDef = mappingDefinition.cloneStatementMappingWithoutChildren(); }*/ try { ManagedConnection mconn = storeMgr.getConnectionManager().getConnection(ec); SQLController sqlControl = storeMgr.getSQLController(); try { PreparedStatement ps = sqlControl.getStatementForQuery(mconn, statement); AbstractClassMetaData cmd = sm.getClassMetaData(); try { // Provide the primary key field(s) to the JDBC statement if (cmd.getIdentityType() == IdentityType.DATASTORE) { StatementMappingIndex datastoreIdx = mappingDef.getMappingForMemberPosition(SurrogateColumnType.DATASTORE_ID.getFieldNumber()); for (int i=0;i 0) { // Using IN clause so nothing to do since hardcoded } else { // Set MultiTenancy tenant id in statement StatementMappingIndex multitenancyIdx = mappingDef.getMappingForMemberPosition(SurrogateColumnType.MULTITENANCY.getFieldNumber()); String tenantId = ec.getTenantId(); for (int i=0;i exceptions = new ArrayList<>(); exceptions.add(sqle); while ((sqle = sqle.getNextException()) != null) { exceptions.add(sqle); } throw new NucleusDataStoreException(msg, exceptions.toArray(new Throwable[exceptions.size()])); } } // Execute any mapping actions now that we have fetched the fields if (mappingCallbacks != null) { for (MappingCallbacks m : mappingCallbacks) { m.postFetch(sm); } } } /** * Method to process the supplied members of the class, adding to the SQLStatement as required. * Can recurse if some of the requested fields are persistent objects in their own right, so we take the opportunity to retrieve some of their fields. * @param sqlStatement Statement being built * @param fpClass FetchPlan for class * @param mmds MetaData for the members to fetch * @param mmdsToStore MetaData for the members to store * @param table The table to look for member mappings * @param sqlTbl The table in the SQL statement to use for selects * @param mappingDef Mapping definition for the result * @param fetchCallbacks Any additional required callbacks are added here * @param clr ClassLoader resolver * @param memberNumbersToStore Numbers of the members that need storing (populated by this call) * @return Number of members being selected in the statement */ protected int processMembersOfClass(SelectStatement sqlStatement, FetchPlanForClass fpClass, AbstractMemberMetaData[] mmds, AbstractMemberMetaData[] mmdsToStore, DatastoreClass table, SQLTable sqlTbl, StatementClassMapping mappingDef, Collection fetchCallbacks, ClassLoaderResolver clr, List memberNumbersToStore) { int number = 0; if (mmds != null) { // Process the members to fetch for (int i=0;i fetchCallbacks, SelectStatement sqlStmt, SQLTable sqlTbl, StatementClassMapping mappingDef, List memberNumbersToStore) { boolean selected = false; JavaTypeMapping mapping = table.getMemberMapping(mmd); if (mapping != null) { if (!mmd.isPrimaryKey() && mapping.includeInFetchStatement()) { // The depth is the number of levels down to load in this statement : 0 is to load just this objects fields int depth = 0; AbstractMemberMetaData mmdToUse = mmd; JavaTypeMapping mappingToUse = mapping; if (mapping instanceof SingleCollectionMapping) { // Check the wrapped type mappingToUse = ((SingleCollectionMapping) mapping).getWrappedMapping(); mmdToUse = ((SingleCollectionMapping) mapping).getWrappedMapping().getMemberMetaData(); } boolean fetchAndSaveFK = false; if (mappingToUse instanceof PersistableMapping) { // Special cases : 1-1/N-1 (FK) int recDepth = fpClass.getRecursionDepthForMember(mmd.getAbsoluteFieldNumber()); if (recDepth == FetchPlan.RECURSION_DEPTH_FK_ONLY) { // recursion-depth set as 0 (just retrieve the FK and don't instantiate the related object in the field) depth = 0; fetchAndSaveFK = true; } else { // Special case of 1-1/N-1 where we know the other side type so know what to join to and can load the related object depth = 1; if (Modifier.isAbstract(mmdToUse.getType().getModifiers())) { String typeName = mmdToUse.getTypeName(); DatastoreClass relTable = table.getStoreManager().getDatastoreClass(typeName, clr); if (relTable != null && relTable.getSurrogateMapping(SurrogateColumnType.DISCRIMINATOR, false) == null) { // 1-1 relation to base class with no discriminator and has subclasses // hence no way of determining the exact type, hence no point in fetching it String[] subclasses = table.getStoreManager().getMetaDataManager().getSubclassesForClass(typeName, false); if (subclasses != null && subclasses.length > 0) { depth = 0; } } } } } else if (mappingToUse instanceof ReferenceMapping) { ReferenceMapping refMapping = (ReferenceMapping)mappingToUse; if (refMapping.getMappingStrategy() == ReferenceMapping.PER_IMPLEMENTATION_MAPPING) { JavaTypeMapping[] subMappings = refMapping.getJavaTypeMapping(); if (subMappings != null && subMappings.length == 1) { // Support special case of reference mapping with single implementation possible depth = 1; } } } // TODO We should use the actual FetchPlan, and the max fetch depth, so then it can pull in all related objects within reach. // But this will mean we cannot cache the statement, since it is for a specific ExecutionContext // TODO If this field is a 1-1 and the other side has a discriminator or version then we really ought to fetch it SQLStatementHelper.selectMemberOfSourceInStatement(sqlStmt, mappingDef, null, sqlTbl, mmd, clr, depth, null); if (fetchAndSaveFK) { memberNumbersToStore.add(mmd.getAbsoluteFieldNumber()); } selected = true; } if (mapping instanceof MappingCallbacks) { // TODO Need to add that this mapping is for base object or base.field1, etc fetchCallbacks.add((MappingCallbacks) mapping); } } return selected; } /** * Method to process the specified member. * @param mmd MetaData for the member * @param fpClass FetchPlan for class * @param clr ClassLoader resolver * @param fetchCallbacks Any fetch callbacks to register the mapping with * @param sqlStmt The SELECT statement * @param sqlTbl The SQL table (of the candidate) * @param mappingDef The mapping definition for this statement * @param memberNumbersToStore List of member numbers that are approved to be stored * @return Whether this member is selected in the statement */ boolean processMemberToStore(AbstractMemberMetaData mmd, FetchPlanForClass fpClass, ClassLoaderResolver clr, Collection fetchCallbacks, SelectStatement sqlStmt, SQLTable sqlTbl, StatementClassMapping mappingDef, List memberNumbersToStore) { boolean selected = false; JavaTypeMapping mapping = table.getMemberMapping(mmd); if (mapping != null) { if (!mmd.isPrimaryKey() && mapping.includeInFetchStatement()) { AbstractMemberMetaData mmdToUse = mmd; JavaTypeMapping mappingToUse = mapping; if (mapping instanceof SingleCollectionMapping) { // Check the wrapped type mappingToUse = ((SingleCollectionMapping) mapping).getWrappedMapping(); mmdToUse = ((SingleCollectionMapping) mapping).getWrappedMapping().getMemberMetaData(); } if (mappingToUse instanceof PersistableMapping) { // TODO Omit members that are compound identity SQLStatementHelper.selectMemberOfSourceInStatement(sqlStmt, mappingDef, null, sqlTbl, mmdToUse, clr, 0, null); memberNumbersToStore.add(mmd.getAbsoluteFieldNumber()); selected = true; } } if (mapping instanceof MappingCallbacks) { // TODO Need to add that this mapping is for base object or base.field1, etc fetchCallbacks.add((MappingCallbacks) mapping); } } return selected; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy