/*
* Copyright 2015 Edward Capriolo
*
* Licensed 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 io.teknek.nibiru.engine;
import io.teknek.nibiru.Token;
import io.teknek.nibiru.engine.atom.AtomKey;
import io.teknek.nibiru.engine.atom.AtomValue;
import io.teknek.nibiru.engine.atom.ColumnKey;
import io.teknek.nibiru.engine.atom.ColumnValue;
import io.teknek.nibiru.engine.atom.RowTombstoneKey;
import io.teknek.nibiru.engine.atom.TombstoneValue;
import io.teknek.nibiru.io.BufferGroup;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.SortedMap;
import java.util.TreeMap;
public class SsTableReader {
public static final char START_RECORD = '\0';
public static final char END_ROWKEY = '\2';
public static final char END_COLUMN_PART = '\3';
public static final char END_COLUMN = '\4';
public static final char END_ROW = '\n';
private RandomAccessFile ssRaf;
private FileChannel ssChannel;
private MappedByteBuffer ssBuffer;
private RandomAccessFile indexRaf;
private FileChannel indexChannel;
private MappedByteBuffer indexBuffer;
private SsTable ssTable;
private KeyCache keyCache;
private BloomFilter bloomFilter;
public SsTableReader(SsTable ssTable, KeyCache keyCache, BloomFilter bloomFilter){
this.ssTable = ssTable;
this.keyCache = keyCache;
this.bloomFilter = bloomFilter;
}
public void open(String id) throws IOException{
File pathToDataDirectory = SsTableStreamWriter.pathToSsTableDataDirectory(
ssTable.getColumnFamily().getKeyspace().getConfiguration(),
ssTable.getColumnFamily().getStoreMetadata());
File sstable = new File(pathToDataDirectory, id + ".ss" );
ssRaf = new RandomAccessFile(sstable, "r");
ssChannel = ssRaf.getChannel();
ssBuffer = ssChannel.map(FileChannel.MapMode.READ_ONLY, 0, ssChannel.size());
File index = new File(ssTable.getColumnFamily().getKeyspace().getConfiguration().getDataDirectory(), id + ".index");
indexRaf = new RandomAccessFile(index, "r");
indexChannel = indexRaf.getChannel();
indexBuffer = indexChannel.map(FileChannel.MapMode.READ_ONLY, 0, indexChannel.size());
}
public void close() throws IOException {
ssChannel.close();
indexChannel.close();
ssRaf.close();
indexRaf.close();
}
public SsTableStreamReader getStreamReader() throws IOException {
BufferGroup bg = new BufferGroup();
bg.channel = ssChannel;
bg.mbb = (MappedByteBuffer) ssBuffer.duplicate();
bg.setStartOffset((int) 0);
return new SsTableStreamReader(bg);
}
static void readHeader(BufferGroup bg) throws IOException {
if (bg.dst[bg.currentIndex] != '\0'){
throw new RuntimeException("corrupt expected \\0 got " + (char) bg.dst[bg.currentIndex] );
}
bg.advanceIndex();
}
static StringBuilder readToken(BufferGroup bg) throws IOException {
StringBuilder token = new StringBuilder();
int length = readTwoByteSize(bg);
for (int i=0;i< length;i++){
token.append((char) bg.dst[bg.currentIndex]);
bg.advanceIndex();
}
return token;
}
static StringBuilder readRowkey(BufferGroup bg) throws IOException {
StringBuilder token = new StringBuilder();
int length = readTwoByteSize(bg);
for (int i=0;i< length;i++){
token.append((char) bg.dst[bg.currentIndex]);
bg.advanceIndex();
}
return token;
}
private void skipRowkey(BufferGroup bg) throws IOException{
int length = readTwoByteSize(bg);
for (int i=0;i< length;i++){
bg.advanceIndex();
}
}
private static StringBuilder readColumn(BufferGroup bg) throws IOException{
StringBuilder token = new StringBuilder();
int length = readTwoByteSize(bg);
bg.advanceIndex();
for (int i=0;i< length;i++){
token.append((char) bg.dst[bg.currentIndex]);
bg.advanceIndex();
}
return token;
}
private static StringBuilder endColumn(BufferGroup bg) throws IOException{
StringBuilder create = new StringBuilder();
while (!(bg.dst[bg.currentIndex] == END_COLUMN ||bg.dst[bg.currentIndex] == END_ROW) ){
create.append((char) bg.dst[bg.currentIndex]);
bg.advanceIndex();
}
return create;
}
private static StringBuilder endColumnPart(BufferGroup bg) throws IOException{
StringBuilder create = new StringBuilder();
while (!(bg.dst[bg.currentIndex] == END_COLUMN_PART) ){
create.append((char) bg.dst[bg.currentIndex]);
bg.advanceIndex();
}
return create;
}
public static int readTwoByteSize(BufferGroup bg) throws IOException{
int colSize = (bg.dst[bg.currentIndex] & 0xFF) << 8;
bg.advanceIndex();
colSize = colSize + (bg.dst[bg.currentIndex] & 0xFF);
bg.advanceIndex();
return colSize;
}
static StringBuilder readNextNIntoBuilder(BufferGroup bg, int size) throws IOException{
StringBuilder sb = new StringBuilder();
for (int i =0;i< size ; i++){
sb.append((char) bg.dst[bg.currentIndex]);
bg.advanceIndex();
}
return sb;
}
static SortedMap<AtomKey,AtomValue> readColumns(BufferGroup bg) throws IOException {
SortedMap<AtomKey,AtomValue> result = new TreeMap<>();
int numberOfColumns = readTwoByteSize(bg);
for (int i =0;i< numberOfColumns ; i++){
int next = readTwoByteSize(bg);
char typeOfColumn = (char) bg.dst[bg.currentIndex];
bg.advanceIndex();
StringBuilder name = readNextNIntoBuilder(bg, next-1);
AtomKey columnType = null;
if (typeOfColumn == ColumnKey.SERIALIZE_CHAR){
columnType = new ColumnKey(name.toString());
} else if (typeOfColumn == RowTombstoneKey.SERIALIZE_CHAR){
columnType = new RowTombstoneKey();
} else {
throw new RuntimeException("can not handle "+typeOfColumn);
}
int size = readTwoByteSize(bg);
char typeOfValue = (char) bg.dst[bg.currentIndex];
bg.advanceIndex();
AtomValue atomValue = null;
if (typeOfValue == 'C'){
int soFar = 0;
StringBuilder create = endColumnPart(bg);
soFar += create.length();
bg.advanceIndex();
StringBuilder time = endColumnPart(bg);
soFar += time.length();
bg.advanceIndex();
StringBuilder ttl = endColumnPart(bg);
soFar += ttl.length();
bg.advanceIndex();
soFar += 5;
int remaining = size - soFar;
bg.advanceIndex();
StringBuilder value = readNextNIntoBuilder(bg, remaining);
ColumnValue v = new ColumnValue();
v.setValue(value.toString());
v.setTime(Long.parseLong(time.toString()));
v.setTtl(Long.parseLong(ttl.toString()));
v.setCreateTime(Long.parseLong(create.toString()));
atomValue = v;
} else if (typeOfValue == 'T'){
StringBuilder value = readNextNIntoBuilder(bg, size-1);
TombstoneValue tv = new TombstoneValue(Long.parseLong(value.toString()));
atomValue = tv;
} else {
throw new RuntimeException("can not handle "+typeOfValue);
}
result.put(columnType, atomValue);
}
return result;
}
private void ignoreColumns(BufferGroup bg) throws IOException {
do {
bg.advanceIndex();
} while (bg.dst[bg.currentIndex] != END_ROW);
}
public AtomValue get(Token searchToken, String column) throws IOException {
boolean mightContain = bloomFilter.mightContain(searchToken);
if (!mightContain) {
return null;
}
BufferGroup bgIndex = new BufferGroup();
bgIndex.channel = indexChannel;
bgIndex.mbb = (MappedByteBuffer) indexBuffer.duplicate();
IndexReader index = new IndexReader(bgIndex);
BufferGroup bg = new BufferGroup();
bg.channel = ssChannel;
bg.mbb = (MappedByteBuffer) ssBuffer.duplicate();
long startOffset = keyCache.get(searchToken.getRowkey());
if (startOffset == -1){
startOffset = index.findStartOffset(searchToken.getToken());
}
bg.setStartOffset((int)startOffset);
do {
if (bg.dst[bg.currentIndex] == END_ROW){
bg.advanceIndex();
}
long startOfRow = bg.mbb.position() - bg.blockSize + bg.currentIndex;
readHeader(bg);
StringBuilder token = readToken(bg);
if (token.toString().equals(searchToken.getToken())){
StringBuilder rowkey = readRowkey(bg);
if (rowkey.toString().equals(searchToken.getRowkey())){
keyCache.put(searchToken.getRowkey(), startOfRow);
SortedMap<AtomKey,AtomValue> columns = readColumns(bg);
TombstoneValue v = (TombstoneValue) columns.get(new RowTombstoneKey());
if (v == null){
return columns.get(new ColumnKey(column));
} else {
AtomValue atomValue = columns.get(new ColumnKey(column));
if (atomValue.getTime() > v.getTime()){
return atomValue;
} else {
return v;
}
}
} else {
ignoreColumns(bg);
}
} else {
skipRowkey(bg);
ignoreColumns(bg);
}
} while (bg.currentIndex < bg.dst.length - 1 || bg.mbb.position() < ssChannel.size());
return null;
}
//TODO this can be more efficient
public SortedMap<AtomKey, AtomValue> slice(Token searchToken, String start, String end) throws IOException {
boolean mightContain = bloomFilter.mightContain(searchToken);
if (!mightContain) {
return null;
}
BufferGroup bgIndex = new BufferGroup();
bgIndex.channel = indexChannel;
bgIndex.mbb = (MappedByteBuffer) indexBuffer.duplicate();
IndexReader index = new IndexReader(bgIndex);
BufferGroup bg = new BufferGroup();
bg.channel = ssChannel;
bg.mbb = (MappedByteBuffer) ssBuffer.duplicate();
long startOffset = keyCache.get(searchToken.getRowkey());
if (startOffset == -1){
startOffset = index.findStartOffset(searchToken.getToken());
}
bg.setStartOffset((int)startOffset);
do {
if (bg.dst[bg.currentIndex] == END_ROW){
bg.advanceIndex();
}
long startOfRow = bg.mbb.position() - bg.blockSize + bg.currentIndex;
readHeader(bg);
StringBuilder token = readToken(bg);
if (token.toString().equals(searchToken.getToken())){
StringBuilder rowkey = readRowkey(bg);
if (rowkey.toString().equals(searchToken.getRowkey())){
keyCache.put(searchToken.getRowkey(), startOfRow);
SortedMap<AtomKey,AtomValue> columns = readColumns(bg);
return columns.subMap(new ColumnKey(start), new ColumnKey(end));
} else {
ignoreColumns(bg);
}
} else {
skipRowkey(bg);
ignoreColumns(bg);
}
} while (bg.currentIndex < bg.dst.length - 1 || bg.mbb.position() < ssChannel.size());
return null;
}
}