blob: 42823a35c18bbdce77fb7698d39d2724bc3e3ac7 [file] [log] [blame]
/*
* 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)
{
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()) )
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()) ;
}
}