Skip to content

Commit

Permalink
FFDB-008: Implement RandomAccessLog.getSegment (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
godcrampy authored Jan 6, 2024
1 parent c7615c1 commit 478b435
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

import java.io.*;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sahilbondre.firefly.log;

import com.sahilbondre.firefly.filetable.FilePointer;
import com.sahilbondre.firefly.model.Segment;

import java.io.IOException;
import java.io.RandomAccessFile;
Expand Down Expand Up @@ -51,6 +52,46 @@ public byte[] read(long offset, long length) throws IOException, InvalidRangeExc
return buffer.array();
}

@Override
public Segment readSegment(long offset) throws IOException, InvalidRangeException {
long fileSize = fileChannel.size();

if (offset < 0 || offset >= fileSize) {
throw new InvalidRangeException("Invalid offset");
}

// Read Key Size
byte[] keySizeBytes = new byte[Segment.KEY_SIZE_LENGTH];
fileChannel.read(ByteBuffer.wrap(keySizeBytes), offset + Segment.KEY_SIZE_LENGTH);

// Read Value Size
byte[] valueSizeBytes = new byte[Segment.VALUE_SIZE_LENGTH];
fileChannel.read(ByteBuffer.wrap(valueSizeBytes),
offset + Segment.KEY_SIZE_LENGTH + Segment.VALUE_SIZE_LENGTH);

// Total Size
int totalSize = Segment.CRC_LENGTH + Segment.KEY_SIZE_LENGTH +
Segment.VALUE_SIZE_LENGTH + byteArrayToInt(keySizeBytes) + byteArrayToInt(valueSizeBytes);

// Read entire segment
byte[] segmentBytes = new byte[totalSize];
fileChannel.read(ByteBuffer.wrap(segmentBytes), offset);


Segment segment = Segment.fromByteArray(segmentBytes);

// Validate CRC
if (!segment.isSegmentValid()) {
throw new InvalidRangeException("Segment is invalid");
}

return segment;
}

private int byteArrayToInt(byte[] bytes) {
return (bytes[0] << 8) | (bytes[1] & 0xFF);
}

public void close() throws IOException {
fileChannel.close();
randomAccessFile.close();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sahilbondre.firefly.log;

import com.sahilbondre.firefly.filetable.FilePointer;
import com.sahilbondre.firefly.model.Segment;

import java.io.IOException;

Expand All @@ -13,5 +14,7 @@ public interface RandomAccessLog {

byte[] read(long offset, long length) throws IOException, InvalidRangeException;

Segment readSegment(long offset) throws IOException, InvalidRangeException;

void close() throws IOException;
}
6 changes: 3 additions & 3 deletions src/main/java/com/sahilbondre/firefly/model/Segment.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

public class Segment {

private static final int CRC_LENGTH = 2;
private static final int KEY_SIZE_LENGTH = 2;
private static final int VALUE_SIZE_LENGTH = 4;
public static final int CRC_LENGTH = 2;
public static final int KEY_SIZE_LENGTH = 2;
public static final int VALUE_SIZE_LENGTH = 4;
/**
* Class representing a segment of the log file.
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.sahilbondre.firefly.log;

import com.sahilbondre.firefly.filetable.FilePointer;
import com.sahilbondre.firefly.model.Segment;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -121,4 +123,65 @@ void givenLog_whenClose_thenFileIsNotAccessible() throws IOException {
assertTrue(Files.exists(TEST_FILE_PATH));
assertThrows(IOException.class, () -> randomAccessLog.append("NewContent".getBytes()));
}

@Test
void givenLogWithContent_whenReadSegment_thenReturnsCorrectSegment() throws IOException, InvalidRangeException {
// Given
// A log with existing content
Segment firstSegment = Segment.fromKeyValuePair("Hello".getBytes(), "World".getBytes());
Segment secondSegment = Segment.fromKeyValuePair("Foo".getBytes(), "Bar".getBytes());
FilePointer firstFilePointer = randomAccessLog.append(firstSegment.getBytes());
FilePointer secondFilePointer = randomAccessLog.append(secondSegment.getBytes());

// When
Segment firstReadSegment = randomAccessLog.readSegment(firstFilePointer.getOffset());
Segment secondReadSegment = randomAccessLog.readSegment(secondFilePointer.getOffset());

// Then
assertArrayEquals(firstSegment.getBytes(), firstReadSegment.getBytes());
assertArrayEquals(secondSegment.getBytes(), secondReadSegment.getBytes());
assertEquals("Hello", new String(firstReadSegment.getKey()));
assertEquals("World", new String(firstReadSegment.getValue()));
assertEquals("Foo", new String(secondReadSegment.getKey()));
assertEquals("Bar", new String(secondReadSegment.getValue()));
}

@Test
void givenLogWithContent_whenReadSegmentWithInvalidOffset_thenThrowsInvalidRangeException() throws IOException {
// Given
// A log with existing content
Segment firstSegment = Segment.fromKeyValuePair("Hello".getBytes(), "World".getBytes());
Segment secondSegment = Segment.fromKeyValuePair("Foo".getBytes(), "Bar".getBytes());
randomAccessLog.append(firstSegment.getBytes());
randomAccessLog.append(secondSegment.getBytes());

// When/Then
assertThrows(InvalidRangeException.class, () -> randomAccessLog.readSegment(-1));
assertThrows(InvalidRangeException.class, () -> randomAccessLog.readSegment(100));
}

@Test
void givenEmptyLog_whenReadSegment_thenThrowsInvalidRangeException() {
// Given
// An empty log

// When/Then
assertThrows(InvalidRangeException.class, () -> randomAccessLog.readSegment(0));
}

@Test
void givenLogWithContent_whenAppend_thenReturnsCorrectFilePointer() throws IOException {
// Given
// A log with existing content

// When
FilePointer fp1 = randomAccessLog.append("Hello".getBytes());
FilePointer fp2 = randomAccessLog.append("World".getBytes());

// Then
assertEquals(TEST_FILE_NAME, fp1.getFileName());
assertEquals(0, fp1.getOffset());
assertEquals(TEST_FILE_NAME, fp2.getFileName());
assertEquals(5, fp2.getOffset());
}
}

0 comments on commit 478b435

Please sign in to comment.