Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**********************************************************************
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.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.FetchPlanForClass;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.FieldMetaData;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.InheritanceStrategy;
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.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.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.adapter.DatastoreAdapter;
import org.datanucleus.store.rdbms.discriminator.DiscriminatorUtils;
import org.datanucleus.store.rdbms.sql.SQLJoin.JoinType;
import org.datanucleus.store.rdbms.table.DatastoreClass;
import org.datanucleus.store.rdbms.table.ElementContainerTable;
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.store.schema.table.SurrogateColumnType;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.NucleusLogger;
/**
* Series of convenience methods to help the process of generating SQLStatements.
*/
public class SQLStatementHelper
{
private 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.getSQLText();
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.getValueForColumnMapping(ec.getNucleusContext(), param.getColumnNumber(), value);
}
else if (cmd.getIdentityType() == IdentityType.APPLICATION)
{
colValue = getValueForPrimaryKeyIndexOfObjectUsingReflection(value, param.getColumnNumber(), cmd, storeMgr, ec.getClassLoaderResolver());
}
}
mapping.getColumnMapping(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 passed in as query parameter! We cannot simply use mapping.setObject since it will PERSIST the object
// See also JDO TCK Assertion ID: A14.6.2-44 "Comparisons between persistent and non-persistent instances return not equal"
boolean supported = false;
/*if (cmd.getIdentityType() == IdentityType.APPLICATION)
{
// Try to use reflection to get the PK field(s) from the supplied object
String[] pkFieldNames = cmd.getPrimaryKeyMemberNames();
supported = true;
PersistableMapping pcMapping = (PersistableMapping)mapping;
JavaTypeMapping[] pkMappings = pcMapping.getJavaTypeMapping();
for (int j=0;j 1 && param.getColumnNumber() == (mapping.getNumberOfColumnMappings()-1))
{
// Set whole object and this is the last parameter entry for it, so set now
mapping.setObject(ec, ps, MappingHelper.getMappingIndices(num - mapping.getNumberOfColumnMappings() + 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 pkPosition : pkPositions)
{
AbstractMemberMetaData mmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(pkPosition);
Object memberValue = null;
if (mmd instanceof FieldMetaData)
{
memberValue = ClassUtils.getValueOfFieldByReflection(value, mmd.getName());
}
else
{
memberValue = ClassUtils.getValueOfMethodByReflection(value, ClassUtils.getJavaBeanGetterName(mmd.getName(), false));
}
if (storeMgr.getApiAdapter().isPersistable(mmd.getType()))
{
// Compound PK field, so recurse
// TODO Cater for cases without own table ("subclass-table")
AbstractClassMetaData subCmd = storeMgr.getMetaDataManager().getMetaDataForClass(mmd.getType(), clr);
DatastoreClass subTable = storeMgr.getDatastoreClass(mmd.getTypeName(), clr);
JavaTypeMapping subMapping = subTable.getIdMapping();
Object subValue = getValueForPrimaryKeyIndexOfObjectUsingReflection(memberValue, index-position, subCmd, storeMgr, clr);
if (index < position + subMapping.getNumberOfColumnMappings())
{
return subValue;
}
position = position + subMapping.getNumberOfColumnMappings();
}
else
{
// Normal PK field
if (position == index)
{
if (mmd instanceof FieldMetaData)
{
return ClassUtils.getValueOfFieldByReflection(value, mmd.getName());
}
return ClassUtils.getValueOfMethodByReflection(value, ClassUtils.getJavaBeanGetterName(mmd.getName(), false));
}
position++;
}
}
return null;
}
/**
* Method to return the SQLTable where the specified mapping (in the same table group as the provided
* SQLTable) is defined. If the statement doesn't currently join to the required table then a join will
* be added. If the required table is a superclass table then the join will be INNER. If the required
* table is a secondary table then the join will be defined by the meta-data for the secondary table.
* If this table group is NOT the candidate table group then LEFT OUTER JOIN will be used.
* @param stmt The statement
* @param sqlTbl SQLTable to start from for the supplied mapping (may be in super-table, or secondary-table of this)
* @param mapping The mapping
* @return The SQLTable for this mapping (may have been added to the statement during this method)
*/
public static SQLTable getSQLTableForMappingOfTable(SQLStatement stmt, SQLTable sqlTbl, JavaTypeMapping mapping)
{
Table table = sqlTbl.getTable();
if (table instanceof SecondaryDatastoreClass || table instanceof JoinTable)
{
// Secondary/join tables have no inheritance so ought to be correct
if (mapping.getTable() != null)
{
// Check there is no better table already present in the TableGroup for this mapping
// This can happen when we do a select of a join table and the element table is in the
// same table group, so hence already is present
SQLTable mappingSqlTbl = stmt.getTable(mapping.getTable(), sqlTbl.getGroupName());
if (mappingSqlTbl != null)
{
return mappingSqlTbl;
}
}
return sqlTbl;
}
DatastoreClass sourceTbl = (DatastoreClass)sqlTbl.getTable();
DatastoreClass mappingTbl = null;
if (mapping.getTable() != null)
{
mappingTbl = (DatastoreClass)mapping.getTable();
}
else
{
mappingTbl = sourceTbl.getBaseDatastoreClassWithMember(mapping.getMemberMetaData());
}
if (mappingTbl == sourceTbl)
{
return sqlTbl;
}
// Try to find this datastore table in the same table group
SQLTable mappingSqlTbl = stmt.getTable(mappingTbl, sqlTbl.getGroupName());
if (mappingSqlTbl == null)
{
boolean forceLeftOuter = false;
SQLTableGroup tableGrp = stmt.getTableGroup(sqlTbl.getGroupName());
if (tableGrp.getJoinType() == JoinType.LEFT_OUTER_JOIN)
{
// This group isn't the candidate group, and we joined to the candidate group using
// a left outer join originally, so use the same type for this table
forceLeftOuter = true;
}
if (mappingTbl instanceof SecondaryDatastoreClass)
{
// Secondary table, so add inner/outer based on metadata
boolean innerJoin = true;
JoinMetaData joinmd = ((SecondaryDatastoreClass)mappingTbl).getJoinMetaData();
if (joinmd != null && joinmd.isOuter() && !forceLeftOuter)
{
innerJoin = false;
}
if (innerJoin && !forceLeftOuter)
{
// Add join from {sourceTbl}.ID to {secondaryTbl}.ID
mappingSqlTbl = stmt.join(JoinType.INNER_JOIN, sqlTbl, sqlTbl.getTable().getIdMapping(), mappingTbl, null, mappingTbl.getIdMapping(), null, sqlTbl.getGroupName());
}
else
{
// Add join from {sourceTbl}.ID to {secondaryTbl}.ID
mappingSqlTbl = stmt.join(JoinType.LEFT_OUTER_JOIN, sqlTbl, sqlTbl.getTable().getIdMapping(), mappingTbl, null, mappingTbl.getIdMapping(), null, sqlTbl.getGroupName());
}
}
else
{
if (forceLeftOuter)
{
// Add join from {sourceTbl}.ID to {superclassTbl}.ID
mappingSqlTbl = stmt.join(JoinType.LEFT_OUTER_JOIN, sqlTbl, sqlTbl.getTable().getIdMapping(), mappingTbl, null, mappingTbl.getIdMapping(), null, sqlTbl.getGroupName());
}
else
{
// Add join from {sourceTbl}.ID to {superclassTbl}.ID
mappingSqlTbl = stmt.join(JoinType.INNER_JOIN, sqlTbl, sqlTbl.getTable().getIdMapping(), mappingTbl, null, mappingTbl.getIdMapping(), null, sqlTbl.getGroupName());
}
}
}
return mappingSqlTbl;
}
/**
* Method to select the identity for the candidate class.
* The supplied statement and mapping definition are updated during this method.
* Selects the datastore id (if using datastore id) as "DN_DATASTOREID",
* the version (if present) as "DN_VERSION", the discriminator (if present) as "DN_DISCRIM",
* and the application id (if using application id) as "DN_APPID_{i}"
* @param stmt The statement
* @param mappingDefinition Mapping definition for result columns
* @param candidateCmd The candidate class meta-data
*/
public static void selectIdentityOfCandidateInStatement(SelectStatement stmt, StatementClassMapping mappingDefinition, AbstractClassMetaData candidateCmd)
{
DatastoreClass candidateTbl = (DatastoreClass)stmt.getPrimaryTable().getTable();
if (candidateCmd.getIdentityType() == IdentityType.DATASTORE)
{
// Datastore-identity surrogate column
JavaTypeMapping idMapping = candidateTbl.getSurrogateMapping(SurrogateColumnType.DATASTORE_ID, false);
int[] colNumbers = stmt.select(stmt.getPrimaryTable(), idMapping, "DN_DATASTOREID", false);
if (mappingDefinition != null)
{
StatementMappingIndex datastoreIdIdx = new StatementMappingIndex(idMapping);
datastoreIdIdx.setColumnPositions(colNumbers);
mappingDefinition.addMappingForMember(SurrogateColumnType.DATASTORE_ID.getFieldNumber(), datastoreIdIdx);
}
}
else if (candidateCmd.getIdentityType() == IdentityType.APPLICATION)
{
// Application-identity column(s)
int[] pkPositions = candidateCmd.getPKMemberPositions();
String alias = "DN_APPID";
for (int i=0;i 1)
{
alias = "DN_APPID" + i;
}
int[] colNumbers = stmt.select(sqlTbl, pkMapping, alias, false);
if (mappingDefinition != null)
{
StatementMappingIndex appIdIdx = new StatementMappingIndex(pkMapping);
appIdIdx.setColumnPositions(colNumbers);
mappingDefinition.addMappingForMember(pkPositions[i], appIdIdx);
}
}
}
JavaTypeMapping verMapping = candidateTbl.getSurrogateMapping(SurrogateColumnType.VERSION, true);
if (verMapping != null)
{
// Version surrogate column (adds inner join to any required superclass table)
SQLTable versionSqlTbl = SQLStatementHelper.getSQLTableForMappingOfTable(stmt, stmt.getPrimaryTable(), verMapping);
int[] colNumbers = stmt.select(versionSqlTbl, verMapping, "DN_VERSION", false);
if (mappingDefinition != null)
{
StatementMappingIndex versionIdx = new StatementMappingIndex(verMapping);
versionIdx.setColumnPositions(colNumbers);
mappingDefinition.addMappingForMember(SurrogateColumnType.VERSION.getFieldNumber(), versionIdx);
}
}
JavaTypeMapping discrimMapping = candidateTbl.getSurrogateMapping(SurrogateColumnType.DISCRIMINATOR, true);
if (discrimMapping != null)
{
// Discriminator surrogate column (adds inner join to any required superclass table)
SQLTable discrimSqlTbl = SQLStatementHelper.getSQLTableForMappingOfTable(stmt, stmt.getPrimaryTable(), discrimMapping);
int[] colNumbers = stmt.select(discrimSqlTbl, discrimMapping, "DN_DISCRIM", false);
if (mappingDefinition != null)
{
StatementMappingIndex discrimIdx = new StatementMappingIndex(discrimMapping);
discrimIdx.setColumnPositions(colNumbers);
mappingDefinition.addMappingForMember(SurrogateColumnType.DISCRIMINATOR.getFieldNumber(), discrimIdx);
}
}
List unionStmts = stmt.getUnions();
if (unionStmts != null)
{
Iterator iter = unionStmts.iterator();
while (iter.hasNext())
{
SelectStatement 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(SelectStatement 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(SelectStatement stmt, StatementClassMapping mappingDefinition, FetchPlan fetchPlan,
SQLTable sourceSqlTbl, AbstractClassMetaData sourceCmd, int maxFetchDepth)
{
selectFetchPlanOfSourceClassInStatement(stmt, mappingDefinition, fetchPlan, sourceSqlTbl, sourceCmd, maxFetchDepth, null);
}
/**
* 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)
* @param inputJoinType Optional join type to use for subobjects (otherwise decide join type internally)
*/
public static void selectFetchPlanOfSourceClassInStatement(SelectStatement stmt, StatementClassMapping mappingDefinition, FetchPlan fetchPlan,
SQLTable sourceSqlTbl, AbstractClassMetaData sourceCmd, int maxFetchDepth, JoinType inputJoinType)
{
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.getClassLoaderResolver();
for (int fieldNumber : fieldNumbers)
{
AbstractMemberMetaData mmd = sourceCmd.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber);
selectMemberOfSourceInStatement(stmt, mappingDefinition, fetchPlan, sourceSqlTbl, mmd, clr, maxFetchDepth, inputJoinType);
}
if (sourceCmd.getIdentityType() == IdentityType.DATASTORE)
{
// Datastore-identity surrogate column
JavaTypeMapping idMapping = sourceTbl.getSurrogateMapping(SurrogateColumnType.DATASTORE_ID, false);
int[] colNumbers = stmt.select(sourceSqlTbl, idMapping, null);
if (mappingDefinition != null)
{
StatementMappingIndex datastoreIdIdx = new StatementMappingIndex(idMapping);
datastoreIdIdx.setColumnPositions(colNumbers);
mappingDefinition.addMappingForMember(SurrogateColumnType.DATASTORE_ID.getFieldNumber(), datastoreIdIdx);
}
}
JavaTypeMapping verMapping = sourceTbl.getSurrogateMapping(SurrogateColumnType.VERSION, true);
if (verMapping != null)
{
// Version surrogate column (adds inner join to any required superclass table)
SQLTable versionSqlTbl = SQLStatementHelper.getSQLTableForMappingOfTable(stmt, sourceSqlTbl, verMapping);
int[] colNumbers = stmt.select(versionSqlTbl, verMapping, null);
if (mappingDefinition != null)
{
StatementMappingIndex versionIdx = new StatementMappingIndex(verMapping);
versionIdx.setColumnPositions(colNumbers);
mappingDefinition.addMappingForMember(SurrogateColumnType.VERSION.getFieldNumber(), versionIdx);
}
}
JavaTypeMapping discrimMapping = sourceTbl.getSurrogateMapping(SurrogateColumnType.DISCRIMINATOR, true);
if (discrimMapping != null)
{
// Discriminator surrogate column (adds inner join to any required superclass table)
SQLTable discrimSqlTbl = SQLStatementHelper.getSQLTableForMappingOfTable(stmt, sourceSqlTbl, discrimMapping);
int[] colNumbers = stmt.select(discrimSqlTbl, discrimMapping, null);
if (mappingDefinition != null)
{
StatementMappingIndex discrimIdx = new StatementMappingIndex(discrimMapping);
discrimIdx.setColumnPositions(colNumbers);
mappingDefinition.addMappingForMember(SurrogateColumnType.DISCRIMINATOR.getFieldNumber(), discrimIdx);
}
}
}
/**
* Method to select the specified member (field/property) of the source table in the passed SQL
* statement. This populates the mappingDefinition with the column details for this member.
* @param stmt The SQL statement
* @param mappingDefinition Mapping definition for the results (will be populated by any
* selected mappings if provided as input)
* @param fetchPlan FetchPlan
* @param sourceSqlTbl Table that has the member (or a super-table/secondary-table of this table)
* @param mmd Meta-data for the field/property in the source that we are selecting
* @param clr ClassLoader resolver
* @param maxFetchPlanLimit Max fetch depth from this point to select (0 implies no other objects)
* @param inputJoinType Optional join type to use for subobjects (otherwise decide join type internally)
*/
public static void selectMemberOfSourceInStatement(SelectStatement stmt, StatementClassMapping mappingDefinition, FetchPlan fetchPlan,
SQLTable sourceSqlTbl, AbstractMemberMetaData mmd, ClassLoaderResolver clr, int maxFetchPlanLimit, JoinType inputJoinType)
{
// Determine whether to select sub-objects
boolean selectSubobjects = (maxFetchPlanLimit > 0);
if (fetchPlan != null)
{
FetchPlanForClass fpClass = fetchPlan.getFetchPlanForClass(mmd.getAbstractClassMetaData());
int recDepth = fpClass.getRecursionDepthForMember(mmd.getAbsoluteFieldNumber());
if (RelationType.isRelationSingleValued(mmd.getRelationType(clr)))
{
if (recDepth == FetchPlan.RECURSION_DEPTH_FK_ONLY)
{
// User has marked this member as recursion-depth=0 meaning pull in just the FK and dont join to the sub-object
selectSubobjects = false;
}
}
}
// 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.getNumberOfColumnMappings() > 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, inputJoinType);
}
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]);
ElementContainerTable collTable = (ElementContainerTable)joinTable;
JavaTypeMapping selectMapping = collTable.getOwnerMapping();
SQLTable joinSqlTbl = null;
if (stmt.getPrimaryTable().getTable() != joinTable)
{
// Join to the join table
JavaTypeMapping referenceMapping = collTable.getElementMapping();
joinSqlTbl = stmt.join(JoinType.LEFT_OUTER_JOIN, sourceSqlTbl, sourceSqlTbl.getTable().getIdMapping(), collTable, null, referenceMapping,
null, tableGroupName, true);
}
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, inputJoinType);
}
}
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
{
String typeName = mmd.isSingleCollection() ? mmd.getCollection().getElementType() : mmd.getTypeName();
clsNames = new String[] { typeName };
}
DatastoreClass relatedTbl = storeMgr.getDatastoreClass(clsNames[0], clr);
JavaTypeMapping relatedMapping = relatedTbl.getMemberMapping(relatedMmd);
JavaTypeMapping relatedDiscrimMapping = relatedTbl.getSurrogateMapping(SurrogateColumnType.DISCRIMINATOR, 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 (String clsName : clsNames)
{
List values = DiscriminatorUtils.getDiscriminatorValuesForMember(clsName, relatedDiscrimMapping, storeMgr, clr);
if (discValueList == null)
{
discValueList = values;
}
else
{
discValueList.addAll(values);
}
}
if (discValueList != null)
{
discrimValues = discValueList.toArray(new Object[discValueList.size()]);
}
}
else if (relatedTbl != relatedMapping.getTable())
{
// The relation is to a base class table, and the type stored is a sub-class
relatedTypeMapping = relatedTbl.getIdMapping();
}
SQLTable relatedSqlTbl = null;
if (relatedTypeMapping == null)
{
// Join the 1-1 relation, if not already joined
relatedSqlTbl = stmt.getTable(relatedTbl, tableGroupName);
if (relatedSqlTbl == null)
{
JoinType joinType = getJoinTypeForOneToOneRelationJoin(sourceSqlTbl.getTable().getIdMapping(), sourceSqlTbl, inputJoinType);
if (joinType == JoinType.LEFT_OUTER_JOIN || joinType == JoinType.RIGHT_OUTER_JOIN)
{
inputJoinType = joinType;
}
relatedSqlTbl = addJoinForOneToOneRelation(stmt, sourceSqlTbl.getTable().getIdMapping(), sourceSqlTbl,
relatedMapping, relatedTbl, null, discrimValues, tableGroupName, joinType);
}
// Select the id mapping in the related table
int[] colNumbers = stmt.select(relatedSqlTbl, relatedTbl.getIdMapping(), null);
stmtMapping.setColumnPositions(colNumbers);
}
else
{
DatastoreClass relationTbl = (DatastoreClass)relatedMapping.getTable();
if (relatedTbl != relatedMapping.getTable())
{
if (relatedMapping.isNullable())
{
// Nullable - left outer join from {sourceTable}.ID to {relatedBaseTable}.FK
// and inner join from {relatedBaseTable}.ID to {relatedTable}.ID
// (joins the relation and restricts to the right type)
relatedSqlTbl = stmt.join(JoinType.LEFT_OUTER_JOIN, sourceSqlTbl, sourceSqlTbl.getTable().getIdMapping(),
relatedMapping.getTable(), null, relatedMapping, null, tableGroupName, true);
relatedSqlTbl = stmt.join(JoinType.INNER_JOIN, relatedSqlTbl, relatedMapping.getTable().getIdMapping(),
relatedTbl, null, relatedTbl.getIdMapping(), null, tableGroupName, true);
}
else
{
// Not nullable - inner join from {sourceTable}.ID to {relatedBaseTable}.FK
// and inner join from {relatedBaseTable}.ID to {relatedTable}.ID
// (joins the relation and restricts to the right type)
relatedSqlTbl = stmt.join(JoinType.INNER_JOIN, sourceSqlTbl, sourceSqlTbl.getTable().getIdMapping(),
relatedMapping.getTable(), null, relatedMapping, null, tableGroupName, true);
relatedSqlTbl = stmt.join(JoinType.INNER_JOIN, relatedSqlTbl, relatedMapping.getTable().getIdMapping(),
relatedTbl, null, relatedTbl.getIdMapping(), null, tableGroupName, true);
}
}
else
{
// Join the 1-1 relation, if not already joined
relatedSqlTbl = stmt.getTable(relatedTbl, tableGroupName);
if (relatedSqlTbl == null)
{
JoinType joinType = getJoinTypeForOneToOneRelationJoin(sourceSqlTbl.getTable().getIdMapping(), sourceSqlTbl, inputJoinType);
if (joinType == JoinType.LEFT_OUTER_JOIN || joinType == JoinType.RIGHT_OUTER_JOIN)
{
inputJoinType = joinType;
}
relatedSqlTbl = addJoinForOneToOneRelation(stmt, sourceSqlTbl.getTable().getIdMapping(), sourceSqlTbl,
relatedMapping, relationTbl, null, null, tableGroupName, joinType);
}
}
// Select the id mapping in the subclass of the related table
// Note this adds an inner join from relatedTable to its subclass
relatedSqlTbl = SQLStatementHelper.getSQLTableForMappingOfTable(stmt, relatedSqlTbl, relatedTbl.getIdMapping());
int[] colNumbers = stmt.select(relatedSqlTbl, relatedTbl.getIdMapping(), null);
stmtMapping.setColumnPositions(colNumbers);
}
if (selectSubobjects && !mmd.isSerialized() && !mmd.isEmbedded())
{
// Select the fetch-plan fields of the related object
StatementClassMapping subMappingDefinition = new StatementClassMapping(null, mmd.getName());
selectFetchPlanOfSourceClassInStatement(stmt, subMappingDefinition, fetchPlan, relatedSqlTbl, relatedMmd.getAbstractClassMetaData(), maxFetchPlanLimit-1, inputJoinType);
if (mappingDefinition != null)
{
mappingDefinition.addMappingDefinitionForMember(mmd.getAbsoluteFieldNumber(), subMappingDefinition);
}
}
}
else if (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
// Add left outer join from {sourceTable}.ID to {joinTable}.ELEM_FK
Table joinTable = storeMgr.getTable(relatedMmds[0]);
ElementContainerTable collTable = (ElementContainerTable)joinTable;
JavaTypeMapping selectMapping = collTable.getOwnerMapping();
SQLTable joinSqlTbl = null;
if (stmt.getPrimaryTable().getTable() != joinTable)
{
// Join to the join table
JavaTypeMapping referenceMapping = collTable.getElementMapping();
if (referenceMapping instanceof ReferenceMapping)
{
// Join table has a reference mapping pointing to our table, so get the submapping for the implementation
ReferenceMapping refMap = (ReferenceMapping)referenceMapping;
Class implType = clr.classForName(mmd.getClassName(true));
referenceMapping = refMap.getJavaTypeMappingForType(implType);
}
joinSqlTbl = stmt.join(JoinType.LEFT_OUTER_JOIN, sourceSqlTbl, sourceSqlTbl.getTable().getIdMapping(),
collTable, null, referenceMapping, null, tableGroupName + "_JOIN", true);
}
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 Select fetch plan fields of this related object
}
else if (relationType == RelationType.MANY_TO_ONE_UNI)
{
// Add left outer join from {sourceTable}.ID to {joinTable}.OWNER_FK
PersistableJoinTable joinTable = (PersistableJoinTable) storeMgr.getTable(mmd);
SQLTable joinSqlTbl = stmt.join(JoinType.LEFT_OUTER_JOIN, sourceSqlTbl, sourceSqlTbl.getTable().getIdMapping(),
joinTable, null, joinTable.getOwnerMapping(), null, tableGroupName + "_JOIN", true);
int[] colNumbers = stmt.select(joinSqlTbl, joinTable.getRelatedMapping(), null);
stmtMapping.setColumnPositions(colNumbers);
// TODO Select fetch plan fields of this related object
}
}
if (mappingDefinition != null)
{
mappingDefinition.addMappingForMember(mmd.getAbsoluteFieldNumber(), stmtMapping);
}
}
}
/**
* Convenience method to join to and select all required FP fields of a related object where linked via an FK at this side.
* Note that the FetchPlan can have null passed in here (27/04/2021) so cater for it.
* @return Whether the caller should select the FK themselves (i.e we haven't selected anything)
*/
private static boolean selectFetchPlanFieldsOfFKRelatedObject(SelectStatement stmt, StatementClassMapping mappingDefinition, FetchPlan fetchPlan,
SQLTable sourceSqlTbl, AbstractMemberMetaData mmd, ClassLoaderResolver clr, int maxFetchPlanLimit, JavaTypeMapping m, String tableGroupName,
StatementMappingIndex stmtMapping, SQLTable sqlTbl, JoinType inputJoinType)
{
boolean selectFK = true;
RDBMSStoreManager storeMgr = stmt.getRDBMSManager();
Class type = mmd.isSingleCollection() ? clr.classForName(mmd.getCollection().getElementType()) : mmd.getType();
if (m instanceof ReferenceMapping)
{
ReferenceMapping refMapping = (ReferenceMapping)m;
if (refMapping.getMappingStrategy() == ReferenceMapping.PER_IMPLEMENTATION_MAPPING && refMapping.getJavaTypeMapping().length == 1)
{
JavaTypeMapping[] subMappings = refMapping.getJavaTypeMapping();
if (subMappings != null && subMappings.length == 1)
{
// Special case of reference mapping with single FK implementation
type = clr.classForName(refMapping.getJavaTypeMapping()[0].getType());
}
}
}
// select fetch plan fields of this object
AbstractClassMetaData relatedCmd = storeMgr.getMetaDataManager().getMetaDataForClass(type, clr);
if (relatedCmd != null)
{
if (relatedCmd.isEmbeddedOnly())
{
return true;
}
if (relatedCmd.getBaseAbstractClassMetaData().getInheritanceMetaData().getStrategy() == InheritanceStrategy.COMPLETE_TABLE)
{
// Related object uses complete-table
Collection relSubclassNames = storeMgr.getSubClassesForClass(relatedCmd.getFullClassName(), true, clr);
if (relatedCmd.isMappedSuperclass() && relSubclassNames.size() > 1)
{
// Multiple possible related types and we don't have the FK so omit
return true;
}
else if (!relatedCmd.isMappedSuperclass() && relSubclassNames.size() > 0)
{
// Multiple possible related types and we don't have the FK so omit
return true;
}
// TODO Maybe do a LEFT OUTER JOIN to each possible?
}
// Check if we are only fetching the PK field(s), in which case we can avoid any join
if (fetchPlan != null)
{
FetchPlanForClass relatedFP = fetchPlan.getFetchPlanForClass(relatedCmd);
int[] fpFieldNums = relatedFP.getMemberNumbers();
if (relatedCmd.getIdentityType() == IdentityType.APPLICATION)
{
int[] pkFieldNums = relatedCmd.getPKMemberPositions();
if (fpFieldNums != null && pkFieldNums != null && fpFieldNums.length == pkFieldNums.length)
{
boolean equal = true;
for (int i=0;i 1)
{
// TODO Fix this
NucleusLogger.QUERY.warn("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
JoinType joinType = getJoinTypeForOneToOneRelationJoin(m, sqlTbl, inputJoinType);
if (joinType == JoinType.LEFT_OUTER_JOIN || joinType == JoinType.RIGHT_OUTER_JOIN)
{
inputJoinType = joinType;
}
relatedSqlTbl = addJoinForOneToOneRelation(stmt, m, sqlTbl, relatedTbl.getIdMapping(), relatedTbl, null, null, tableGroupName, joinType);
}
StatementClassMapping subMappingDefinition =
new StatementClassMapping(mmd.getClassName(), mmd.getName());
selectFetchPlanOfSourceClassInStatement(stmt, subMappingDefinition, fetchPlan, relatedSqlTbl, relatedCmd, maxFetchPlanLimit-1, inputJoinType);
if (mappingDefinition != null)
{
if (relatedCmd.getIdentityType() == IdentityType.APPLICATION)
{
int[] pkFields = relatedCmd.getPKMemberPositions();
int[] pkCols = new int[m.getNumberOfColumnMappings()];
int pkColNo = 0;
for (int pkField : pkFields)
{
StatementMappingIndex pkIdx = subMappingDefinition.getMappingForMemberPosition(pkField);
int[] pkColNumbers = pkIdx.getColumnPositions();
for (int pkColNumber : pkColNumbers)
{
pkCols[pkColNo] = pkColNumber;
pkColNo++;
}
}
selectFK = false;
stmtMapping.setColumnPositions(pkCols);
}
else if (relatedCmd.getIdentityType() == IdentityType.DATASTORE)
{
StatementMappingIndex pkIdx = subMappingDefinition.getMappingForMemberPosition(SurrogateColumnType.DATASTORE_ID.getFieldNumber());
selectFK = false;
stmtMapping.setColumnPositions(pkIdx.getColumnPositions());
}
mappingDefinition.addMappingDefinitionForMember(mmd.getAbsoluteFieldNumber(), subMappingDefinition);
}
}
else
{
// TODO 1-1 interface relation
}
return selectFK;
}
/**
* Convenience method to add a join across a 1-1 relation to the provided SQLStatement.
* Returns the SQLTable for the other side of the relation.
* If the join type isn't provided and ID is in the target and FK in the source and the FK is
* not nullable then an "inner join" is used, otherwise a "left outer join" is used to cater
* for null values. If the join type is supplied then it is respected.
* @param stmt The SQLStatement
* @param sourceMapping Mapping of the relation in the source table
* @param sourceSqlTbl Source table in the SQLStatement
* @param targetMapping Mapping of the relation in the target table
* @param targetTable Target table in the datastore
* @param targetAlias Alias for target table to use in SQLStatement
* @param discrimValues Any discriminator values to apply to restrict the target side (if any)
* @param targetTablegroupName Name of the tablegroup that the target SQLTable should be in (if known)
* @param joinType Type of join to use (if known). If not known will use based on whether nullable
* @return The SQLTable for the target once the join is added
*/
public static SQLTable addJoinForOneToOneRelation(SQLStatement stmt,
JavaTypeMapping sourceMapping, SQLTable sourceSqlTbl,
JavaTypeMapping targetMapping, Table targetTable, String targetAlias,
Object[] discrimValues, String targetTablegroupName, JoinType joinType)
{
if (joinType == null)
{
// Fallback to nullability logic since no type specified
joinType = getJoinTypeForOneToOneRelationJoin(sourceMapping, sourceSqlTbl, joinType);
// TODO Cater for JDOQL where user hasn't checked for null on the relation and this is an OUTER join (i.e add WHERE check for null).
// Note that we cannot just do this here since it applies it to the WHERE clause at the "top-level" yet this may be for some branch of the WHERE clause only
//if (joinType == JoinType.LEFT_OUTER_JOIN)
//{
//SQLExpression fkExpr = stmt.getSQLExpressionFactory().newExpression(stmt, sourceSqlTbl, sourceMapping);
//SQLExpression nullLit = stmt.getSQLExpressionFactory().newLiteral(stmt, null, null);
//stmt.whereAnd(fkExpr.ne(nullLit), true);
//}
}
SQLTable targetSqlTbl = null;
if (joinType == JoinType.INNER_JOIN || joinType == JoinType.LEFT_OUTER_JOIN || joinType == JoinType.RIGHT_OUTER_JOIN)
{
// join from {sourceTable}.{key} to {relatedTable}.{key}
targetSqlTbl = stmt.join(joinType, sourceSqlTbl, sourceMapping, targetTable, targetAlias, targetMapping, discrimValues, targetTablegroupName);
}
else if (joinType == JoinType.CROSS_JOIN)
{
// cross join to {relatedTable}.{key}
targetSqlTbl = stmt.join(JoinType.CROSS_JOIN, null, null, null, targetTable, targetAlias, null, null, null, targetTablegroupName, true, null);
}
return targetSqlTbl;
}
/**
* Convenience method to return the join type to use for the specified 1-1 relation.
* If the join type isn't provided and ID is in the target and FK in the source and the FK is
* not nullable then an "inner join" is used, otherwise a "left outer join" is used to cater for null values.
* @param sourceMapping Mapping of the relation in the source table
* @param sourceSqlTbl Source table in the SQLStatement
* @param joinType Join type to use if already known; will be returned if not null
* @return Join type that should be used for this 1-1 relation
*/
public static JoinType getJoinTypeForOneToOneRelationJoin(JavaTypeMapping sourceMapping, SQLTable sourceSqlTbl, JoinType joinType)
{
if (joinType == null)
{
joinType = JoinType.LEFT_OUTER_JOIN; // Default to LEFT OUTER join
if (sourceMapping != sourceSqlTbl.getTable().getIdMapping())
{
// ID in target, FK in source, so check for not nullable in source (what we are selecting)
// If nullable then LEFT OUTER join, otherwise INNER join
joinType = (sourceMapping.isNullable() ? JoinType.LEFT_OUTER_JOIN : JoinType.INNER_JOIN);
}
}
return joinType;
}
}