--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.store.raw.data.UpdateFieldOperation\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.store.raw.data;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.impl.store.raw.data.BasePage;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.services.io.FormatIdUtil;\r
+import org.apache.derby.iapi.services.io.StoredFormatIds;\r
+\r
+import org.apache.derby.iapi.store.access.conglomerate.LogicalUndo;\r
+\r
+import org.apache.derby.iapi.store.raw.Page;\r
+import org.apache.derby.iapi.store.raw.RecordHandle;\r
+import org.apache.derby.iapi.store.raw.Transaction;\r
+import org.apache.derby.iapi.store.raw.xact.RawTransaction; \r
+\r
+import org.apache.derby.iapi.store.raw.log.LogInstant;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+\r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+import org.apache.derby.iapi.services.io.CompressedNumber;\r
+import org.apache.derby.iapi.util.ByteArray;\r
+import org.apache.derby.iapi.services.io.DynamicByteArrayOutputStream;\r
+\r
+import java.io.OutputStream;\r
+import java.io.ObjectOutput;\r
+import java.io.ObjectInput;\r
+import java.io.IOException;\r
+import org.apache.derby.iapi.services.io.LimitObjectInput;\r
+\r
+\r
+/**\r
+ Represents the update of a particular field of a row on a page.\r
+\r
+ <PRE>\r
+ @format_id LOGOP_UPDATE_FIELD\r
+ the formatId is written by FormatIdOutputStream when this object is\r
+ written out by writeObject\r
+ @purpose update a field of a record on the page\r
+ @upgrade\r
+ @disk_layout\r
+ LogicalPageOperation the super class\r
+ doMeSlot(CompressedInt) the slot of the record being updated\r
+ fieldId(CompressedInt) the recordId of the record being updated\r
+\r
+ OptionalData The after image of the column (length included),\r
+ follow by the old image of the record (length\r
+ included). If this is logically undoable, then the\r
+ before image of the entire row is logged\r
+ @end_format\r
+ </PRE>\r
+*/\r
+public final class UpdateFieldOperation extends LogicalPageOperation \r
+{\r
+\r
+ protected int doMeSlot; // insert slot - only valid during a doMe() operation\r
+ protected int fieldId;\r
+\r
+ transient protected ByteArray preparedLog;\r
+\r
+ public UpdateFieldOperation(\r
+ RawTransaction t, \r
+ BasePage page, \r
+ int slot, \r
+ int recordId, \r
+ int fieldId, \r
+ Object column, \r
+ LogicalUndo undo) \r
+ throws StandardException\r
+ {\r
+ super(page, undo, recordId);\r
+\r
+ this.doMeSlot = slot;\r
+ this.fieldId = fieldId;\r
+\r
+ try {\r
+ writeOptionalDataToBuffer(t, column);\r
+ } catch (IOException ioe) {\r
+ throw StandardException.newException(\r
+ SQLState.DATA_UNEXPECTED_EXCEPTION, ioe);\r
+ }\r
+ }\r
+\r
+ /*\r
+ * Formatable methods\r
+ */\r
+\r
+ // no-arg constructor, required by Formatable \r
+ public UpdateFieldOperation() { super(); }\r
+\r
+ public void writeExternal(ObjectOutput out) throws IOException \r
+ {\r
+ super.writeExternal(out);\r
+ CompressedNumber.writeInt(out, doMeSlot);\r
+ CompressedNumber.writeInt(out, fieldId);\r
+ }\r
+\r
+ /**\r
+ Read this in\r
+ @exception IOException error reading from log stream\r
+ @exception ClassNotFoundException log stream corrupted\r
+ */\r
+ public void readExternal(ObjectInput in) \r
+ throws IOException, ClassNotFoundException\r
+ {\r
+ super.readExternal(in);\r
+ doMeSlot = CompressedNumber.readInt(in);\r
+ fieldId = CompressedNumber.readInt(in);\r
+ }\r
+\r
+ /**\r
+ Return my format identifier.\r
+ */\r
+ public int getTypeFormatId() {\r
+ return StoredFormatIds.LOGOP_UPDATE_FIELD;\r
+ }\r
+\r
+ /*\r
+ * Loggable methods\r
+ */\r
+ /**\r
+ Change the value of a field.\r
+\r
+ @exception IOException Can be thrown by any of the methods of ObjectInput.\r
+ @exception StandardException Standard Derby policy.\r
+\r
+ @see org.apache.derby.iapi.store.raw.Loggable#doMe\r
+ */\r
+ public void doMe(Transaction xact, LogInstant instant, LimitObjectInput in)\r
+ throws StandardException, IOException \r
+ {\r
+ this.page.storeField(instant, doMeSlot, fieldId, in);\r
+ }\r
+ \r
+ /*\r
+ * Undoable methods\r
+ */\r
+\r
+ /**\r
+ Restore field to its old value.\r
+\r
+ @exception IOException Can be thrown by any of the methods of ObjectInput.\r
+ @exception StandardException Standard Derby policy.\r
+\r
+ @see LogicalPageOperation#undoMe\r
+ */\r
+ public void undoMe(Transaction xact, BasePage undoPage, int undoRecordId,\r
+ LogInstant CLRInstant, LimitObjectInput in)\r
+ throws StandardException, IOException\r
+ {\r
+ int slot = \r
+ undoPage.findRecordById(undoRecordId, Page.FIRST_SLOT_NUMBER);\r
+ \r
+ if (SanityManager.DEBUG)\r
+ {\r
+ // if the record Id has changed, the page had better changed\r
+ // this can only happen during recovery since in run time undo,\r
+ // this resetRecordHandle gets called and this object have the new\r
+ // page number and recordId\r
+ if (undoRecordId != this.recordId)\r
+ if (undoPage.getPageNumber() == getPageId().getPageNumber())\r
+ SanityManager.THROWASSERT(\r
+ "recordId changed from " + this.recordId +\r
+ " to " + undoRecordId +\r
+ " but page number did not change " +\r
+ undoPage.getPageNumber());\r
+\r
+ if (slot == -1)\r
+ SanityManager.THROWASSERT(\r
+ "recordId " +\r
+ undoRecordId +\r
+ " not found on page " +\r
+ undoPage.getPageNumber());\r
+ }\r
+\r
+ undoPage.skipField((java.io.ObjectInput) in); // skip the after image of the column\r
+ undoPage.storeField(CLRInstant, slot, fieldId, in);\r
+ undoPage.setAuxObject(null);\r
+ }\r
+\r
+ /*\r
+ * LogicalUndoable methods\r
+ */\r
+\r
+\r
+ /**\r
+ Restore the row stored in the optional data of the log record.\r
+\r
+ @exception IOException error reading from log stream\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ public void restoreLoggedRow(Object[] row, LimitObjectInput in)\r
+ throws StandardException, IOException\r
+ {\r
+ BasePage p = null;\r
+\r
+ try {\r
+ // the optional data is written by the page in the same format it\r
+ // stores record on the page, \r
+ // only a page knows how to restore a logged row back to a storable row\r
+ // first get the page where the insert went even though the row may no\r
+ // longer be there\r
+ p = (BasePage)(getContainer().getPage(getPageId().getPageNumber()));\r
+\r
+ // skip over the before and after image of the column, position the\r
+ // input stream at the entire row\r
+ p.skipField(in); // AI of the column\r
+ p.skipField(in); // BI of the column\r
+\r
+ p.restoreRecordFromStream(in, row);\r
+\r
+ // RESOLVE: this returns the BI of the row, what we need is the AI\r
+ // of the row. We need to someone splice in the AI of the column\r
+ // into the storable row.\r
+\r
+ } finally {\r
+\r
+ if (p != null) {\r
+ p.unlatch();\r
+ p = null;\r
+ }\r
+ }\r
+ }\r
+\r
+ /*\r
+ * method to support BeforeImageLogging\r
+ */\r
+\r
+ /**\r
+ * restore the before image of the page\r
+ *\r
+ * @exception StandardException Standard Derby Error Policy\r
+ * @exception IOException problem reading the complete log record from the\r
+ * input stream\r
+ */\r
+ public void restoreMe(Transaction xact, BasePage undoPage, LogInstant CLRInstant, LimitObjectInput in)\r
+ throws StandardException, IOException\r
+ {\r
+ int slot = undoPage.findRecordById(recordId, Page.FIRST_SLOT_NUMBER);\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if ( ! getPageId().equals(undoPage.getPageId()))\r
+ SanityManager.THROWASSERT(\r
+ "restoreMe cannot restore to a different page. "\r
+ + "doMe page:" + getPageId() + " undoPage:" + \r
+ undoPage.getPageId());\r
+ if (slot != doMeSlot)\r
+ SanityManager.THROWASSERT(\r
+ "restoreMe cannot restore to a different slot. "\r
+ + "doMe slot:" + doMeSlot + " undoMe slot: " +\r
+ slot + " recordId:" + recordId);\r
+ }\r
+\r
+ undoPage.skipField(in); // skip the after image of the column\r
+ undoPage.storeField(CLRInstant, slot, fieldId, in);\r
+ undoPage.setAuxObject(null);\r
+ }\r
+\r
+ /*\r
+ methods to support prepared log\r
+ \r
+ the following two methods should not be called during recover\r
+ */\r
+\r
+ public ByteArray getPreparedLog()\r
+ {\r
+ return (this.preparedLog);\r
+ }\r
+\r
+ /**\r
+ Write the old column value and and new column value as optional data.\r
+ If logical undo, writes out the entire row's before image.\r
+\r
+ @exception IOException Can be thrown by any of the methods of ObjectOutput.\r
+ @exception StandardException Standard Derby policy.\r
+ */\r
+ private void writeOptionalDataToBuffer(\r
+ RawTransaction t, \r
+ Object column)\r
+ throws StandardException, IOException\r
+ {\r
+\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(this.page != null);\r
+ }\r
+\r
+ DynamicByteArrayOutputStream logBuffer = t.getLogBuffer();\r
+ int optionalDataStart = logBuffer.getPosition();\r
+\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(optionalDataStart == 0,\r
+ "Buffer for writing optional data should start at position 0");\r
+ }\r
+ \r
+ this.page.logColumn(doMeSlot, fieldId, column, logBuffer, 100); // the after image of the column\r
+ this.page.logField(doMeSlot, fieldId, logBuffer); // the BI of the column\r
+ if (undo != null)\r
+ {\r
+ // RESOLVE: we want the AFTER image of the row, not the BEFORE\r
+ // image. This works for now because only btree needs a logical\r
+ // undoable updateField and it always update only the pointer field\r
+ // to point to something else.\r
+ //\r
+ // But in the future, it needs to be changed. \r
+\r
+ this.page.logRecord(doMeSlot, BasePage.LOG_RECORD_DEFAULT,\r
+ recordId, (FormatableBitSet) null, logBuffer,\r
+ (RecordHandle)null); \r
+ // log the BI of the entire row\r
+\r
+ }\r
+\r
+ int optionalDataLength = logBuffer.getPosition() - optionalDataStart;\r
+\r
+ if (SanityManager.DEBUG) {\r
+ if (optionalDataLength != logBuffer.getUsed())\r
+ SanityManager.THROWASSERT("wrong optional data length, optionalDataLength = "\r
+ + optionalDataLength + ", logBuffer.getUsed() = " + logBuffer.getUsed());\r
+ }\r
+\r
+ // set the position to the beginning of the buffer\r
+ logBuffer.setPosition(optionalDataStart);\r
+\r
+ this.preparedLog = new ByteArray(logBuffer.getByteArray(), optionalDataStart,\r
+ optionalDataLength);\r
+ }\r
+\r
+ /**\r
+ DEBUG: Print self.\r
+ */\r
+ public String toString()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ return super.toString() + \r
+ "UpdateField : " + \r
+ " Slot=" + doMeSlot +\r
+ " recordId=" + recordId +\r
+ " fieldId=" + fieldId;\r
+ }\r
+ else\r
+ return null;\r
+ }\r
+\r
+}\r