/**********************************************************************
Copyright (c) 2007 Andy Jefferson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contributors:
...
**********************************************************************/
package org.datanucleus.store.rdbms.scostore;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.ObjectProvider;
import org.datanucleus.store.connection.ManagedConnection;
import org.datanucleus.store.mapped.DatastoreContainerObject;
import org.datanucleus.store.mapped.exceptions.MappedDatastoreException;
import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
import org.datanucleus.store.mapped.scostore.ElementContainerStore;
import org.datanucleus.store.mapped.scostore.JoinListStoreSpecialization;
import org.datanucleus.store.rdbms.RDBMSStoreManager;
import org.datanucleus.store.rdbms.SQLController;
import org.datanucleus.store.rdbms.fieldmanager.DynamicSchemaFieldManager;
import org.datanucleus.store.rdbms.mapping.RDBMSMapping;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
/**
* RDBMS-specific implementation of a {@link JoinListStoreSpecialization}.
*/
public class RDBMSJoinListStoreSpecialization extends RDBMSAbstractListStoreSpecialization implements JoinListStoreSpecialization
{
RDBMSJoinListStoreSpecialization(Localiser localiser, ClassLoaderResolver clr, RDBMSStoreManager storeMgr)
{
super(localiser, clr, storeMgr);
}
/**
* Generate statement for removing a collection of items from the List.
* <PRE>
* DELETE FROM LISTTABLE
* WHERE (OWNERCOL=? AND ELEMENTCOL=?) OR
* (OWNERCOL=? AND ELEMENTCOL=?) OR
* (OWNERCOL=? AND ELEMENTCOL=?)
* </PRE>
* @param elements Collection of elements to remove
* @return Statement for deleting items from the List.
*/
protected String getRemoveAllStmt(Collection elements, ElementContainerStore ecs)
{
if (elements == null || elements.size() == 0)
{
return null;
}
JavaTypeMapping ownerMapping = ecs.getOwnerMapping();
DatastoreContainerObject containerTable = ecs.getContainerTable();
boolean elementsAreSerialised = ecs.isElementsAreSerialised();
JavaTypeMapping elementMapping = ecs.getElementMapping();
JavaTypeMapping relationDiscriminatorMapping = ecs.getRelationDiscriminatorMapping();
StringBuffer stmt = new StringBuffer();
stmt.append("DELETE FROM ");
stmt.append(containerTable.toString());
stmt.append(" WHERE ");
Iterator elementsIter = elements.iterator();
boolean first = true;
while (elementsIter.hasNext())
{
elementsIter.next(); // Not really used at the moment except to size the Collection
if (first)
{
stmt.append("(");
}
else
{
stmt.append(" OR (");
}
for (int i = 0; i < ownerMapping.getNumberOfDatastoreMappings(); i++)
{
if (i > 0)
{
stmt.append(" AND ");
}
stmt.append(ownerMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString());
stmt.append(" = ");
stmt.append(((RDBMSMapping) ownerMapping.getDatastoreMapping(i)).getUpdateInputParameter());
}
for (int i = 0; i < elementMapping.getNumberOfDatastoreMappings(); i++)
{
stmt.append(" AND ");
stmt.append(elementMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString());
if (elementsAreSerialised)
{
// Can't directly compare serialised element fields
stmt.append(" LIKE ");
}
else
{
stmt.append(" = ");
}
stmt.append(((RDBMSMapping) elementMapping.getDatastoreMapping(i)).getUpdateInputParameter());
}
if (relationDiscriminatorMapping != null)
{
for (int i = 0; i < relationDiscriminatorMapping.getNumberOfDatastoreMappings(); i++)
{
stmt.append(" AND ");
stmt.append(relationDiscriminatorMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString());
stmt.append(" = ");
stmt.append(((RDBMSMapping) relationDiscriminatorMapping.getDatastoreMapping(i)).getUpdateInputParameter());
}
}
stmt.append(")");
first = false;
}
return stmt.toString();
}
public void removeAt(ObjectProvider sm, int index, int size, ElementContainerStore ecs)
{
internalRemoveAt(sm, index, getRemoveAtStmt(ecs), size, ecs);
}
public boolean removeAll(int currentListSize, int[] indices, Collection elements, ObjectProvider sm,
ElementContainerStore ecs)
{
JavaTypeMapping elementMapping = ecs.getElementMapping();
JavaTypeMapping relationDiscriminatorMapping = ecs.getRelationDiscriminatorMapping();
// Remove the specified elements from the join table
boolean modified = false;
String removeAllStmt = getRemoveAllStmt(elements, ecs);
SQLController sqlControl = storeMgr.getSQLController();
try
{
ExecutionContext ec = sm.getExecutionContext();
ManagedConnection mconn = storeMgr.getConnection(ec);
try
{
PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, removeAllStmt, false);
try
{
int jdbcPosition = 1;
Iterator iter = elements.iterator();
while (iter.hasNext())
{
Object element = iter.next();
jdbcPosition = BackingStoreHelper.populateOwnerInStatement(sm, ec, ps, jdbcPosition, ecs);
jdbcPosition = BackingStoreHelper.populateElementInStatement(ec, ps, element, jdbcPosition, elementMapping);
if (relationDiscriminatorMapping != null)
{
jdbcPosition =
BackingStoreHelper.populateRelationDiscriminatorInStatement(ec, ps, jdbcPosition, ecs);
}
}
int[] number = sqlControl.executeStatementUpdate(mconn, removeAllStmt, ps, true);
if (number[0] > 0)
{
modified = true;
}
}
finally
{
sqlControl.closeStatement(mconn, ps);
}
}
finally
{
mconn.release();
}
}
catch (SQLException e)
{
NucleusLogger.DATASTORE.error(e);
throw new NucleusDataStoreException(localiser.msg("056012", removeAllStmt), e);
}
try
{
// Shift the remaining indices to remove the holes in ordering
boolean batched = storeMgr.allowsBatching();
ExecutionContext ec = sm.getExecutionContext();
ManagedConnection mconn = storeMgr.getConnection(ec);
try
{
for (int i = 0; i < currentListSize; i++)
{
// Find the number of deleted indexes above this index
int shift = 0;
boolean removed = false;
for (int j = 0; j < indices.length; j++)
{
if (indices[j] == i)
{
removed = true;
break;
}
if (indices[j] < i)
{
shift++;
}
}
if (!removed && shift > 0)
{
internalShift(sm, mconn, batched, i, -1 * shift, (i == currentListSize - 1), ecs);
}
}
}
finally
{
mconn.release();
}
}
catch (MappedDatastoreException e)
{
NucleusLogger.DATASTORE.error(e);
throw new NucleusDataStoreException(localiser.msg("056012", removeAllStmt), e);
}
boolean dependent = ecs.getOwnerMemberMetaData().getCollection().isDependentElement();
if (ecs.getOwnerMemberMetaData().isCascadeRemoveOrphans())
{
dependent = true;
}
if (dependent)
{
// "delete-dependent" : delete elements if the collection is marked as dependent
// TODO What if the collection contains elements that are not in the List ? should not delete them
sm.getExecutionContext().deleteObjects(elements.toArray());
}
return modified;
}
/**
* Generates the statement for setting an item.
* <PRE>
* UPDATE LISTTABLE SET [ELEMENTCOL = ?]
* [EMBEDDEDFIELD1=?, EMBEDDEDFIELD2=?, ...]
* WHERE OWNERCOL = ?
* AND INDEXCOL = ?
* [AND DISTINGUISHER=?]
* </PRE>
* @return The Statement for setting an item
*/
protected String getSetStmt(ElementContainerStore ecs)
{
if (setStmt == null)
{
JavaTypeMapping ownerMapping = ecs.getOwnerMapping();
JavaTypeMapping orderMapping = ecs.getOrderMapping();
DatastoreContainerObject containerTable = ecs.getContainerTable();
JavaTypeMapping elementMapping = ecs.getElementMapping();
JavaTypeMapping relationDiscriminatorMapping = ecs.getRelationDiscriminatorMapping();
StringBuffer stmt = new StringBuffer();
stmt.append("UPDATE ");
stmt.append(containerTable.toString());
stmt.append(" SET ");
for (int i = 0; i < elementMapping.getNumberOfDatastoreMappings(); i++)
{
if (i > 0)
{
stmt.append(",");
}
stmt.append(elementMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString());
stmt.append(" = ");
stmt.append(((RDBMSMapping) elementMapping.getDatastoreMapping(i)).getUpdateInputParameter());
}
stmt.append(" WHERE ");
for (int i = 0; i < ownerMapping.getNumberOfDatastoreMappings(); i++)
{
if (i > 0)
{
stmt.append(" AND ");
}
stmt.append(ownerMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString());
stmt.append(" = ");
stmt.append(((RDBMSMapping) ownerMapping.getDatastoreMapping(i)).getUpdateInputParameter());
}
if (ecs.getOwnerMemberMetaData().getOrderMetaData() != null &&
!ecs.getOwnerMemberMetaData().getOrderMetaData().isIndexedList())
{
// Ordered list, so can't easily do a set!!!
}
else
{
for (int i = 0; i < orderMapping.getNumberOfDatastoreMappings(); i++)
{
stmt.append(" AND ");
stmt.append(orderMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString());
stmt.append(" = ");
stmt.append(((RDBMSMapping) orderMapping.getDatastoreMapping(i)).getUpdateInputParameter());
}
}
if (relationDiscriminatorMapping != null)
{
for (int i = 0; i < relationDiscriminatorMapping.getNumberOfDatastoreMappings(); i++)
{
stmt.append(" AND ");
stmt.append(relationDiscriminatorMapping.getDatastoreMapping(i).getDatastoreField().getIdentifier().toString());
stmt.append(" = ");
stmt.append(((RDBMSMapping) relationDiscriminatorMapping.getDatastoreMapping(i)).getUpdateInputParameter());
}
}
setStmt = stmt.toString();
}
return setStmt;
}
public void set(Object element, int index, ObjectProvider sm, ElementContainerStore ecs)
{
// Check for dynamic schema updates prior to update
if (storeMgr.getBooleanObjectProperty("datanucleus.rdbms.dynamicSchemaUpdates").booleanValue())
{
DynamicSchemaFieldManager dynamicSchemaFM = new DynamicSchemaFieldManager(storeMgr, sm);
Collection coll = new ArrayList();
coll.add(element);
dynamicSchemaFM.storeObjectField(ecs.getOwnerMemberMetaData().getAbsoluteFieldNumber(), coll);
if (dynamicSchemaFM.hasPerformedSchemaUpdates())
{
setStmt = null;
}
}
JavaTypeMapping orderMapping = ecs.getOrderMapping();
JavaTypeMapping elementMapping = ecs.getElementMapping();
JavaTypeMapping relationDiscriminatorMapping = ecs.getRelationDiscriminatorMapping();
String setStmt = getSetStmt(ecs);
try
{
ExecutionContext ec = sm.getExecutionContext();
ManagedConnection mconn = ecs.getStoreManager().getConnection(ec);
SQLController sqlControl = storeMgr.getSQLController();
try
{
PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, setStmt, false);
try
{
int jdbcPosition = 1;
jdbcPosition =
BackingStoreHelper.populateElementInStatement(ec, ps, element, jdbcPosition, elementMapping);
jdbcPosition = BackingStoreHelper.populateOwnerInStatement(sm, ec, ps, jdbcPosition, ecs);
if (ecs.getOwnerMemberMetaData().getOrderMetaData() != null &&
!ecs.getOwnerMemberMetaData().getOrderMetaData().isIndexedList())
{
// Ordered list, so can't easily do a set!!!
NucleusLogger.PERSISTENCE.warn("Calling List.addElement at a position for an ordered list is a stupid thing to do; the ordering is set my the ordering specification. Use an indexed list to do this correctly");
}
else
{
jdbcPosition =
BackingStoreHelper.populateOrderInStatement(ec, ps, index, jdbcPosition, orderMapping);
}
if (relationDiscriminatorMapping != null)
{
jdbcPosition =
BackingStoreHelper.populateRelationDiscriminatorInStatement(ec, ps, jdbcPosition, ecs);
}
sqlControl.executeStatementUpdate(mconn, setStmt, ps, true);
}
finally
{
sqlControl.closeStatement(mconn, ps);
}
}
finally
{
mconn.release();
}
}
catch (SQLException e)
{
throw new NucleusDataStoreException(localiser.msg("056015", setStmt), e);
}
}
public boolean internalAdd(ObjectProvider sm, ElementContainerStore ecs, int start, boolean atEnd,
Collection c, int currentListSize, int shift)
{
// Check for dynamic schema updates prior to addition
if (storeMgr.getBooleanObjectProperty("datanucleus.rdbms.dynamicSchemaUpdates").booleanValue())
{
DynamicSchemaFieldManager dynamicSchemaFM = new DynamicSchemaFieldManager(storeMgr, sm);
dynamicSchemaFM.storeObjectField(ecs.getOwnerMemberMetaData().getAbsoluteFieldNumber(), c);
if (dynamicSchemaFM.hasPerformedSchemaUpdates())
{
invalidateAddStmt();
}
}
String addStmt = getAddStmt(ecs);
try
{
ExecutionContext ec = sm.getExecutionContext();
ManagedConnection mconn = ecs.getStoreManager().getConnection(ec);
SQLController sqlControl = storeMgr.getSQLController();
try
{
// Shift any existing elements so that we can insert the new element(s) at their position
if (!atEnd && start != currentListSize)
{
boolean batched = currentListSize - start > 0;
for (int i = currentListSize - 1; i >= start; i--)
{
// Shift the index for this row by "shift"
internalShift(sm, mconn, batched, i, shift, (i == start), ecs);
}
}
else
{
start = currentListSize;
}
// Insert the elements at their required location
int jdbcPosition = 1;
boolean batched = (c.size() > 1);
Iterator iter = c.iterator();
while (iter.hasNext())
{
Object element = iter.next();
PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, addStmt, batched);
try
{
JavaTypeMapping orderMapping = ecs.getOrderMapping();
JavaTypeMapping elementMapping = ecs.getElementMapping();
JavaTypeMapping relationDiscriminatorMapping = ecs.getRelationDiscriminatorMapping();
jdbcPosition = 1;
jdbcPosition = BackingStoreHelper.populateOwnerInStatement(sm, ec, ps, jdbcPosition, ecs);
jdbcPosition = BackingStoreHelper.populateElementInStatement(ec, ps, element, jdbcPosition, elementMapping);
if (orderMapping != null)
{
jdbcPosition = BackingStoreHelper.populateOrderInStatement(ec, ps, start, jdbcPosition, orderMapping);
}
if (relationDiscriminatorMapping != null)
{
jdbcPosition =
BackingStoreHelper.populateRelationDiscriminatorInStatement(ec, ps, jdbcPosition, ecs);
}
start++;
// Execute the statement
sqlControl.executeStatementUpdate(mconn, addStmt, ps, !iter.hasNext());
}
finally
{
sqlControl.closeStatement(mconn, ps);
}
}
}
finally
{
mconn.release();
}
}
catch (MappedDatastoreException e)
{
throw new NucleusDataStoreException(localiser.msg("056009", addStmt), e);
}
catch (SQLException e)
{
throw new NucleusDataStoreException(localiser.msg("056009", addStmt), e);
}
return true;
}
}