001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.execute.IndexChanger
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.monitor.Monitor;
025: import org.apache.derby.iapi.services.sanity.SanityManager;
026: import org.apache.derby.iapi.error.StandardException;
027: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
028: import org.apache.derby.iapi.sql.dictionary.IndexRowGenerator;
029: import org.apache.derby.iapi.sql.ResultDescription;
030: import org.apache.derby.iapi.sql.execute.CursorResultSet;
031: import org.apache.derby.iapi.sql.execute.ExecRow;
032: import org.apache.derby.iapi.sql.execute.ExecIndexRow;
033: import org.apache.derby.iapi.sql.execute.ExecutionContext;
034: import org.apache.derby.iapi.sql.execute.ExecutionFactory;
035:
036: import org.apache.derby.iapi.reference.SQLState;
037:
038: import org.apache.derby.iapi.sql.Activation;
039:
040: import org.apache.derby.iapi.store.access.ConglomerateController;
041: import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;
042: import org.apache.derby.iapi.store.access.ScanController;
043: import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;
044: import org.apache.derby.iapi.store.access.TransactionController;
045:
046: import org.apache.derby.catalog.UUID;
047: import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
048: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
049: import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
050: import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptor;
051: import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList;
052:
053: import org.apache.derby.iapi.types.DataValueDescriptor;
054: import org.apache.derby.iapi.types.RowLocation;
055:
056: import org.apache.derby.iapi.services.io.FormatableBitSet;
057: import org.apache.derby.iapi.services.i18n.MessageService;
058: import java.util.Properties;
059:
060: /**
061: Perform Index maintenace associated with DML operations for a single index.
062: */
063: public class IndexChanger {
064: private IndexRowGenerator irg;
065: //Index Conglomerate ID
066: private long indexCID;
067: private DynamicCompiledOpenConglomInfo indexDCOCI;
068: private StaticCompiledOpenConglomInfo indexSCOCI;
069: private String indexName;
070: private ConglomerateController baseCC;
071: private TransactionController tc;
072: private int lockMode;
073: private FormatableBitSet baseRowReadMap;
074:
075: private ConglomerateController indexCC = null;
076: private ScanController indexSC = null;
077:
078: private LanguageConnectionContext lcc;
079:
080: //
081: //Index rows used by this module to perform DML.
082: private ExecIndexRow ourIndexRow = null;
083: private ExecIndexRow ourUpdatedIndexRow = null;
084:
085: private TemporaryRowHolderImpl rowHolder = null;
086: private boolean rowHolderPassedIn;
087: private int isolationLevel;
088: private Activation activation;
089: private boolean ownIndexSC = true;
090:
091: /**
092: Create an IndexChanger
093:
094: @param irg the IndexRowGenerator for the index.
095: @param indexCID the conglomerate id for the index.
096: @param indexSCOCI the SCOCI for the idexes.
097: @param indexDCOCI the DCOCI for the idexes.
098: @param baseCC the ConglomerateController for the base table.
099: @param tc The TransactionController
100: @param lockMode The lock mode (granularity) to use
101: @param baseRowReadMap Map of columns read in. 1 based.
102: @param isolationLevel Isolation level to use.
103: @param activation Current activation
104:
105: @exception StandardException Thrown on error
106: */
107: public IndexChanger(IndexRowGenerator irg, long indexCID,
108: StaticCompiledOpenConglomInfo indexSCOCI,
109: DynamicCompiledOpenConglomInfo indexDCOCI,
110: String indexName, ConglomerateController baseCC,
111: TransactionController tc, int lockMode,
112: FormatableBitSet baseRowReadMap, int isolationLevel,
113: Activation activation) throws StandardException {
114: this .irg = irg;
115: this .indexCID = indexCID;
116: this .indexSCOCI = indexSCOCI;
117: this .indexDCOCI = indexDCOCI;
118: this .baseCC = baseCC;
119: this .tc = tc;
120: this .lockMode = lockMode;
121: this .baseRowReadMap = baseRowReadMap;
122: this .rowHolderPassedIn = false;
123: this .isolationLevel = isolationLevel;
124: this .activation = activation;
125: this .indexName = indexName;
126:
127: // activation will be null when called from DataDictionary
128: if (activation != null
129: && activation.getIndexConglomerateNumber() == indexCID) {
130: ownIndexSC = false;
131: }
132:
133: if (SanityManager.DEBUG) {
134: SanityManager
135: .ASSERT(tc != null,
136: "TransactionController argument to constructor is null");
137: SanityManager
138: .ASSERT(irg != null,
139: "IndexRowGenerator argument to constructor is null");
140: }
141: }
142:
143: /**
144: * Set the row holder for this changer to use.
145: * If the row holder is set, it wont bother
146: * saving copies of rows needed for deferred
147: * processing. Also, it will never close the
148: * passed in rowHolder.
149: *
150: * @param rowHolder the row holder
151: */
152: public void setRowHolder(TemporaryRowHolderImpl rowHolder) {
153: this .rowHolder = rowHolder;
154: rowHolderPassedIn = (rowHolder != null);
155: }
156:
157: /**
158: * Propagate the heap's ConglomerateController to
159: * this index changer.
160: *
161: * @param baseCC The heap's ConglomerateController.
162: */
163: public void setBaseCC(ConglomerateController baseCC) {
164: this .baseCC = baseCC;
165: }
166:
167: /**
168: Set the column values for 'ourIndexRow' to refer to
169: a base table row and location provided by the caller.
170: The idea here is to
171: @param baseRow a base table row.
172: @param baseRowLoc baseRowLoc baseRow's location
173: @exception StandardException Thrown on error
174: */
175: private void setOurIndexRow(ExecRow baseRow, RowLocation baseRowLoc)
176: throws StandardException {
177: if (ourIndexRow == null)
178: ourIndexRow = irg.getIndexRowTemplate();
179:
180: irg.getIndexRow(baseRow, baseRowLoc, ourIndexRow,
181: baseRowReadMap);
182: }
183:
184: /**
185: Set the column values for 'ourUpdatedIndexRow' to refer to
186: a base table row and location provided by the caller.
187: The idea here is to
188: @param baseRow a base table row.
189: @param baseRowLoc baseRowLoc baseRow's location
190: @exception StandardException Thrown on error
191: */
192: private void setOurUpdatedIndexRow(ExecRow baseRow,
193: RowLocation baseRowLoc) throws StandardException {
194: if (ourUpdatedIndexRow == null)
195: ourUpdatedIndexRow = irg.getIndexRowTemplate();
196:
197: irg.getIndexRow(baseRow, baseRowLoc, ourUpdatedIndexRow,
198: baseRowReadMap);
199: }
200:
201: /**
202: * Determine whether or not any columns in the current index
203: * row are being changed by the update. No need to update the
204: * index if no columns changed.
205: *
206: * @return Nothing.
207: *
208: * @exception StandardException Thrown on error
209: */
210: private boolean indexRowChanged() throws StandardException {
211: int numColumns = ourIndexRow.nColumns();
212: for (int index = 1; index <= numColumns; index++) {
213: DataValueDescriptor oldOrderable = ourIndexRow
214: .getColumn(index);
215: DataValueDescriptor newOrderable = ourUpdatedIndexRow
216: .getColumn(index);
217: if (!(oldOrderable.compare(
218: DataValueDescriptor.ORDER_OP_EQUALS, newOrderable,
219: true, true))) {
220: return true;
221: }
222: }
223: return false;
224: }
225:
226: private ExecIndexRow getDeferredIndexRowTemplate(ExecRow baseRow,
227: RowLocation baseRowLoc) throws StandardException {
228: ExecIndexRow template;
229:
230: template = irg.getIndexRowTemplate();
231:
232: irg.getIndexRow(baseRow, baseRowLoc, template, baseRowReadMap);
233:
234: return template;
235: }
236:
237: /**
238: Position our index scan to 'ourIndexRow'.
239:
240: <P>This creates the scan the first time it is called.
241:
242: @exception StandardException Thrown on error
243: */
244: private void setScan() throws StandardException {
245: /* Get the SC from the activation if re-using */
246: if (!ownIndexSC) {
247: indexSC = activation.getIndexScanController();
248: } else if (indexSC == null) {
249: RowLocation templateBaseRowLocation = baseCC
250: .newRowLocationTemplate();
251: /* DataDictionary doesn't have compiled info */
252: if (indexSCOCI == null) {
253: indexSC = tc.openScan(indexCID, false, /* hold */
254: TransactionController.OPENMODE_FORUPDATE, /* forUpdate */
255: lockMode, isolationLevel, (FormatableBitSet) null, /* all fields */
256: ourIndexRow.getRowArray(), /* startKeyValue */
257: ScanController.GE, /* startSearchOp */
258: null, /* qualifier */
259: ourIndexRow.getRowArray(), /* stopKeyValue */
260: ScanController.GT /* stopSearchOp */
261: );
262: } else {
263: indexSC = tc.openCompiledScan(false, /* hold */
264: TransactionController.OPENMODE_FORUPDATE, /* forUpdate */
265: lockMode, isolationLevel, (FormatableBitSet) null, /* all fields */
266: ourIndexRow.getRowArray(), /* startKeyValue */
267: ScanController.GE, /* startSearchOp */
268: null, /* qualifier */
269: ourIndexRow.getRowArray(), /* stopKeyValue */
270: ScanController.GT, /* stopSearchOp */
271: indexSCOCI, indexDCOCI);
272: }
273: } else {
274: indexSC.reopenScan(ourIndexRow.getRowArray(), /* startKeyValue */
275: ScanController.GE, /* startSearchOperator */
276: null, /* qualifier */
277: ourIndexRow.getRowArray(), /* stopKeyValue */
278: ScanController.GT /* stopSearchOperator */
279: );
280: }
281: }
282:
283: /**
284: Close our index Conglomerate Controller
285: */
286: private void closeIndexCC() throws StandardException {
287: if (indexCC != null)
288: indexCC.close();
289: indexCC = null;
290: }
291:
292: /**
293: Close our index ScanController.
294: */
295: private void closeIndexSC() throws StandardException {
296: /* Only consider closing index SC if we own it. */
297: if (ownIndexSC && indexSC != null) {
298: indexSC.close();
299: indexSC = null;
300: }
301: }
302:
303: /**
304: Delete a row from our index. This assumes our index ScanController
305: is positioned before the row by setScan if we own the SC, otherwise
306: it is positioned on the row by the underlying index scan.
307:
308: <P>This verifies the row exists and is unique.
309:
310: @exception StandardException Thrown on error
311: */
312: private void doDelete() throws StandardException {
313: if (ownIndexSC) {
314: if (!indexSC.next()) {
315: // This means that the entry for the index does not exist, this
316: // is a serious problem with the index. Past fixed problems
317: // like track 3703 can leave db's in the field with this problem
318: // even though the bug in the code which caused it has long
319: // since been fixed. Then the problem can surface months later
320: // when the customer attempts to upgrade. By "ignoring" the
321: // missing row here the problem is automatically "fixed" and
322: // since the code is trying to delete the row anyway it doesn't
323: // seem like such a bad idea. It also then gives a tool to
324: // support to be able to fix some system catalog problems where
325: // they can delete the base rows by dropping the system objects
326: // like stored statements.
327:
328: if (SanityManager.DEBUG)
329: SanityManager.THROWASSERT("Index row "
330: + RowUtil.toString(ourIndexRow)
331: + " not found in conglomerateid "
332: + indexCID + "Current scan = " + indexSC);
333:
334: Object[] args = new Object[2];
335: args[0] = ourIndexRow.getRowArray()[ourIndexRow
336: .getRowArray().length - 1];
337: args[1] = new Long(indexCID);
338:
339: Monitor
340: .getStream()
341: .println(
342: MessageService
343: .getCompleteMessage(
344: SQLState.LANG_IGNORE_MISSING_INDEX_ROW_DURING_DELETE,
345: args));
346:
347: // just return indicating the row has been deleted.
348: return;
349: }
350: }
351:
352: indexSC.delete();
353: }
354:
355: /**
356: Insert a row into our indes.
357:
358: <P>This opens our index ConglomeratController the first time it
359: is called.
360:
361: @exception StandardException Thrown on error
362: */
363: private void doInsert() throws StandardException {
364: insertAndCheckDups(ourIndexRow);
365: }
366:
367: /**
368: Insert a row into the temporary conglomerate
369:
370: <P>This opens our deferred ConglomeratController the first time it
371: is called.
372:
373: @exception StandardException Thrown on error
374: */
375: private void doDeferredInsert() throws StandardException {
376: if (rowHolder == null) {
377: Properties properties = new Properties();
378:
379: // Get the properties on the index
380: openIndexCC().getInternalTablePropertySet(properties);
381:
382: /*
383: ** Create our row holder. it is ok to skip passing
384: ** in the result description because if we don't already
385: ** have a row holder, then we are the only user of the
386: ** row holder (the description is needed when the row
387: ** holder is going to be handed to users for triggers).
388: */
389: rowHolder = new TemporaryRowHolderImpl(activation,
390: properties, (ResultDescription) null);
391: }
392:
393: /*
394: ** If the user of the IndexChanger already
395: ** had a row holder, then we don't need to
396: ** bother saving deferred inserts -- they
397: ** have already done so.
398: */
399: if (!rowHolderPassedIn) {
400: rowHolder.insert(ourIndexRow);
401: }
402: }
403:
404: /**
405: * Insert the given row into the given conglomerate and check for duplicate
406: * key error.
407: *
408: * @param row The row to insert
409: *
410: * @exception StandardException Thrown on duplicate key error
411: */
412: private void insertAndCheckDups(ExecIndexRow row)
413: throws StandardException {
414: openIndexCC();
415:
416: int insertStatus = indexCC.insert(row.getRowArray());
417:
418: if (insertStatus == ConglomerateController.ROWISDUPLICATE) {
419: /*
420: ** We have a duplicate key error.
421: */
422: String indexOrConstraintName = indexName;
423: // now get table name, and constraint name if needed
424: LanguageConnectionContext lcc = activation
425: .getLanguageConnectionContext();
426: DataDictionary dd = lcc.getDataDictionary();
427: //get the descriptors
428: ConglomerateDescriptor cd = dd
429: .getConglomerateDescriptor(indexCID);
430:
431: UUID tableID = cd.getTableID();
432: TableDescriptor td = dd.getTableDescriptor(tableID);
433: String tableName = td.getName();
434:
435: if (indexOrConstraintName == null) // no index name passed in
436: {
437: ConstraintDescriptor conDesc = dd
438: .getConstraintDescriptor(td, cd.getUUID());
439: indexOrConstraintName = conDesc.getConstraintName();
440: }
441:
442: StandardException se = StandardException.newException(
443: SQLState.LANG_DUPLICATE_KEY_CONSTRAINT,
444: indexOrConstraintName, tableName);
445: throw se;
446: }
447: if (SanityManager.DEBUG) {
448: if (insertStatus != 0) {
449: SanityManager.THROWASSERT("Unknown insert status "
450: + insertStatus);
451: }
452: }
453: }
454:
455: /**
456: * Open the ConglomerateController for this index if it isn't open yet.
457: *
458: * @return The ConglomerateController for this index.
459: *
460: * @exception StandardException Thrown on duplicate key error
461: */
462: private ConglomerateController openIndexCC()
463: throws StandardException {
464: if (indexCC == null) {
465: /* DataDictionary doesn't have compiled info */
466: if (indexSCOCI == null) {
467: indexCC = tc
468: .openConglomerate(
469: indexCID,
470: false,
471: (TransactionController.OPENMODE_FORUPDATE | TransactionController.OPENMODE_BASEROW_INSERT_LOCKED),
472: lockMode, isolationLevel);
473: } else {
474: indexCC = tc
475: .openCompiledConglomerate(
476: false,
477: (TransactionController.OPENMODE_FORUPDATE | TransactionController.OPENMODE_BASEROW_INSERT_LOCKED),
478: lockMode, isolationLevel, indexSCOCI,
479: indexDCOCI);
480: }
481: }
482:
483: return indexCC;
484: }
485:
486: /**
487: Open this IndexChanger.
488:
489: @exception StandardException Thrown on error
490: */
491: public void open() throws StandardException {
492: }
493:
494: /**
495: Perform index maintenance to support a delete of a base table row.
496:
497: @param baseRow the base table row.
498: @param baseRowLocation the base table row's location.
499: @exception StandardException Thrown on error
500: */
501: public void delete(ExecRow baseRow, RowLocation baseRowLocation)
502: throws StandardException {
503: setOurIndexRow(baseRow, baseRowLocation);
504: setScan();
505: doDelete();
506: }
507:
508: /**
509: Perform index maintenance to support an update of a base table row.
510:
511: @param oldBaseRow the old image of the base table row.
512: @param newBaseRow the new image of the base table row.
513: @param baseRowLocation the base table row's location.
514:
515: @exception StandardException Thrown on error
516: */
517: public void update(ExecRow oldBaseRow, ExecRow newBaseRow,
518: RowLocation baseRowLocation) throws StandardException {
519: setOurIndexRow(oldBaseRow, baseRowLocation);
520: setOurUpdatedIndexRow(newBaseRow, baseRowLocation);
521:
522: /* We skip the update in the degenerate case
523: * where none of the key columns changed.
524: * (From an actual customer case.)
525: */
526: if (indexRowChanged()) {
527: setScan();
528: doDelete();
529: insertForUpdate(newBaseRow, baseRowLocation);
530: }
531: }
532:
533: /**
534: Perform index maintenance to support an insert of a base table row.
535:
536: @param newRow the base table row.
537: @param baseRowLocation the base table row's location.
538:
539: @exception StandardException Thrown on error
540: */
541: public void insert(ExecRow newRow, RowLocation baseRowLocation)
542: throws StandardException {
543: setOurIndexRow(newRow, baseRowLocation);
544: doInsert();
545: }
546:
547: /**
548: If we're updating a unique index, the inserts have to be
549: deferred. This is to avoid uniqueness violations that are only
550: temporary. If we do all the deletes first, only "true" uniqueness
551: violations can happen. We do this here, rather than in open(),
552: because this is the only operation that requires deferred inserts,
553: and we only want to create the conglomerate if necessary.
554:
555: @param newRow the base table row.
556: @param baseRowLocation the base table row's location.
557:
558: @exception StandardException Thrown on error
559: */
560: void insertForUpdate(ExecRow newRow, RowLocation baseRowLocation)
561: throws StandardException {
562: setOurIndexRow(newRow, baseRowLocation);
563:
564: if (irg.isUnique()) {
565: doDeferredInsert();
566: } else {
567: doInsert();
568: }
569: }
570:
571: /**
572: Finish doing the changes for this index. This is intended for deferred
573: inserts for unique indexes. It has no effect unless we are doing an
574: update of a unique index.
575:
576: @exception StandardException Thrown on error
577: */
578: public void finish() throws StandardException {
579: ExecRow deferredRow;
580: ExecIndexRow deferredIndexRow = new IndexRow();
581:
582: /* Deferred processing only necessary for unique indexes */
583: if (rowHolder != null) {
584: CursorResultSet rs = rowHolder.getResultSet();
585: try {
586: rs.open();
587: while ((deferredRow = rs.getNextRow()) != null) {
588: if (SanityManager.DEBUG) {
589: if (!(deferredRow instanceof ExecIndexRow)) {
590: SanityManager
591: .THROWASSERT("deferredRow isn't an instance "
592: + "of ExecIndexRow as expected. "
593: + "It is an "
594: + deferredRow.getClass()
595: .getName());
596: }
597: }
598: insertAndCheckDups((ExecIndexRow) deferredRow);
599: }
600: } finally {
601: rs.close();
602:
603: /*
604: ** If row holder was passed in, let the
605: ** client of this method clean it up.
606: */
607: if (!rowHolderPassedIn) {
608: rowHolder.close();
609: }
610: }
611: }
612: }
613:
614: /**
615: Close this IndexChanger.
616:
617: @exception StandardException Thrown on error
618: */
619: public void close() throws StandardException {
620: closeIndexCC();
621: closeIndexSC();
622: if (rowHolder != null && !rowHolderPassedIn) {
623: rowHolder.close();
624: }
625: baseCC = null;
626: }
627: }
|