blob: 1b377c3c48d90db83fdc9bf142917de349389cb2 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.history;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform;
import org.eclipse.persistence.internal.expressions.ConstantExpression;
import org.eclipse.persistence.internal.expressions.ObjectExpression;
import org.eclipse.persistence.internal.expressions.SQLDeleteStatement;
import org.eclipse.persistence.internal.expressions.SQLInsertStatement;
import org.eclipse.persistence.internal.expressions.SQLUpdateStatement;
import org.eclipse.persistence.internal.expressions.TableExpression;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.ConversionManager;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.history.HistoricalDatabaseTable;
import org.eclipse.persistence.internal.queries.StatementQueryMechanism;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DatabaseMapping.WriteType;
import org.eclipse.persistence.mappings.DirectCollectionMapping;
import org.eclipse.persistence.mappings.ManyToManyMapping;
import org.eclipse.persistence.queries.DataModifyQuery;
import org.eclipse.persistence.queries.DeleteAllQuery;
import org.eclipse.persistence.queries.ModifyQuery;
import org.eclipse.persistence.queries.ObjectLevelModifyQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
/**
* <b>Purpose:</b>Expresses how historical data is saved on the data store.
* <p>This information is used to both maintain a history of all objects
* modified through TopLink and to enable point in time querying.
* <p>If Oracle 9R2 or later Flashback is used this policy is not required, as
* the preservation of history is automatic.
* <p>Descriptors, ManyToManyMappings, DirectCollectionMappings,
* and DirectMapMappings only can have a history policy, as only they have associated
* database tables.
* @author Stephen McRitchie
* @since 10
*/
public class HistoryPolicy implements Cloneable, Serializable {
protected ClassDescriptor descriptor;
protected DatabaseMapping mapping;
protected List<DatabaseTable> historicalTables;
protected List<DatabaseField> startFields;
protected List<DatabaseField> endFields;
protected boolean shouldHandleWrites = true;
protected boolean usesLocalTime = true;
public HistoryPolicy() {
}
/**
* INTERNAL:
* Add any temporal querying conditions to this object expression.
*/
public Expression additionalHistoryExpression(Expression context, Expression base) {
return additionalHistoryExpression(context, base, null);
}
/**
* INTERNAL:
* Add any temporal querying conditions to this object expression.
* @param tableIndex not null indicates that only expression for a single table should be returned.
*/
public Expression additionalHistoryExpression(Expression context, Expression base, Integer tableIndex) {
//
AsOfClause clause = base.getAsOfClause();
Object value = clause.getValue();
Expression join = null;
Expression subJoin = null;
Expression start = null;
Expression end = null;
if (value == null) {
return null;
// for now nothing as assume mirroring historical tables.
} else {
if (value instanceof Expression) {
// Sort of an implementation of native sql.
// Print AS OF TIMESTAMP (SYSDATE - 1000*60*10) not AS OF ('SYSDATE - 1000*60*10').
if ((value instanceof ConstantExpression) && (((ConstantExpression)value).getValue() instanceof String)) {
value = (((ConstantExpression)value).getValue());
}
} else {
ConversionManager converter = ConversionManager.getDefaultManager();
value = converter.convertObject(value, ClassConstants.TIMESTAMP);
}
if (getMapping() != null) {
if (tableIndex != null && tableIndex > 0) {
return null;
}
TableExpression tableExp = null;
DatabaseTable historicalTable = getHistoricalTables().get(0);
tableExp = (TableExpression)((ObjectExpression)base).existingDerivedTable(historicalTable);
start = tableExp.getField(getStart());
end = tableExp.getField(getEnd());
join = start.lessThanEqual(value).and(end.isNull().or(end.greaterThan(value)));
// We also need to do step two here in advance.
tableExp.setTable(historicalTable);
return join;
}
int iFirst, iLast;
if (tableIndex == null) {
// loop through all history tables
iFirst = 0;
iLast = getHistoricalTables().size() - 1;
} else {
// only return expression for the specified table
iFirst = tableIndex;
iLast = iFirst;
}
for (int i = iFirst; i <= iLast ; i++) {
start = base.getField(getStart(i));
end = base.getField(getEnd(i));
subJoin = start.lessThanEqual(value).and(end.isNull().or(end.greaterThan(value)));
join = ((join == null) ? subJoin : join.and(subJoin));
}
return join;
}
}
/**
* PUBLIC:
* Performs a sufficiently deep clone.
* Use to quickly setup standard policies on multiple descriptors.
*/
@Override
public Object clone() {
HistoryPolicy clone = null;
try {
clone = (HistoryPolicy)super.clone();
} catch (CloneNotSupportedException ignore) {
}
if (startFields != null) {
clone.setStartFields(new ArrayList(startFields.size()));
for (DatabaseField field : startFields) {
clone.getStartFields().add(field.clone());
}
}
if (endFields != null) {
clone.setEndFields(new ArrayList(endFields.size()));
for (DatabaseField field : endFields) {
clone.getEndFields().add(field.clone());
}
}
if (historicalTables != null) {
clone.setHistoricalTables(new ArrayList(historicalTables));
}
return clone;
}
/**
* PUBLIC:
* Whenever a historical record is logically deleted (updated) or inserted,
* the end and start fields respectively will be set to this value.
*/
public Object getCurrentTime(AbstractSession session) {
if (shouldUseLocalTime()) {
return new java.sql.Timestamp(System.currentTimeMillis());
}
if (shouldUseDatabaseTime()) {
AbstractSession readSession = session.getSessionForClass(getDescriptor().getJavaClass());
while (readSession.isUnitOfWork()) {
readSession = readSession.getParent().getSessionForClass(getDescriptor().getJavaClass());
}
return readSession.getDatasourceLogin().getDatasourcePlatform().getTimestampFromServer(session, readSession.getName());
}
return null;
}
/**
* INTERNAL:
* Return a minimal time increment supported by the platform.
*/
public long getMinimumTimeIncrement(AbstractSession session) {
AbstractSession readSession = session.getSessionForClass(getDescriptor().getJavaClass());
while (readSession.isUnitOfWork()) {
readSession = readSession.getParent().getSessionForClass(getDescriptor().getJavaClass());
}
return readSession.getPlatform().minimumTimeIncrement();
}
/**
* PUBLIC:
* Return the descriptor of the policy.
*/
public ClassDescriptor getDescriptor() {
return descriptor;
}
/**
* INTERNAL:
*/
public final List<DatabaseTable> getHistoricalTables() {
if (historicalTables == null) {
historicalTables = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1);
}
return historicalTables;
}
/**
* PUBLIC:
*/
public List<String> getHistoryTableNames() {
List<String> names = new ArrayList(getHistoricalTables().size());
for (DatabaseTable table : getHistoricalTables()) {
names.add(table.getQualifiedName());
}
return names;
}
/**
* PUBLIC:
*/
public DatabaseMapping getMapping() {
return mapping;
}
/**
* INTERNAL:
*/
protected DatabaseField getStart() {
if (startFields != null) {
return startFields.get(0);
} else {
return null;
}
}
/**
* INTERNAL:
*/
protected DatabaseField getStart(int i) {
return startFields.get(i);
}
/**
* PUBLIC:
* Answers the name of the start field. Assumes that multiple tables
* for a descriptor have the same field names.
*/
public String getStartFieldName() {
if (getStart() != null) {
return getStart().getName();
} else {
return null;
}
}
/**
* INTERNAL:
*/
public List<DatabaseField> getStartFields() {
return startFields;
}
/**
* INTERNAL:
*/
protected DatabaseField getEnd() {
if (endFields != null) {
return endFields.get(0);
} else {
return null;
}
}
/**
* INTERNAL:
*/
protected DatabaseField getEnd(int i) {
return endFields.get(i);
}
/**
* PUBLIC:
*/
public String getEndFieldName() {
if (getEnd() != null) {
return getEnd().getName();
} else {
return null;
}
}
/**
* INTERNAL:
*/
public List<DatabaseField> getEndFields() {
return endFields;
}
/**
* PUBLIC:
*/
public void setDescriptor(ClassDescriptor descriptor) {
this.descriptor = descriptor;
}
/**
* INTERNAL:
* Initialize a HistoryPolicy.
*/
public void initialize(AbstractSession session) {
if (getMapping() != null) {
setDescriptor(getMapping().getDescriptor());
if (getMapping().isDirectCollectionMapping()) {
DatabaseTable refTable = ((DirectCollectionMapping)getMapping()).getReferenceTable();
DatabaseTable histTable = getHistoricalTables().get(0);
histTable.setName(refTable.getName());
histTable.setTableQualifier(refTable.getTableQualifier());
getStart().setTable(histTable);
getEnd().setTable(histTable);
} else if (getMapping().isManyToManyMapping()) {
DatabaseTable relationTable = ((ManyToManyMapping)getMapping()).getRelationTable();
DatabaseTable histTable = getHistoricalTables().get(0);
histTable.setName(relationTable.getName());
histTable.setTableQualifier(relationTable.getTableQualifier());
getStart().setTable(histTable);
getEnd().setTable(histTable);
}
verifyTableQualifiers(session.getPlatform());
return;
}
// Some historicalTables will be inherited from a parent policy.
int offset = getDescriptor().getTables().size() - getHistoricalTables().size();
// In this configuration descriptor tables, history tables, and start/end fields
// are all in the same order.
if (!getHistoricalTables().isEmpty() && getHistoricalTables().get(0).getName().equals("")) {
for (int i = 0; i < getHistoricalTables().size(); i++) {
DatabaseTable table = getHistoricalTables().get(i);
if (table.getName().equals("")) {
DatabaseTable mirrored = getDescriptor().getTables().get(i + offset);
table.setName(mirrored.getName());
table.setTableQualifier(mirrored.getTableQualifier());
}
if (getStartFields().size() < (i + 1)) {
DatabaseField startField = getStart(0).clone();
startField.setTable(table);
getStartFields().add(startField);
} else {
DatabaseField startField = getStart(i);
startField.setTable(table);
}
if (getEndFields().size() < (i + 1)) {
DatabaseField endField = getEnd(0).clone();
endField.setTable(table);
getEndFields().add(endField);
} else {
DatabaseField endField = getEnd(i);
endField.setTable(table);
}
}
} else {
// The user did not specify history tables/fields in order, so
// initialize will take a little longer.
List<DatabaseTable> unsortedTables = getHistoricalTables();
List<DatabaseTable> sortedTables = new ArrayList(unsortedTables.size());
List<DatabaseField> sortedStartFields = new ArrayList(unsortedTables.size());
List<DatabaseField> sortedEndFields = new ArrayList(unsortedTables.size());
boolean universalStartField = ((getStartFields().size() == 1) && (!(getStartFields().get(0)).hasTableName()));
boolean universalEndField = ((getEndFields().size() == 1) && (!(getEndFields().get(0)).hasTableName()));
DatabaseTable descriptorTable = null;
DatabaseTable historicalTable = null;
DatabaseField historyField = null;
List<DatabaseTable> descriptorTables = getDescriptor().getTables();
for (int i = offset; i < descriptorTables.size(); i++) {
descriptorTable = descriptorTables.get(i);
int index = unsortedTables.indexOf(descriptorTable);
if (index == -1) {
// this is a configuration error!
}
historicalTable = unsortedTables.get(index);
historicalTable.setTableQualifier(descriptorTable.getTableQualifier());
sortedTables.add(historicalTable);
if (universalStartField) {
historyField = getStart(0).clone();
historyField.setTable(historicalTable);
sortedStartFields.add(historyField);
} else {
for (DatabaseField field : getStartFields()) {
if (field.getTable().equals(historicalTable)) {
sortedStartFields.add(field);
break;
}
}
}
if (universalEndField) {
historyField = getEnd(0).clone();
historyField.setTable(historicalTable);
sortedEndFields.add(historyField);
} else {
for (DatabaseField field : getEndFields()) {
if (field.getTable().equals(historicalTable)) {
sortedEndFields.add(field);
break;
}
}
}
}
setHistoricalTables(sortedTables);
setStartFields(sortedStartFields);
setEndFields(sortedEndFields);
}
verifyTableQualifiers(session.getPlatform());
// A user need not set a policy on every level of an inheritance, but
// historic tables can be inherited.
if (getDescriptor().hasInheritance()) {
ClassDescriptor parentDescriptor = getDescriptor().getInheritancePolicy().getParentDescriptor();
while ((parentDescriptor != null) && (parentDescriptor.getHistoryPolicy() == null)) {
parentDescriptor = parentDescriptor.getInheritancePolicy().getParentDescriptor();
}
if (parentDescriptor != null) {
// Unique is required because the builder can add the same table many times.
// This is done after init properties to make sure the default table is the first local one.
setHistoricalTables(Helper.concatenateUniqueLists(parentDescriptor.getHistoryPolicy().getHistoricalTables(), getHistoricalTables()));
setStartFields(Helper.concatenateUniqueLists(parentDescriptor.getHistoryPolicy().getStartFields(), getStartFields()));
setEndFields(Helper.concatenateUniqueLists(parentDescriptor.getHistoryPolicy().getEndFields(), getEndFields()));
}
}
}
/**
* PUBLIC:
* Use to specify the names of the mirroring historical tables.
* <p>
* Assumes that the order in which tables are added with descriptor.addTableName()
* matches the order in which mirroring historical tables are added with
* descriptor.addHistoryTableName().
*/
public void addHistoryTableName(String name) {
HistoricalDatabaseTable table = new HistoricalDatabaseTable("");
table.setHistoricalName(name);
getHistoricalTables().add(table);
}
/**
* PUBLIC:
* Use to specify the names of the mirroring historical tables.
* <p>
* Explicitly states that <code>sourceTableName</code> is mirrored by history table
* <code>historyTableName</code>.
* The order in which tables are added with descriptor.addTableName()
* should still match the order in which mirroring historical tables are
* added with descriptor.addMirroringHistoryTableName().
*/
public void addHistoryTableName(String sourceTableName, String historyTableName) {
if ((sourceTableName == null) || sourceTableName.equals("")) {
addHistoryTableName(historyTableName);
}
HistoricalDatabaseTable table = new HistoricalDatabaseTable(sourceTableName);
table.setHistoricalName(historyTableName);
// Note that the equality check is only on sourceTableName, not historyTableName.
int index = getHistoricalTables().indexOf(table);
if (index == -1) {
getHistoricalTables().add(table);
} else {
getHistoricalTables().set(index, table);
}
}
/**
* INTERNAL:
*/
public void setHistoricalTables(List<DatabaseTable> historicalTables) {
this.historicalTables = historicalTables;
}
/**
* INTERNAL:
*/
public void setMapping(DatabaseMapping mapping) {
this.mapping = mapping;
}
/**
* INTERNAL:
*/
protected void setStartFields(List<DatabaseField> startFields) {
this.startFields = startFields;
}
/**
* PUBLIC:
* Sets the name of the start field.
* <p>
* By default all tables belonging to a descriptor have the same primary
* key field names, and so the same start field names also.
* <p>
* However, if <code>startFieldName</code> is qualified, i.e. of the form
* "EMPLOYEE_HIST.EMP_START", then this call will only set the start field
* name for a single historical table.
*/
public void addStartFieldName(String startFieldName) {
DatabaseField startField = new DatabaseField(startFieldName);
startField.setType(ClassConstants.TIMESTAMP);
// #440278
startField.setLength(6);
if (startFields == null) {
startFields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance();
startFields.add(startField);
return;
}
for (DatabaseField existing : startFields) {
if (startField.getTableName().equals(existing.getTableName())) {
existing.setName(startField.getName());
return;
}
}
startFields.add(startField);
}
/**
* ADVANCED:
* Sets the type of all start fields. Not required to be set as the default
* of Timestamp is assumed.
*/
public void setStartFieldType(Class<?> type) {
for (DatabaseField existing : startFields) {
existing.setType(type);
}
}
/**
* INTERNAL:
*/
protected void setEndFields(List<DatabaseField> endFields) {
this.endFields = endFields;
}
/**
* PUBLIC:
* @see #addStartFieldName
*/
public void addEndFieldName(String endFieldName) {
DatabaseField endField = new DatabaseField(endFieldName);
endField.setType(ClassConstants.TIMESTAMP);
// #440278
endField.setLength(6);
if (endFields == null) {
endFields = new ArrayList();
endFields.add(endField);
return;
}
for (DatabaseField existing : endFields) {
if (endField.getTableName().equals(existing.getTableName())) {
existing.setName(endField.getName());
return;
}
}
endFields.add(endField);
}
/**
* ADVANCED:
* @see #setStartFieldType
*/
public void setEndFieldType(String fieldName, Class<?> type) {
for (DatabaseField existing : endFields) {
existing.setType(type);
}
}
/**
* Sets if TopLink is responsible for writing history.
* <p>
* If history is maintained via low level database triggers or application
* logic a policy is still needed for point in time querying.
* <p>
* If Oracle flashback is used no HistoryPolicy is needed.
* <p>
* Setting this to false lets you use History for many other applications.
* For instance a table that tracks available flights or hotel deals may
* benefit from a HistoryPolicy just to simplify temporal querying.
* <p>If all hotel discounts have a start and end date, you could query on
* all discounts available at a certain date.
*/
public void setShouldHandleWrites(boolean value) {
this.shouldHandleWrites = value;
}
/**
* Answers if TopLink is responsible for writing history.
* <p>
* If history is maintained via low level database triggers or application
* logic a policy is still usefull for point in time querying.
* <p>
* If Oracle flashback is used no HistoryPolicy is needed.
* @return true by default
* @see #setShouldHandleWrites
*/
public boolean shouldHandleWrites() {
return shouldHandleWrites;
}
/**
* Sets if the Timestamp used in maintainaing history should be the
* current time according to the database.
* @param value if false uses localTime (default) instead
*/
public void setShouldUseDatabaseTime(boolean value) {
usesLocalTime = !value;
}
/**
* Answers if the Timestamp used in maintaining history should be
* System.currentTimeMillis();
* @see #shouldUseDatabaseTime
* @see #useLocalTime
* @return true by default
*/
public boolean shouldUseLocalTime() {
return usesLocalTime;
}
/**
* Answers if the Timestamp used in maintaining history should be the
* current time according to the database.
* @see #shouldUseLocalTime
* @see #useDatabaseTime
* @return false by default
*/
public boolean shouldUseDatabaseTime() {
return !usesLocalTime;
}
/**
* Answers if the Timestamp used in maintaining history should be
* System.currentTimeMillis();
* @see #useDatabaseTime
* @see #shouldUseLocalTime
*/
public void useLocalTime() {
usesLocalTime = true;
}
/**
* Answers if the Timestamp used in maintaining history should be the
* current time according to the database.
* @see #useLocalTime
* @see #shouldUseDatabaseTime
*/
public void useDatabaseTime() {
usesLocalTime = false;
}
/**
* INTERNAL: Check that the qualifiers on the historical tables are
* properly set.
* <p>A similar method exists on ClassDescriptor.
*/
protected void verifyTableQualifiers(DatasourcePlatform platform) {
String tableQualifier = platform.getTableQualifier();
if (tableQualifier.length() == 0) {
return;
}
for (DatabaseTable table : getHistoricalTables()) {
// Build a scratch table to see if history table name has a qualifier.
DatabaseTable scratchTable = new DatabaseTable(table.getQualifiedName());
if (scratchTable.getTableQualifier().length() == 0) {
scratchTable.setTableQualifier(tableQualifier);
((HistoricalDatabaseTable)table).setHistoricalName(scratchTable.getQualifiedNameDelimited(platform));
}
}
}
/**
* INTERNAL:
* Checks for the case where an object has multiple tables but only some
* are part of a minimal update.
*/
protected boolean checkWastedVersioning(AbstractRecord modifyRow, DatabaseTable table) {
for (Enumeration fieldsEnum = modifyRow.keys(); fieldsEnum.hasMoreElements();) {
DatabaseField field = (DatabaseField)fieldsEnum.nextElement();
if (field.getTable().equals(table) || (!field.hasTableName())) {
return true;
}
}
return false;
}
/**
* INTERNAL:
*/
public void postDelete(ModifyQuery deleteQuery) {
logicalDelete(deleteQuery, false);
}
/**
* INTERNAL:
*/
// Bug 319276 - pass whether shallow insert/update
public void postUpdate(ObjectLevelModifyQuery writeQuery) {
postUpdate(writeQuery, false);
}
/**
* INTERNAL:
*/
public void postUpdate(ObjectLevelModifyQuery writeQuery, boolean isShallow) {
logicalDelete(writeQuery, true, isShallow);
logicalInsert(writeQuery, true);
}
/**
* INTERNAL:
*/
public void postInsert(ObjectLevelModifyQuery writeQuery) {
logicalInsert(writeQuery, false);
}
/**
* INTERNAL:
* Perform a logical insert into the historical schema, creating a new version
* of an object.
* <p>Called by postInsert() and also postUpdate() (which first does a logicalDelete
* of the previous version).
*/
public void logicalInsert(ObjectLevelModifyQuery writeQuery, boolean isUpdate) {
ClassDescriptor descriptor = getDescriptor();
AbstractRecord modifyRow = null;
AbstractRecord originalModifyRow = writeQuery.getModifyRow();
Object currentTime = null;
if (isUpdate) {
modifyRow = descriptor.getObjectBuilder().buildRow(writeQuery.getObject(), writeQuery.getSession(), WriteType.UPDATE); // Bug 319276
// If anyone added items to the modify row, then they should also be added here.
modifyRow.putAll(originalModifyRow);
} else {
modifyRow = originalModifyRow;
// If update would have already discovered timestamp to use.
currentTime = getCurrentTime(writeQuery.getSession());
}
StatementQueryMechanism insertMechanism = new StatementQueryMechanism(writeQuery);
for (int i = 0; i < getHistoricalTables().size(); i++) {
DatabaseTable table = getHistoricalTables().get(i);
if (isUpdate && !checkWastedVersioning(originalModifyRow, table)) {
continue;
}
if (!isUpdate) {
modifyRow.add(getStart(i), currentTime);
}
SQLInsertStatement insertStatement = new SQLInsertStatement();
insertStatement.setTable(table);
insertMechanism.getSQLStatements().add(insertStatement);
}
if (insertMechanism.hasMultipleStatements()) {
writeQuery.setTranslationRow(modifyRow);
writeQuery.setModifyRow(modifyRow);
insertMechanism.insertObject();
}
}
/**
* INTERNAL:
* Performs a logical insert into the historical schema. Direct
* collections and many to many mappings are maintained through the session
* events.
*/
public void mappingLogicalInsert(DataModifyQuery originalQuery, AbstractRecord arguments, AbstractSession session) {
DataModifyQuery historyQuery = new DataModifyQuery();
SQLInsertStatement historyStatement = new SQLInsertStatement();
DatabaseTable histTable = getHistoricalTables().get(0);
historyStatement.setTable(histTable);
AbstractRecord modifyRow = originalQuery.getModifyRow().clone();
AbstractRecord translationRow = arguments.clone();
// Start could be the version field in timestamp locking.
if (!modifyRow.containsKey(getStart())) {
Object time = getCurrentTime(session);
modifyRow.add(getStart(), time);
translationRow.add(getStart(), time);
}
historyQuery.setSQLStatement(historyStatement);
historyQuery.setModifyRow(modifyRow);
historyStatement.setModifyRow(modifyRow);
session.executeQuery(historyQuery, translationRow);
}
/**
* INTERNAL:
* Performs a logical delete (update) on the historical schema.
*/
// Bug 319276 - pass whether shallow insert/update
public void logicalDelete(ModifyQuery writeQuery, boolean isUpdate) {
logicalDelete(writeQuery, isUpdate, false);
}
/**
* INTERNAL:
* Performs a logical delete (update) on the historical schema.
*/
public void logicalDelete(ModifyQuery writeQuery, boolean isUpdate, boolean isShallow) {
ClassDescriptor descriptor = writeQuery.getDescriptor();
AbstractRecord originalModifyRow = writeQuery.getModifyRow();
AbstractRecord modifyRow = new DatabaseRecord();
StatementQueryMechanism updateMechanism = new StatementQueryMechanism(writeQuery);
Object currentTime = getCurrentTime(writeQuery.getSession());
for (int i = 0; i < getHistoricalTables().size(); i++) {
DatabaseTable table = getHistoricalTables().get(i);
if (isUpdate && !checkWastedVersioning(originalModifyRow, table)) {
continue;
}
SQLUpdateStatement updateStatement = new SQLUpdateStatement();
updateStatement.setTable(table);
Expression whereClause = null;
if (writeQuery instanceof DeleteAllQuery) {
if (writeQuery.getSelectionCriteria() != null) {
whereClause = (Expression)writeQuery.getSelectionCriteria().clone();
}
} else {
whereClause = descriptor.getObjectBuilder().buildPrimaryKeyExpression(table);
}
ExpressionBuilder builder = ((whereClause == null) ? new ExpressionBuilder() : whereClause.getBuilder());
whereClause = builder.getField(getEnd(i)).isNull().and(whereClause);
updateStatement.setWhereClause(whereClause);
modifyRow.add(getEnd(i), currentTime);
// save a little time here and add the same timestamp value for
// the start field in the logicalInsert.
if (isUpdate) {
if (isShallow) {
// Bug 319276 - increment the timestamp by 1 to avoid unique constraint violation potential
java.sql.Timestamp incrementedTime = (java.sql.Timestamp) currentTime;
incrementedTime.setTime(incrementedTime.getTime() + getMinimumTimeIncrement(writeQuery.getSession()));
originalModifyRow.add(getStart(i), incrementedTime);
} else {
originalModifyRow.add(getStart(i), currentTime);
}
}
updateMechanism.getSQLStatements().add(updateStatement);
}
if (updateMechanism.hasMultipleStatements()) {
writeQuery.setModifyRow(modifyRow);
updateMechanism.updateObject();
writeQuery.setModifyRow(originalModifyRow);
}
}
/**
* INTERNAL:
* Performs a logical delete (update) on the historical schema. Direct
* collections and many to many mappings are maintained through the session
* events.
*/
public void mappingLogicalDelete(ModifyQuery originalQuery, AbstractRecord arguments, AbstractSession session) {
SQLDeleteStatement originalStatement = (SQLDeleteStatement)originalQuery.getSQLStatement();
DataModifyQuery historyQuery = new DataModifyQuery();
SQLUpdateStatement historyStatement = new SQLUpdateStatement();
DatabaseTable histTable = getHistoricalTables().get(0);
historyStatement.setTable(histTable);
Expression whereClause = (Expression)originalStatement.getWhereClause().clone();
DatabaseField endField = getEnd();
whereClause = whereClause.getBuilder().getField(endField).isNull().and(whereClause);
historyStatement.setWhereClause(whereClause);
AbstractRecord modifyRow = new DatabaseRecord();
AbstractRecord translationRow = arguments.clone();
Object time = getCurrentTime(session);
modifyRow.add(getEnd(), time);
translationRow.add(getEnd(), time);
historyStatement.setModifyRow(modifyRow);
historyQuery.setSQLStatement(historyStatement);
historyQuery.setModifyRow(modifyRow);
session.executeQuery(historyQuery, translationRow);
}
}