/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jena.tdb.index.bplustree; import static java.lang.String.format ; import static org.apache.jena.atlas.lib.Alg.decodeIndex ; import static org.apache.jena.tdb.index.bplustree.BPlusTreeParams.CheckingNode ; import org.apache.jena.atlas.io.IndentedWriter ; import org.apache.jena.tdb.base.StorageException ; import org.apache.jena.tdb.base.block.Block ; import org.apache.jena.tdb.base.buffer.RecordBuffer ; import org.apache.jena.tdb.base.record.Record ; import org.apache.jena.tdb.base.recordbuffer.RecordBufferPage ; import org.slf4j.Logger ; import org.slf4j.LoggerFactory ; /** B+Tree wrapper over a block of records in a RecordBufferPage. * This class adds no persistent state to a RecordBufferPage */ public final class BPTreeRecords extends BPTreePage { private static Logger log = LoggerFactory.getLogger(BPTreeRecords.class) ; private final RecordBufferPage rBuffPage ; private RecordBuffer rBuff ; // Used heavily. BPTreeRecords(BPlusTree bpTree, RecordBufferPage rbp) { super(bpTree) ; rBuffPage = rbp ; rBuff = rBuffPage.getRecordBuffer() ; } RecordBufferPage getRecordBufferPage() { return rBuffPage ; } /*TEMP*/ public RecordBuffer getRecordBuffer() { return rBuff ; } @Override public final Block getBackingBlock() { return rBuffPage.getBackingBlock() ; } @Override public void reset(Block block) { rBuffPage.reset(block) ; rBuff = rBuffPage.getRecordBuffer() ; } int getLink() { return rBuffPage.getLink() ; } @Override public boolean isFull() { return ( rBuff.size() >= rBuff.maxSize() ) ; } @Override public boolean hasAnyKeys() { return rBuff.size() > 0 ; } @Override public boolean isMinSize() { // 50% packing minimum. // If of max length 5 (i.e. odd), min size is 2. Integer division works. return ( rBuff.size() <= rBuff.maxSize()/2 ) ; } @Override public Record internalSearch(Record rec) { int i = rBuff.find(rec) ; if ( i < 0 ) return null ; return rBuff.get(i) ; } // @Override // public BPTreeRecords findPage(Record record) // { // if ( rBuff.size() == 0 ) // return this ; // // // Not true if above the last record. // if ( this.getLink() != RecordBufferPage.NO_ID && Record.keyGT(record, maxRecord()) ) // error("Record [%s] not in this page: %s", record , this) ; // return this ; // } // // @Override // public BPTreeRecords findFirstPage() { return this ; } @Override final public void write() { bpTree.getRecordsMgr().write(this) ; } @Override final public void promote() { bpTree.getRecordsMgr().promote(this) ; } @Override final public void release() { bpTree.getRecordsMgr().release(this) ; } @Override final public void free() { bpTree.getRecordsMgr().free(this) ; } @Override public Record internalInsert(Record record) { // [TxTDB:PATCH-UP] promote() ; int i = rBuff.find(record) ; Record r2 = null ; if ( i < 0 ) { i = decodeIndex(i) ; if ( rBuff.size() >= rBuff.maxSize()) throw new StorageException("RecordBlock.put overflow") ; rBuff.add(i, record) ; } else { r2 = rBuff.get(i) ; if ( Record.compareByKeyValue(record, r2) != 0 ) // Replace : return old rBuff.set(i, record) ; } write() ; return r2 ; } @Override public Record internalDelete(Record record) { promote() ; int i = rBuff.find(record) ; if ( i < 0 ) return null ; Record r2 = rBuff.get(i) ; rBuff.remove(i) ; write() ; return r2 ; } @Override final public Record getSplitKey() { int splitIdx = rBuff.size()/2-1 ; Record r = rBuff.get(splitIdx) ; return r ; } /** Split: place old high half in 'other'. Return the new (upper) BPTreeRecords(BPTreePage). * Split is the high end of the low page. */ @Override final public BPTreePage split() { // LinkIn // Create a new BPTreeRecords. BPTreeRecords other = create(rBuffPage.getLink()) ; rBuffPage.setLink(other.getId()) ; int splitIdx = rBuff.size()/2-1 ; Record r = rBuff.get(splitIdx) ; // Only need key for checking later. int moveLen = rBuff.size()-(splitIdx+1) ; // Number to move. // Copy high end to new. rBuff.copy(splitIdx+1, other.getRecordBufferPage().getRecordBuffer(), 0, moveLen) ; rBuff.clear(splitIdx+1, moveLen) ; rBuff.setSize(splitIdx+1) ; if ( CheckingNode ) { if ( ! Record.keyEQ(r, maxRecord()) ) { System.err.println(rBuff) ; System.err.println(other.rBuff) ; error("BPTreeRecords.split: Not returning expected record") ; } } return other ; } private BPTreeRecords create(int linkId) { BPTreeRecords newPage = bpTree.getRecordsMgr().create() ; newPage.getRecordBufferPage().setLink(linkId) ; return newPage ; } @Override public Record shiftRight(BPTreePage other, Record splitKey) { // Error checking by RecordBuffer BPTreeRecords page = cast(other) ; rBuff.shiftRight(page.rBuff) ; if ( rBuff.size() == 0 ) return null ; return rBuff.getHigh() ; } @Override public Record shiftLeft(BPTreePage other, Record splitKey) { // Error checking by RecordBuffer BPTreeRecords page = cast(other) ; rBuff.shiftLeft(page.rBuff) ; if ( rBuff.size() == 0 ) return null ; return rBuff.getHigh() ; } @Override public BPTreePage merge(BPTreePage right, Record splitKey) { // Split key ignored - it's for the B+Tree case of pushing down a key // Records blocks have all the key/values in them anyway. return merge(this, cast(right)) ; } private static BPTreeRecords merge(BPTreeRecords left, BPTreeRecords right) { // Copy right to top of left. // The other way round needs a shift as well. right.rBuff.copyToTop(left.rBuff) ; // Same as: right.rBuff.copy(0, left.rBuff, left.rBuff.size(), right.rBuff.size()) ; right.rBuff.clear() ; //The right page is released by the caller. left is still in use. // So the test code can poke around in the right block after merge. //left.bpTree.getRecordsMgr().release(left.getId()) ; // Fix up the link chain. left.rBuffPage.setLink(right.rBuffPage.getLink()) ; return left ; } private static BPTreeRecords cast(BPTreePage page) { try { return (BPTreeRecords)page ; } catch (ClassCastException ex) { error("Wrong type: "+page) ; return null ; } } @Override final public Record minRecord() { return getLowRecord() ; } @Override final public Record maxRecord() { return getHighRecord() ; } private static void error(String msg, Object... args) { msg = format(msg, args) ; System.out.println(msg) ; System.out.flush(); throw new BPTreeException(msg) ; } @Override public final Record getLowRecord() { if ( rBuff.size() == 0 ) return null ; return rBuff.getLow() ; } @Override public final Record getHighRecord() { if ( rBuff.size() == 0 ) return null ; return rBuff.getHigh() ; } @Override public final int getMaxSize() { return rBuff.maxSize() ; } @Override public final int getCount() { return rBuff.size() ; } @Override public final void setCount(int count) { rBuff.setSize(count) ; } @Override public String toString() { return String.format("BPTreeRecords[id=%d, link=%d]: %s", getId(), getLink(), rBuff.toString()); } @Override public final void checkNode() { if ( ! CheckingNode ) return ; if ( rBuff.size() < 0 || rBuff.size() > rBuff.maxSize() ) error("Misized: %s", this) ; for ( int i = 1 ; i < getCount() ; i++ ) { Record r1 = rBuff.get(i-1) ; Record r2 = rBuff.get(i) ; if ( Record.keyGT(r1, r2) ) error("Not sorted: %s", this) ; } } @Override public final void checkNodeDeep() { checkNode() ; } // @Override // public ByteBuffer getBackingByteBuffer() { return rBuffPage.getBackingByteBuffer() ; } @Override public int getId() { return rBuffPage.getId() ; } // @Override // public void setId(int id) { rBuffPage.setId(id) ; } @Override public void output(IndentedWriter out) { out.print(toString()) ; } }