001: /*
002:
003: Derby - Class org.apache.derby.impl.store.access.heap.HeapController
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.store.access.heap;
023:
024: import org.apache.derby.iapi.reference.SQLState;
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.store.access.conglomerate.TransactionManager;
031:
032: import org.apache.derby.iapi.store.access.AccessFactoryGlobals;
033: import org.apache.derby.iapi.store.access.ConglomerateController;
034: import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;
035: import org.apache.derby.iapi.store.access.RowLocationRetRowSource;
036: import org.apache.derby.iapi.store.access.RowUtil;
037: import org.apache.derby.iapi.store.access.TransactionController;
038:
039: import org.apache.derby.iapi.store.raw.ContainerHandle;
040: import org.apache.derby.iapi.store.raw.LockingPolicy;
041: import org.apache.derby.iapi.store.raw.Page;
042: import org.apache.derby.iapi.store.raw.RecordHandle;
043:
044: import org.apache.derby.iapi.types.DataValueDescriptor;
045:
046: import org.apache.derby.iapi.types.RowLocation;
047:
048: import org.apache.derby.impl.store.access.conglomerate.OpenConglomerate;
049: import org.apache.derby.impl.store.access.conglomerate.GenericConglomerateController;
050: import org.apache.derby.impl.store.access.conglomerate.RowPosition;
051:
052: import org.apache.derby.iapi.services.io.FormatableBitSet;
053:
054: /**
055:
056: **/
057:
058: public class HeapController extends GenericConglomerateController
059: implements ConglomerateController {
060: /**************************************************************************
061: * Fields of the class
062: **************************************************************************
063: */
064:
065: /**************************************************************************
066: * Constructors for This class:
067: **************************************************************************
068: */
069:
070: /**************************************************************************
071: * Protected concrete impl of abstract methods of
072: * GenericConglomerateController class:
073: **************************************************************************
074: */
075: protected final void getRowPositionFromRowLocation(
076: RowLocation row_loc, RowPosition pos)
077: throws StandardException {
078: if (SanityManager.DEBUG) {
079: SanityManager.ASSERT(row_loc instanceof HeapRowLocation);
080: }
081: pos.current_rh = ((HeapRowLocation) row_loc)
082: .getRecordHandle(open_conglom.getContainer());
083: pos.current_rh_qualified = true;
084: }
085:
086: protected void queueDeletePostCommitWork(RowPosition pos)
087: throws StandardException {
088: TransactionManager xact_mgr = open_conglom.getXactMgr();
089:
090: xact_mgr.addPostCommitWork(new HeapPostCommit(xact_mgr
091: .getAccessManager(), (Heap) open_conglom
092: .getConglomerate(), pos.current_page.getPageNumber()));
093: }
094:
095: /**************************************************************************
096: * Private/Protected methods of This class:
097: **************************************************************************
098: */
099:
100: /**
101: * Check and purge committed deleted rows on a page.
102: * <p>
103: *
104: * @return true, if no purging has been done on page, and thus latch
105: * can be released before end transaction. Otherwise the latch
106: * on the page can not be released before commit.
107: *
108: * @param page A non-null, latched page must be passed in. If all
109: * rows on page are purged, then page will be removed and
110: * latch released.
111: *
112: * @exception StandardException Standard exception policy.
113: **/
114: protected final boolean purgeCommittedDeletes(Page page)
115: throws StandardException {
116: boolean purgingDone = false;
117:
118: // The number records that can be reclaimed is:
119: // total recs - recs_not_deleted
120: int num_possible_commit_delete = page.recordCount()
121: - page.nonDeletedRecordCount();
122:
123: if (num_possible_commit_delete > 0) {
124: // loop backward so that purges which affect the slot table
125: // don't affect the loop (ie. they only move records we
126: // have already looked at).
127: for (int slot_no = page.recordCount() - 1; slot_no >= 0; slot_no--) {
128: boolean row_is_committed_delete = page
129: .isDeletedAtSlot(slot_no);
130:
131: if (row_is_committed_delete) {
132: // At this point we only know that the row is
133: // deleted, not whether it is committed.
134:
135: // see if we can purge the row, by getting an
136: // exclusive lock on the row. If it is marked
137: // deleted and we can get this lock, then it
138: // must be a committed delete and we can purge
139: // it.
140:
141: RecordHandle rh = page.fetchFromSlot(
142: (RecordHandle) null, slot_no,
143: RowUtil.EMPTY_ROW,
144: RowUtil.EMPTY_ROW_FETCH_DESCRIPTOR, true);
145:
146: row_is_committed_delete = this
147: .lockRowAtSlotNoWaitExclusive(rh);
148:
149: if (row_is_committed_delete) {
150: purgingDone = true;
151:
152: page.purgeAtSlot(slot_no, 1, false);
153: }
154: }
155: }
156: }
157: if (page.recordCount() == 0) {
158:
159: // Deallocate the current page with 0 rows on it.
160: this .removePage(page);
161:
162: // removePage guarantees to unlatch the page even if an
163: // exception is thrown. The page is protected against reuse
164: // because removePage locks it with a dealloc lock, so it
165: // is OK to release the latch even after a purgeAtSlot is
166: // called.
167: // @see ContainerHandle#removePage
168:
169: purgingDone = true;
170: }
171:
172: return (purgingDone);
173: }
174:
175: /**
176: * Insert a new row into the heap.
177: * <p>
178: * Overflow policy:
179: * The current heap access method implements an algorithm that optimizes
180: * for fetch efficiency vs. space efficiency. A row will not be over
181: * flowed unless it is bigger than a page. If it is bigger than a page
182: * then it's initial part will be placed on a page and then subsequent
183: * parts will be overflowed to other pages.
184: * <p>
185: *
186: * @return The record handle of the inserted row.
187: *
188: * @param row The row to insert.
189: *
190: * @exception StandardException Standard exception policy.
191: **/
192: private RecordHandle doInsert(DataValueDescriptor[] row)
193: throws StandardException {
194: Page page = null;
195: byte insert_mode;
196:
197: RecordHandle rh;
198:
199: if (SanityManager.DEBUG) {
200: Heap heap = (Heap) open_conglom.getConglomerate();
201: // Make sure valid columns are in the list. The RowUtil
202: // call is too expensive to make in a released system for
203: // every insert.
204:
205: int invalidColumn = RowUtil.columnOutOfRange(row, null,
206: heap.format_ids.length);
207:
208: if (invalidColumn >= 0) {
209: throw (StandardException.newException(
210: SQLState.HEAP_TEMPLATE_MISMATCH, new Long(
211: invalidColumn), new Long(
212: heap.format_ids.length)));
213: }
214: }
215:
216: // Get the last page that was returned for insert or the last page
217: // that was allocated.
218: page = open_conglom.getContainer().getPageForInsert(0);
219:
220: if (page != null) {
221:
222: // if there are 0 rows on the page allow the insert to overflow.
223: insert_mode = (page.recordCount() == 0) ? Page.INSERT_OVERFLOW
224: : Page.INSERT_DEFAULT;
225:
226: // Check to see if there is enough space on the page
227: // for the row.
228: rh = page.insert(row, null, insert_mode,
229: AccessFactoryGlobals.HEAP_OVERFLOW_THRESHOLD);
230: page.unlatch();
231: page = null;
232:
233: // If we have found a page with enough space for the row,
234: // insert it and release exclusive access to the page.
235: if (rh != null) {
236: return rh;
237:
238: }
239: }
240:
241: // If the last inserted page is now full, or RawStore have
242: // forgotten what it was, or the row cannot fit on the last
243: // inserted page, try to have rawStore get a relatively unfilled
244: // page.
245:
246: page = open_conglom.getContainer().getPageForInsert(
247: ContainerHandle.GET_PAGE_UNFILLED);
248:
249: if (page != null) {
250: // Do the insert all over again hoping that it will fit into
251: // this page, and if not, allocate a new page.
252:
253: // if there are 0 rows on the page allow the insert to overflow.
254: insert_mode = (page.recordCount() == 0) ? Page.INSERT_OVERFLOW
255: : Page.INSERT_DEFAULT;
256:
257: rh = page.insert(row, null, insert_mode,
258: AccessFactoryGlobals.HEAP_OVERFLOW_THRESHOLD);
259:
260: page.unlatch();
261: page = null;
262:
263: // If we have found a page with enough space for the row,
264: // insert it and release exclusive access to the page.
265: if (rh != null) {
266: return rh;
267: }
268: }
269:
270: page = open_conglom.getContainer().addPage();
271:
272: // At this point with long rows the raw store will guarantee
273: // that any size row will fit on an empty page.
274:
275: rh = page.insert(row, null, Page.INSERT_OVERFLOW,
276: AccessFactoryGlobals.HEAP_OVERFLOW_THRESHOLD);
277: page.unlatch();
278: page = null;
279:
280: if (SanityManager.DEBUG) {
281: // a null will only be returned if this page is not empty
282: SanityManager.ASSERT(rh != null);
283: }
284:
285: return rh;
286: }
287:
288: protected long load(TransactionManager xact_manager, Heap heap,
289: boolean createConglom, RowLocationRetRowSource rowSource)
290: throws StandardException {
291: long num_rows_loaded = 0;
292:
293: if (SanityManager.DEBUG) {
294: SanityManager
295: .ASSERT(open_conglom == null,
296: "load expects container handle to be closed on entry.");
297: }
298:
299: // The individual rows that are inserted are not logged. To use a
300: // logged interface, use insert. RESOLVE: do we want to allow client
301: // to use the load interface even for logged insert?
302: int mode = (ContainerHandle.MODE_FORUPDATE | ContainerHandle.MODE_UNLOGGED);
303:
304: // If the container is being created in the same operation, don't log
305: // page allocation.
306: if (createConglom)
307: mode |= ContainerHandle.MODE_CREATE_UNLOGGED;
308:
309: OpenConglomerate open_conglom = new OpenHeap();
310:
311: if (open_conglom.init((ContainerHandle) null, heap,
312: heap.format_ids, xact_manager, xact_manager
313: .getRawStoreXact(), false, mode,
314: TransactionController.MODE_TABLE,
315: xact_manager.getRawStoreXact().newLockingPolicy(
316: LockingPolicy.MODE_CONTAINER,
317: TransactionController.ISOLATION_SERIALIZABLE,
318: true), (DynamicCompiledOpenConglomInfo) null) == null) {
319: throw StandardException.newException(
320: SQLState.HEAP_CONTAINER_NOT_FOUND, new Long(heap.id
321: .getContainerId()));
322: }
323:
324: this .init(open_conglom);
325:
326: // For bulk loading, we always use only brand new page because the row
327: // insertion itself is not logged. We cannot pollute pages with
328: // pre-existing data with unlogged rows because nobody is going to wipe
329: // out these rows if the transaction rolls back. We are counting on
330: // the allocation page rollback to obliterate these rows if the
331: // transaction fails, or, in the CREAT_UNLOGGED case, the whole
332: // container to be removed.
333:
334: Page page = open_conglom.getContainer().addPage();
335:
336: boolean callbackWithRowLocation = rowSource.needsRowLocation();
337: RecordHandle rh;
338: HeapRowLocation rowlocation;
339:
340: if (callbackWithRowLocation)
341: rowlocation = new HeapRowLocation();
342: else
343: rowlocation = null;
344:
345: FormatableBitSet validColumns = rowSource.getValidColumns();
346:
347: try {
348: // get the next row and its valid columns from the rowSource
349: DataValueDescriptor[] row;
350: while ((row = rowSource.getNextRowFromRowSource()) != null) {
351: num_rows_loaded++;
352:
353: if (SanityManager.DEBUG) {
354: // Make sure valid columns are in the list. The RowUtil
355: // call is too expensive to make in a released system for
356: // every insert.
357: int invalidColumn = RowUtil.columnOutOfRange(row,
358: validColumns, heap.format_ids.length);
359:
360: if (invalidColumn >= 0) {
361: throw (StandardException.newException(
362: SQLState.HEAP_TEMPLATE_MISMATCH,
363: new Long(invalidColumn), new Long(
364: heap.format_ids.length)));
365: }
366: }
367:
368: // Insert it onto this page as long as it can fit more rows.
369: if ((rh = page.insert(row, validColumns,
370: Page.INSERT_DEFAULT,
371: AccessFactoryGlobals.HEAP_OVERFLOW_THRESHOLD)) == null) {
372: // Insert faied, row did not fit. Get a new page.
373:
374: page.unlatch();
375: page = null;
376:
377: page = open_conglom.getContainer().addPage();
378:
379: // RESOLVE (mikem) - no long rows yet so the following code
380: // will get an exception from the raw store for a row that
381: // does not fit on a page.
382: //
383: // Multi-thread considerations aside, the raw store will
384: // guarantee that any size row will fit on an empty page.
385: rh = page
386: .insert(
387: row,
388: validColumns,
389: Page.INSERT_OVERFLOW,
390: AccessFactoryGlobals.HEAP_OVERFLOW_THRESHOLD);
391:
392: }
393:
394: // Else, the row fit. If we are expected to call back with the
395: // row location, do so. All the while keep the page latched
396: // and go for the next row.
397: if (callbackWithRowLocation) {
398: rowlocation.setFrom(rh);
399: rowSource.rowLocation(rowlocation);
400: }
401: }
402: page.unlatch();
403: page = null;
404:
405: // Done with the container, now we need to flush it to disk since
406: // it is unlogged.
407: if (!heap.isTemporary())
408: open_conglom.getContainer().flushContainer();
409: } finally {
410: // If an error happened here, don't bother flushing the
411: // container since the changes should be rolled back anyhow.
412: close();
413: }
414: return (num_rows_loaded);
415: }
416:
417: protected boolean lockRow(RecordHandle rh, int lock_oper,
418: boolean wait, int lock_duration) throws StandardException {
419: boolean ret_val;
420: boolean forUpdate = ((ConglomerateController.LOCK_UPD & lock_oper) != 0);
421: boolean forUpdateLock = ((ConglomerateController.LOCK_UPDATE_LOCKS & lock_oper) != 0);
422:
423: if (forUpdate && !forUpdateLock) {
424: boolean forInsert = ((ConglomerateController.LOCK_INS & lock_oper) != 0);
425: boolean forInsertPrevKey = ((ConglomerateController.LOCK_INS_PREVKEY & lock_oper) != 0);
426:
427: if (SanityManager.DEBUG) {
428: SanityManager.ASSERT(!(forInsertPrevKey && forInsert));
429: }
430:
431: if (lock_duration == TransactionManager.LOCK_INSTANT_DURATION) {
432: ret_val = open_conglom.getContainer()
433: .getLockingPolicy()
434: .zeroDurationLockRecordForWrite(
435: open_conglom.getRawTran(), rh,
436: forInsertPrevKey, wait);
437: } else {
438: ret_val = open_conglom.getContainer()
439: .getLockingPolicy().lockRecordForWrite(
440: open_conglom.getRawTran(), rh,
441: forInsert, wait);
442: }
443: } else {
444: if (SanityManager.DEBUG) {
445: SanityManager
446: .ASSERT((ConglomerateController.LOCK_INS & lock_oper) == 0);
447: SanityManager
448: .ASSERT((ConglomerateController.LOCK_INS_PREVKEY & lock_oper) == 0);
449: }
450:
451: ret_val = open_conglom.getContainer().getLockingPolicy()
452: .lockRecordForRead(open_conglom.getRawTran(),
453: open_conglom.getContainer(), rh, wait,
454: forUpdate);
455: }
456:
457: return (ret_val);
458: }
459:
460: protected Page getUserPageNoWait(long pageno)
461: throws StandardException {
462: return (open_conglom.getContainer().getUserPageNoWait(pageno));
463: }
464:
465: protected Page getUserPageWait(long pageno)
466: throws StandardException {
467: return (open_conglom.getContainer().getUserPageWait(pageno));
468: }
469:
470: protected boolean lockRowAtSlotNoWaitExclusive(RecordHandle rh)
471: throws StandardException {
472: return (open_conglom.getContainer().getLockingPolicy()
473: .lockRecordForWrite(open_conglom.getRawTran(), rh,
474: false, false));
475: }
476:
477: protected void removePage(Page page) throws StandardException {
478: open_conglom.getContainer().removePage(page);
479: }
480:
481: /**************************************************************************
482: * Public Methods of This class:
483: **************************************************************************
484: */
485:
486: public int insert(DataValueDescriptor[] row)
487: throws StandardException {
488: if (open_conglom.isClosed()) {
489: if (open_conglom.getHold()) {
490: open_conglom.reopen();
491: } else {
492: throw (StandardException.newException(
493: SQLState.HEAP_IS_CLOSED, open_conglom
494: .getConglomerate().getId()));
495: }
496: }
497:
498: doInsert(row);
499:
500: return (0);
501: }
502:
503: public void insertAndFetchLocation(DataValueDescriptor[] row,
504: RowLocation templateRowLocation) throws StandardException {
505: if (open_conglom.isClosed()) {
506: if (open_conglom.getHold()) {
507: open_conglom.reopen();
508: } else {
509: throw (StandardException.newException(
510: SQLState.HEAP_IS_CLOSED, open_conglom
511: .getConglomerate().getId()));
512: }
513: }
514:
515: RecordHandle rh = doInsert(row);
516: if (SanityManager.DEBUG) {
517: SanityManager
518: .ASSERT(templateRowLocation instanceof HeapRowLocation);
519: }
520: HeapRowLocation hrl = (HeapRowLocation) templateRowLocation;
521: hrl.setFrom(rh);
522: }
523:
524: /**
525: * Lock the given row location.
526: * <p>
527: * Should only be called by access.
528: * <p>
529: * This call can be made on a ConglomerateController that was opened
530: * for locking only.
531: * <p>
532: * RESOLVE (mikem) - move this call to ConglomerateManager so it is
533: * obvious that non-access clients should not call this.
534: *
535: * @return true if lock was granted, only can be false if wait was false.
536: *
537: * @param loc The "RowLocation" which describes the exact row to lock.
538: * @param wait Should the lock call wait to be granted?
539: *
540: * @exception StandardException Standard exception policy.
541: **/
542: public boolean lockRow(RowLocation loc, int lock_operation,
543: boolean wait, int lock_duration) throws StandardException {
544: RecordHandle rh = ((HeapRowLocation) loc)
545: .getRecordHandle(open_conglom.getContainer());
546:
547: return (lockRow(rh, lock_operation, wait, lock_duration));
548: }
549:
550: /**
551: * UnLock the given row location.
552: * <p>
553: * Should only be called by access.
554: * <p>
555: * This call can be made on a ConglomerateController that was opened
556: * for locking only.
557: * <p>
558: * RESOLVE (mikem) - move this call to ConglomerateManager so it is
559: * obvious that non-access clients should not call this.
560: *
561: * @param loc The "RowLocation" which describes the row to unlock.
562: * @param forUpdate Row was previously Locked the record for read or update.
563: *
564: * @exception StandardException Standard exception policy.
565: **/
566: public void unlockRowAfterRead(RowLocation loc, boolean forUpdate,
567: boolean row_qualified) throws StandardException {
568:
569: RecordHandle rh = ((HeapRowLocation) loc)
570: .getRecordHandle(open_conglom.getContainer());
571:
572: open_conglom.getContainer().getLockingPolicy()
573: .unlockRecordAfterRead(open_conglom.getRawTran(),
574: open_conglom.getContainer(), rh,
575: open_conglom.isForUpdate(), row_qualified);
576: }
577:
578: /**
579: * Lock the given record id/page num pair.
580: * <p>
581: * Should only be called by access, to lock "special" locks formed from
582: * the Recordhandle.* reserved constants for page specific locks.
583: * <p>
584: * This call can be made on a ConglomerateController that was opened
585: * for locking only.
586: * <p>
587: * RESOLVE (mikem) - move this call to ConglomerateManager so it is
588: * obvious that non-access clients should not call this.
589: *
590: * @return true if lock was granted, only can be false if wait was false.
591: *
592: * @param page_num Page number of row to lock.
593: * @param record_id Record id of row on page_num to lock.
594: * @param lock_operation Desc of what to lock for, ie. update, insert ...
595: * @param wait Should the lock call wait to be granted?
596: *
597: * @exception StandardException Standard exception policy.
598: **/
599: public boolean lockRow(long page_num, int record_id,
600: int lock_operation, boolean wait, int lock_duration)
601: throws StandardException {
602: boolean ret_val;
603:
604: RecordHandle rh = open_conglom.getContainer().makeRecordHandle(
605: page_num, record_id);
606:
607: return (lockRow(rh, lock_operation, wait, lock_duration));
608: }
609:
610: public RowLocation newRowLocationTemplate()
611: throws StandardException {
612: if (open_conglom.isClosed()) {
613: if (open_conglom.getHold()) {
614: open_conglom.reopen();
615: } else {
616: throw (StandardException.newException(
617: SQLState.HEAP_IS_CLOSED, open_conglom
618: .getConglomerate().getId()));
619: }
620: }
621:
622: return new HeapRowLocation();
623: }
624:
625: /**************************************************************************
626: * Public Methods of XXXX class:
627: **************************************************************************
628: */
629: }
|