001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.execute.RowChangerImpl
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.sql.execute;
023:
024: import org.apache.derby.iapi.services.io.FormatableBitSet;
025:
026: import org.apache.derby.iapi.services.sanity.SanityManager;
027:
028: import org.apache.derby.iapi.error.StandardException;
029:
030: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
031: import org.apache.derby.iapi.sql.conn.StatementContext;
032: import org.apache.derby.iapi.sql.dictionary.IndexRowGenerator;
033: import org.apache.derby.iapi.sql.execute.ExecRow;
034: import org.apache.derby.iapi.sql.execute.ExecutionContext;
035: import org.apache.derby.iapi.sql.execute.RowChanger;
036: import org.apache.derby.iapi.sql.execute.ExecutionFactory;
037: import org.apache.derby.iapi.sql.execute.TemporaryRowHolder;
038:
039: import org.apache.derby.iapi.sql.Activation;
040:
041: import org.apache.derby.iapi.store.access.ConglomerateController;
042: import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;
043: import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;
044: import org.apache.derby.iapi.store.access.TransactionController;
045:
046: import org.apache.derby.iapi.types.DataValueDescriptor;
047:
048: import org.apache.derby.iapi.types.RowLocation;
049:
050: import java.util.Vector;
051:
052: /**
053: Perform row at a time DML operations of tables and maintain indexes.
054: */
055: class RowChangerImpl implements RowChanger {
056: boolean isOpen = false;
057:
058: //
059: //Stuff provided to the constructor
060: boolean[] fixOnUpdate = null;
061: long heapConglom;
062: DynamicCompiledOpenConglomInfo heapDCOCI;
063: StaticCompiledOpenConglomInfo heapSCOCI;
064: long[] indexCIDS = null;
065: DynamicCompiledOpenConglomInfo[] indexDCOCIs;
066: StaticCompiledOpenConglomInfo[] indexSCOCIs;
067: IndexRowGenerator[] irgs = null;
068: Activation activation;
069: TransactionController tc;
070: FormatableBitSet changedColumnBitSet;
071: FormatableBitSet baseRowReadList;
072: private int[] baseRowReadMap; //index=heap column, value=input row column.
073: int[] changedColumnIds;
074: TemporaryRowHolderImpl rowHolder;
075:
076: // for error reporting.
077: String[] indexNames;
078:
079: //
080: //Stuff filled in by open
081: private ConglomerateController baseCC;
082: private RowLocation baseRowLocation;
083: private IndexSetChanger isc;
084:
085: // a row array with all non-updated columns compacted out
086: private DataValueDescriptor[] sparseRowArray;
087: private int[] partialChangedColumnIds;
088:
089: /**
090: Create a new RowChanger for performing update and delete operations
091: based on partial before and after rows.
092:
093: @param heapConglom Conglomerate # for the heap
094: @param heapSCOCI SCOCI for heap.
095: @param heapDCOCI DCOCI for heap
096: @param irgs the IndexRowGenerators for the table's indexes. We use
097: positions in this array as local id's for indexes. To support updates,
098: only indexes that change need be included.
099: @param indexCIDS the conglomerateids for the table's idexes.
100: indexCIDS[ix] corresponds to the same index as irgs[ix].
101: @param indexSCOCIs the SCOCIs for the table's idexes.
102: indexSCOCIs[ix] corresponds to the same index as irgs[ix].
103: @param indexDCOCIs the DCOCIs for the table's idexes.
104: indexDCOCIs[ix] corresponds to the same index as irgs[ix].
105: @param numberOfColumns Number of columns in partial write row.
106: @param changedColumnIdsInput array of 1 based ints indicating the columns
107: to be updated. Only used for updates
108: @param tc the transaction controller
109: @param baseRowReadList bit set of columns read from base row. 1 based.
110: @param baseRowReadMap BaseRowReadMap[heapColId]->ReadRowColumnId. (0 based)
111: @exception StandardException Thrown on error
112: */
113: public RowChangerImpl(long heapConglom,
114: StaticCompiledOpenConglomInfo heapSCOCI,
115: DynamicCompiledOpenConglomInfo heapDCOCI,
116: IndexRowGenerator[] irgs, long[] indexCIDS,
117: StaticCompiledOpenConglomInfo[] indexSCOCIs,
118: DynamicCompiledOpenConglomInfo[] indexDCOCIs,
119: int numberOfColumns, int[] changedColumnIdsInput,
120: TransactionController tc, FormatableBitSet baseRowReadList,
121: int[] baseRowReadMap, Activation activation)
122: throws StandardException {
123: this .heapConglom = heapConglom;
124: this .heapSCOCI = heapSCOCI;
125: this .heapDCOCI = heapDCOCI;
126: this .irgs = irgs;
127: this .indexCIDS = indexCIDS;
128: this .indexSCOCIs = indexSCOCIs;
129: this .indexDCOCIs = indexDCOCIs;
130: this .tc = tc;
131: this .baseRowReadList = baseRowReadList;
132: this .baseRowReadMap = baseRowReadMap;
133: this .activation = activation;
134:
135: if (SanityManager.DEBUG) {
136: SanityManager
137: .ASSERT(indexCIDS != null, "indexCIDS is null");
138: }
139:
140: /*
141: ** Construct the update column FormatableBitSet.
142: ** It is 0 based as opposed to the 1 based
143: ** changed column ids.
144: */
145: if (changedColumnIdsInput != null) {
146: /*
147: ** Sometimes replication does not have columns
148: ** in sorted order, and basically needs to
149: ** have the changed columns in non-sorted order.
150: ** So sort them first if needed.
151: */
152: changedColumnIds = RowUtil
153: .inAscendingOrder(changedColumnIdsInput) ? changedColumnIdsInput
154: : sortArray(changedColumnIdsInput);
155:
156: /*
157: ** Allocate the row array we are going to use during
158: ** update here, to avoid extra work. setup
159: ** the FormatableBitSet of columns being updated. See updateRow
160: ** for the use.
161: **
162: ** changedColumnIds is guaranteed to be in order, so just take
163: ** the last column number in the array to be the highest
164: ** column number.
165: */
166: sparseRowArray = new DataValueDescriptor[changedColumnIds[changedColumnIds.length - 1] + 1];
167: changedColumnBitSet = new FormatableBitSet(numberOfColumns);
168: for (int i = 0; i < changedColumnIds.length; i++) {
169: // make sure changedColumnBitSet can accomodate bit
170: // changedColumnIds[i] - 1
171: changedColumnBitSet.grow(changedColumnIds[i]);
172: changedColumnBitSet.set(changedColumnIds[i] - 1);
173: }
174:
175: /*
176: ** If we have a read map and a write map, we
177: ** need to have a way to map the changed column
178: ** ids to be relative to the read map.
179: */
180: if (baseRowReadList != null) {
181: partialChangedColumnIds = new int[changedColumnIds.length];
182: int partialColumnNumber = 1;
183: int currentColumn = 0;
184: for (int i = 0; i < changedColumnIds.length; i++) {
185: for (; currentColumn < changedColumnIds[i]; currentColumn++) {
186: if (baseRowReadList.get(currentColumn)) {
187: partialColumnNumber++;
188: }
189: }
190: partialChangedColumnIds[i] = partialColumnNumber;
191: }
192: }
193: }
194:
195: if (SanityManager.DEBUG) {
196: SanityManager
197: .ASSERT(indexCIDS != null, "indexCIDS is null");
198: }
199:
200: }
201:
202: /**
203: * Set the row holder for this changer to use.
204: * If the row holder is set, it wont bother
205: * saving copies of rows needed for deferred
206: * processing. Also, it will never close the
207: * passed in rowHolder.
208: *
209: * @param rowHolder the TemporaryRowHolder
210: */
211: public void setRowHolder(TemporaryRowHolder rowHolder) {
212: this .rowHolder = (TemporaryRowHolderImpl) rowHolder;
213: }
214:
215: /**
216: * @see RowChanger#setIndexNames
217: */
218: public void setIndexNames(String[] indexNames) {
219: this .indexNames = indexNames;
220: }
221:
222: /**
223: Open this RowChanger.
224:
225: <P>Note to avoid the cost of fixing indexes that do not
226: change during update operations use openForUpdate().
227: @param lockMode The lock mode to use
228: (row or table, see TransactionController)
229:
230: @exception StandardException thrown on failure to convert
231: */
232: public void open(int lockMode) throws StandardException {
233: open(lockMode, true);
234: }
235:
236: /**
237: * @inheritDoc
238: */
239: public void open(int lockMode, boolean wait)
240: throws StandardException {
241: //
242: //We open for update but say to fix every index on
243: //updates.
244: if (fixOnUpdate == null) {
245: fixOnUpdate = new boolean[irgs.length];
246: for (int ix = 0; ix < irgs.length; ix++)
247: fixOnUpdate[ix] = true;
248: }
249: openForUpdate(fixOnUpdate, lockMode, wait);
250: }
251:
252: /**
253: Open this RowChanger to avoid fixing indexes that do not change
254: during update operations.
255:
256: @param fixOnUpdate fixOnUpdat[ix] == true ==> fix index 'ix' on
257: an update operation.
258: @param lockMode The lock mode to use
259: (row or table, see TransactionController)
260: @param wait If true, then the caller wants to wait for locks. False will be
261: when we using a nested user xaction - we want to timeout right away
262: if the parent holds the lock. (bug 4821)
263:
264: @exception StandardException thrown on failure to convert
265: */
266: public void openForUpdate(boolean[] fixOnUpdate, int lockMode,
267: boolean wait) throws StandardException {
268: LanguageConnectionContext lcc = null;
269:
270: if (SanityManager.DEBUG)
271: SanityManager.ASSERT(!isOpen, "RowChanger already open");
272:
273: if (activation != null) {
274: lcc = activation.getLanguageConnectionContext();
275: }
276:
277: /* Isolation level - translate from language to store */
278: int isolationLevel;
279: if (lcc == null) {
280: isolationLevel = ExecutionContext.READ_COMMITTED_ISOLATION_LEVEL;
281: } else {
282: isolationLevel = lcc.getCurrentIsolationLevel();
283: }
284:
285: switch (isolationLevel) {
286: // Even though we preserve the isolation level at READ UNCOMMITTED,
287: // Cloudscape Store will overwrite it to READ COMMITTED for update.
288: case ExecutionContext.READ_UNCOMMITTED_ISOLATION_LEVEL:
289: isolationLevel = TransactionController.ISOLATION_READ_UNCOMMITTED;
290: break;
291:
292: case ExecutionContext.READ_COMMITTED_ISOLATION_LEVEL:
293: isolationLevel = TransactionController.ISOLATION_READ_COMMITTED;
294: break;
295:
296: case ExecutionContext.REPEATABLE_READ_ISOLATION_LEVEL:
297: isolationLevel = TransactionController.ISOLATION_REPEATABLE_READ;
298: break;
299:
300: case ExecutionContext.SERIALIZABLE_ISOLATION_LEVEL:
301: isolationLevel = TransactionController.ISOLATION_SERIALIZABLE;
302: break;
303:
304: default:
305: if (SanityManager.DEBUG) {
306: SanityManager.THROWASSERT("Invalid isolation level - "
307: + isolationLevel);
308: }
309: }
310:
311: try {
312:
313: /* We can get called by either an activation or
314: * the DataDictionary. The DD cannot use the
315: * CompiledInfo while the activation can.
316: */
317: if (heapSCOCI != null) {
318: baseCC = tc
319: .openCompiledConglomerate(
320: false,
321: (TransactionController.OPENMODE_FORUPDATE | ((wait) ? 0
322: : TransactionController.OPENMODE_LOCK_NOWAIT)),
323: lockMode, isolationLevel, heapSCOCI,
324: heapDCOCI);
325: } else {
326: baseCC = tc
327: .openConglomerate(
328: heapConglom,
329: false,
330: (TransactionController.OPENMODE_FORUPDATE | ((wait) ? 0
331: : TransactionController.OPENMODE_LOCK_NOWAIT)),
332: lockMode, isolationLevel);
333: }
334:
335: } catch (StandardException se) {
336: if (activation != null)
337: activation.checkStatementValidity();
338: throw se;
339: }
340:
341: /* Save the ConglomerateController off in the activation
342: * to eliminate the need to open it a 2nd time if we are doing
343: * and index to base row for the search as part of an update or
344: * delete below us.
345: * NOTE: activation can be null. (We don't have it in
346: * the DataDictionary.)
347: */
348: if (activation != null) {
349: activation.checkStatementValidity();
350: activation.setHeapConglomerateController(baseCC);
351: }
352:
353: /* Only worry about indexes if there are indexes to worry about */
354: if (indexCIDS.length != 0) {
355: /* IndexSetChanger re-used across executions. */
356: if (isc == null) {
357: isc = new IndexSetChanger(irgs, indexCIDS, indexSCOCIs,
358: indexDCOCIs, indexNames, baseCC, tc, lockMode,
359: baseRowReadList, isolationLevel, activation);
360: isc.setRowHolder(rowHolder);
361: } else {
362:
363: /* Propagate the heap's ConglomerateController to
364: * all of the underlying index changers.
365: */
366: isc.setBaseCC(baseCC);
367: }
368:
369: isc.open(fixOnUpdate);
370:
371: if (baseRowLocation == null)
372: baseRowLocation = baseCC.newRowLocationTemplate();
373: }
374:
375: isOpen = true;
376: }
377:
378: /**
379: Insert a row into the table and perform associated index maintenance.
380:
381: @param baseRow the row.
382: @exception StandardException Thrown on error
383: */
384: public void insertRow(ExecRow baseRow) throws StandardException {
385: if (SanityManager.DEBUG)
386: SanityManager.ASSERT(!baseCC.isKeyed(),
387: "Keyed inserts not yet supported");
388:
389: if (baseCC.isKeyed()) {
390: //kcc.insert(row.key(), row());
391: } else {
392: if (isc != null) {
393: baseCC.insertAndFetchLocation(baseRow.getRowArray(),
394: baseRowLocation);
395: isc.insert(baseRow, baseRowLocation);
396: } else {
397: baseCC.insert(baseRow.getRowArray());
398: }
399: }
400: }
401:
402: /**
403: Delete a row from the table and perform associated index maintenance.
404:
405: @param baseRow the row.
406: @param baseRowLocation the row's base conglomerate
407: location
408: @exception StandardException Thrown on error
409: */
410: public void deleteRow(ExecRow baseRow, RowLocation baseRowLocation)
411: throws StandardException {
412: if (isc != null) {
413: isc.delete(baseRow, baseRowLocation);
414: }
415: baseCC.delete(baseRowLocation);
416: }
417:
418: /**
419: Update a row in the table and perform associated index maintenance.
420:
421: @param oldBaseRow the old image of the row.
422: @param newBaseRow the new image of the row.
423: @param baseRowLocation the row's base conglomerate
424: location
425: @exception StandardException Thrown on error
426: */
427: public void updateRow(ExecRow oldBaseRow, ExecRow newBaseRow,
428: RowLocation baseRowLocation) throws StandardException {
429: if (isc != null) {
430: isc.update(oldBaseRow, newBaseRow, baseRowLocation);
431: }
432:
433: if (changedColumnBitSet != null) {
434: DataValueDescriptor[] baseRowArray = newBaseRow
435: .getRowArray();
436: int[] changedColumnArray = (partialChangedColumnIds == null) ? changedColumnIds
437: : partialChangedColumnIds;
438: int nextColumnToUpdate = -1;
439: for (int i = 0; i < changedColumnArray.length; i++) {
440: int copyFrom = changedColumnArray[i] - 1;
441: nextColumnToUpdate = changedColumnBitSet
442: .anySetBit(nextColumnToUpdate);
443: if (SanityManager.DEBUG) {
444: SanityManager
445: .ASSERT(nextColumnToUpdate >= 0,
446: "More columns in changedColumnArray than in changedColumnBitSet");
447: }
448: sparseRowArray[nextColumnToUpdate] = baseRowArray[copyFrom];
449: }
450: } else {
451: sparseRowArray = newBaseRow.getRowArray();
452: }
453: baseCC.replace(baseRowLocation, sparseRowArray,
454: changedColumnBitSet);
455: }
456:
457: /**
458: Finish processing the changes. This means applying the deferred
459: inserts for updates to unique indexes.
460:
461: @exception StandardException Thrown on error
462: */
463: public void finish() throws StandardException {
464: if (isc != null) {
465: isc.finish();
466: }
467: }
468:
469: /**
470: Close this RowChanger.
471:
472: @exception StandardException Thrown on error
473: */
474: public void close() throws StandardException {
475: //
476: //NOTE: isc uses baseCC. Since we close baseCC we free isc for now.
477: //We could consider making isc open its own baseCC or even leaving
478: //baseCC open to promote re-use. We must keep in mind that baseCC
479: //is associated with the opener's TransactionController.
480: if (isc != null) {
481: isc.close();
482: }
483:
484: if (baseCC != null) {
485: if (activation == null
486: || activation.getForUpdateIndexScan() == null)
487: baseCC.close(); //beetle 3865, don't close if borrowed to cursor
488: baseCC = null;
489: }
490:
491: isOpen = false;
492:
493: // rowHolder is reused across executions and closed by caller
494: // since caller creates it
495:
496: if (activation != null) {
497: activation.clearHeapConglomerateController();
498: }
499: }
500:
501: /** @see RowChanger#getHeapConglomerateController */
502: public ConglomerateController getHeapConglomerateController() {
503: return baseCC;
504: }
505:
506: private int[] sortArray(int[] input) {
507: /*
508: ** Sotring.sort() will change the underlying array, so we
509: ** 'clone' it first
510: */
511: int[] output = new int[input.length];
512: System.arraycopy(input, 0, output, 0, input.length);
513: java.util.Arrays.sort(output);
514: return output;
515: }
516: }
|