| /* |
| * 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); |
| } |
| } |