/**********************************************************************
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.scostore;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ListIterator;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.Transaction;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.DiscriminatorStrategy;
import org.datanucleus.metadata.OrderMetaData.FieldOrder;
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.JavaTypeMapping;
import org.datanucleus.store.mapped.mapping.ReferenceMapping;
import org.datanucleus.store.mapped.scostore.JoinListStore;
import org.datanucleus.store.query.ResultObjectFactory;
import org.datanucleus.store.rdbms.RDBMSStoreManager;
import org.datanucleus.store.rdbms.SQLController;
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.store.rdbms.table.CollectionTable;
import org.datanucleus.util.ClassUtils;
/**
* RDBMS-specific implementation of a {@link JoinListStore}
*/
public class RDBMSJoinListStore extends JoinListStore
{
/** 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 an RDBMS implementation of a join list store.
* @param mmd Metadata for the owning field/property
* @param collTable The Join table
* @param clr ClassLoader resolver
*/
public RDBMSJoinListStore(AbstractMemberMetaData mmd, CollectionTable collTable, ClassLoaderResolver clr)
{
super(mmd, clr, collTable, collTable.getOwnerMapping(),
collTable.getElementMapping(), collTable.getOrderMapping(),
collTable.getRelationDiscriminatorMapping(), collTable.getRelationDiscriminatorValue(),
collTable.isEmbeddedElement(), collTable.isSerialisedElement(),
new RDBMSJoinListStoreSpecialization(LOCALISER, clr, (RDBMSStoreManager)collTable.getStoreManager()));
}
/**
* Accessor for an iterator through the list elements.
* @param ownerSM State Manager for the container.
* @param startIdx The start point in the list (only for indexed lists).
* @param endIdx End index in the list (only for indexed lists).
* @return The List Iterator
*/
protected ListIterator listIterator(ObjectProvider ownerSM, int startIdx, int endIdx)
{
ExecutionContext ec = ownerSM.getExecutionContext();
Transaction tx = ec.getTransaction();
boolean useUpdateLock = tx.lockReadObjects();
StatementClassMapping resultDefinition = null;
StatementParameterMapping paramDefinition = null;
String stmt = null;
if (startIdx < 0 && endIdx < 0)
{
// Iteration of all elements - cached
if (iteratorStmtLocked == null)
{
synchronized (this)
{
// Generate the statement, and statement mapping/parameter information
iteratorMappingDef = new StatementClassMapping();
iteratorMappingParams = new StatementParameterMapping();
SQLStatement sqlStmt = getSQLStatementForIterator(ownerSM, startIdx, endIdx,
iteratorMappingDef, iteratorMappingParams);
iteratorStmtUnlocked = sqlStmt.getSelectStatement().toSQL();
sqlStmt.addExtension("lock-for-update", true);
iteratorStmtLocked = sqlStmt.getSelectStatement().toSQL();
}
}
resultDefinition = iteratorMappingDef;
paramDefinition = iteratorMappingParams;
stmt = (useUpdateLock ? iteratorStmtLocked : iteratorStmtUnlocked);
}
else
{
// Iteration over a range so generate statement on the fly (uncached)
resultDefinition = new StatementClassMapping();
paramDefinition = new StatementParameterMapping();
SQLStatement sqlStmt = getSQLStatementForIterator(ownerSM, startIdx, endIdx, resultDefinition,
paramDefinition);
sqlStmt.addExtension("lock-for-update", useUpdateLock);
stmt = sqlStmt.getSelectStatement().toSQL();
}
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 = paramDefinition.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
{
if (elementsAreEmbedded || elementsAreSerialised)
{
// No ResultObjectFactory needed - handled by SetStoreIterator
return new RDBMSListStoreIterator(ownerSM, rs, null, this);
}
else if (elementMapping instanceof ReferenceMapping)
{
// No ResultObjectFactory needed - handled by SetStoreIterator
return new RDBMSListStoreIterator(ownerSM, rs, null, this);
}
else
{
ResultObjectFactory rof = storeMgr.newResultObjectFactory(emd,
resultDefinition, false, null, clr.classForName(elementType));
return new RDBMSListStoreIterator(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 join table.
* Populates the resultMapping and paramMapping argument objects.
* @param ownerSM StateManager for the owner object
* @param startIdx start index to be retrieved (inclusive). Only for indexed list
* @param endIdx end index to be retrieved (exclusive). Only for indexed list
* @param resultMapping Mapping for the candidate result columns
* @param paramMapping Mapping for the input parameters
* @return The SQLStatement
*/
protected SQLStatement getSQLStatementForIterator(ObjectProvider ownerSM, int startIdx, int endIdx,
StatementClassMapping resultMapping, StatementParameterMapping paramMapping)
{
SQLStatement sqlStmt = null;
final ClassLoaderResolver clr = ownerSM.getExecutionContext().getClassLoaderResolver();
RDBMSStoreManager storeMgr = (RDBMSStoreManager)this.storeMgr;
if (elementsAreEmbedded || elementsAreSerialised)
{
// Element = embedded, serialised (maybe Non-PC)
// Just select the join table since we're going to return the embedded/serialised columns from it
sqlStmt = new SQLStatement(storeMgr, containerTable, null, null);
sqlStmt.setClassLoaderResolver(clr);
// Select the element column - first select is assumed by ListStoreIterator
sqlStmt.select(sqlStmt.getPrimaryTable(), elementMapping, null);
}
else if (elementMapping instanceof ReferenceMapping)
{
// Element = Reference type (interface/Object)
// Just select the join table since we're going to return the implementation id columns only
sqlStmt = new SQLStatement(storeMgr, containerTable, null, null);
sqlStmt.setClassLoaderResolver(clr);
// Select the reference column(s) - first select is assumed by ListStoreIterator
sqlStmt.select(sqlStmt.getPrimaryTable(), elementMapping, null);
}
else
{
// Element = PC
// Join to the element table(s)
for (int i = 0; i < elementInfo.length; i++)
{
// TODO This will only work if all element types have a discriminator
final int elementNo = i;
final Class elementCls = clr.classForName(elementInfo[elementNo].getClassName());
SQLStatement elementStmt = null;
if (elementInfo[elementNo].getDiscriminatorStrategy() != null &&
elementInfo[elementNo].getDiscriminatorStrategy() != DiscriminatorStrategy.NONE)
{
// The element uses a discriminator so just use that in the SELECT
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 j = 0; j < clsNames.length; j++)
{
cls[j] = clr.classForName(clsNames[j]);
}
StatementGenerator stmtGen = new DiscriminatorStatementGenerator(storeMgr, clr, cls,
true, null, null, containerTable, null, elementMapping);
if (allowNulls)
{
stmtGen.setOption(StatementGenerator.OPTION_ALLOW_NULLS);
}
elementStmt = stmtGen.getStatement();
}
else
{
StatementGenerator stmtGen = new DiscriminatorStatementGenerator(storeMgr, clr, elementCls,
true, null, null, containerTable, null, elementMapping);
if (allowNulls)
{
stmtGen.setOption(StatementGenerator.OPTION_ALLOW_NULLS);
}
elementStmt = stmtGen.getStatement();
}
iterateUsingDiscriminator = true;
}
else
{
// No discriminator, but subclasses so use UNIONs
StatementGenerator stmtGen = new UnionStatementGenerator(storeMgr, clr, elementCls, true, null,
null, containerTable, null, elementMapping);
stmtGen.setOption(StatementGenerator.OPTION_SELECT_NUCLEUS_TYPE);
resultMapping.setNucleusTypeColumnName(UnionStatementGenerator.NUC_TYPE_COLUMN);
elementStmt = stmtGen.getStatement();
}
if (sqlStmt == null)
{
sqlStmt = elementStmt;
}
else
{
sqlStmt.union(elementStmt);
}
}
// Select the required fields
SQLTable elementSqlTbl = sqlStmt.getTable(elementInfo[0].getDatastoreClass(),
sqlStmt.getPrimaryTable().getGroupName());
SQLStatementHelper.selectFetchPlanOfSourceClassInStatement(sqlStmt, resultMapping,
ownerSM.getExecutionContext().getFetchPlan(), elementSqlTbl, emd, 0);
}
// Apply condition on join-table owner field to filter by owner
SQLExpressionFactory exprFactory = storeMgr.getSQLExpressionFactory();
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 (indexedList)
{
// "Indexed List" so allow restriction on returned indexes
boolean needsOrdering = true;
if (startIdx == -1 && endIdx == -1)
{
// Just restrict to >= 0 so we don't get any disassociated elements
SQLExpression indexExpr = exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), orderMapping);
SQLExpression indexVal = exprFactory.newLiteral(sqlStmt, orderMapping, 0);
sqlStmt.whereAnd(indexExpr.ge(indexVal), true);
}
else if (startIdx >= 0 && endIdx == startIdx)
{
// Particular index required so add restriction
needsOrdering = false;
SQLExpression indexExpr = exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), orderMapping);
SQLExpression indexVal = exprFactory.newLiteral(sqlStmt, orderMapping, startIdx);
sqlStmt.whereAnd(indexExpr.eq(indexVal), true);
}
else
{
// Add restrictions on start/end indices as required
if (startIdx >= 0)
{
SQLExpression indexExpr = exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), orderMapping);
SQLExpression indexVal = exprFactory.newLiteral(sqlStmt, orderMapping, startIdx);
sqlStmt.whereAnd(indexExpr.ge(indexVal), true);
}
else
{
// Just restrict to >= 0 so we don't get any disassociated elements
SQLExpression indexExpr = exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), orderMapping);
SQLExpression indexVal = exprFactory.newLiteral(sqlStmt, orderMapping, 0);
sqlStmt.whereAnd(indexExpr.ge(indexVal), true);
}
if (endIdx >= 0)
{
SQLExpression indexExpr2 = exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), orderMapping);
SQLExpression indexVal2 = exprFactory.newLiteral(sqlStmt, orderMapping, endIdx);
sqlStmt.whereAnd(indexExpr2.lt(indexVal2), true);
}
}
if (needsOrdering)
{
// 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);
}
}
else
{
// Apply ordering defined by <order-by>
if (elementInfo != null && elementInfo.length > 0)
{
DatastoreClass elementTbl = elementInfo[0].getDatastoreClass();
FieldOrder[] orderComponents = ownerMemberMetaData.getOrderMetaData().getFieldOrders();
SQLExpression[] orderExprs = new SQLExpression[orderComponents.length];
boolean[] orderDirs = new boolean[orderComponents.length];
for (int i=0;i<orderComponents.length;i++)
{
String fieldName = orderComponents[i].getFieldName();
JavaTypeMapping fieldMapping = elementTbl.getMemberMapping(elementInfo[0].getAbstractClassMetaData().getMetaDataForMember(fieldName));
orderDirs[i] = !orderComponents[i].isForward();
SQLTable fieldSqlTbl = SQLStatementHelper.getSQLTableForMappingOfTable(sqlStmt, sqlStmt.getPrimaryTable(), fieldMapping);
orderExprs[i] = exprFactory.newExpression(sqlStmt, fieldSqlTbl, fieldMapping);
}
sqlStmt.setOrdering(orderExprs, orderDirs);
}
}
// 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<paramPositions.length;k++)
{
paramPositions[k] = inputParamNum++;
}
ownerIdx.addParameterOccurrence(paramPositions);
}
}
else
{
int[] paramPositions = new int[ownerMapping.getNumberOfDatastoreMappings()];
for (int k=0;k<paramPositions.length;k++)
{
paramPositions[k] = inputParamNum++;
}
ownerIdx.addParameterOccurrence(paramPositions);
}
paramMapping.addMappingForParameter("owner", ownerIdx);
return sqlStmt;
}
}