diff --git a/src/main/java/database/Application.java b/src/main/java/database/Application.java index 7dce43a..40a152f 100644 --- a/src/main/java/database/Application.java +++ b/src/main/java/database/Application.java @@ -1,5 +1,7 @@ package database; +import database.engine.DatabaseServer; + public class Application { public static void main(String[] args) { diff --git a/src/main/java/database/ClientHandler.java b/src/main/java/database/engine/ClientHandler.java similarity index 97% rename from src/main/java/database/ClientHandler.java rename to src/main/java/database/engine/ClientHandler.java index 48abec7..ef98d7f 100644 --- a/src/main/java/database/ClientHandler.java +++ b/src/main/java/database/engine/ClientHandler.java @@ -1,4 +1,4 @@ -package database; +package database.engine; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -7,6 +7,7 @@ import java.net.Socket; public class ClientHandler implements Runnable { + private final Socket clientSocket; public ClientHandler(Socket clientSocket) { diff --git a/src/main/java/database/DatabaseServer.java b/src/main/java/database/engine/DatabaseServer.java similarity index 93% rename from src/main/java/database/DatabaseServer.java rename to src/main/java/database/engine/DatabaseServer.java index 7ef6247..423b11e 100644 --- a/src/main/java/database/DatabaseServer.java +++ b/src/main/java/database/engine/DatabaseServer.java @@ -1,11 +1,12 @@ -package database; +package database.engine; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class DatabaseServer { - private int port; + + private final int port; private boolean isRunning; public DatabaseServer(int port) { diff --git a/src/main/java/database/engine/Handler.java b/src/main/java/database/engine/Handler.java new file mode 100644 index 0000000..f4f56d3 --- /dev/null +++ b/src/main/java/database/engine/Handler.java @@ -0,0 +1,15 @@ +package database.engine; + +import java.util.List; + +public interface Handler { + + void insert(String tableName, Record record); + + List search(String tableName, Object key); + + void update(String tableName, Object key, byte[] newRecord); + + void delete(String tableName, Object key); +} + diff --git a/src/main/java/database/engine/Record.java b/src/main/java/database/engine/Record.java new file mode 100644 index 0000000..da52176 --- /dev/null +++ b/src/main/java/database/engine/Record.java @@ -0,0 +1,23 @@ +package database.engine; + +import java.util.List; + +public class Record { + + private final List values; + + public Record(List values) { + this.values = values; + } + + public List getValues() { + return values; + } + + @Override + public String toString() { + return "Record{" + + "values=" + values + + '}'; + } +} diff --git a/src/main/java/database/page/Page.java b/src/main/java/database/page/Page.java deleted file mode 100644 index b421f9e..0000000 --- a/src/main/java/database/page/Page.java +++ /dev/null @@ -1,19 +0,0 @@ -package database.page; - -public class Page { - private final long pageNum; - private final byte[] data; - - public Page(long pageNum, byte[] data) { - this.pageNum = pageNum; - this.data = data; - } - - public long getPageNum() { - return pageNum; - } - - public byte[] getData() { - return data; - } -} diff --git a/src/main/java/database/storageEngine/StorageEngineHandler.java b/src/main/java/database/storageEngine/StorageEngineHandler.java new file mode 100644 index 0000000..7436175 --- /dev/null +++ b/src/main/java/database/storageEngine/StorageEngineHandler.java @@ -0,0 +1,65 @@ +package database.storageEngine; + +import database.engine.Handler; +import database.engine.Record; +import database.storageEngine.bufferpool.BufferPool; +import database.storageEngine.bufferpool.PageReplacementStrategy; +import database.storageEngine.bufferpool.TablePageKey; +import database.storageEngine.bufferpool.pageReplacementStrategies.LRUStrategy; +import database.storageEngine.page.Page; +import database.storageEngine.page.StorageRecord; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class StorageEngineHandler implements Handler { + + private static final int BUFFER_SIZE = 40; + + private final BufferPool bufferPool; + + public StorageEngineHandler() { + PageReplacementStrategy lruStrategy = new LRUStrategy<>(BUFFER_SIZE); + this.bufferPool = new BufferPool(BUFFER_SIZE, lruStrategy); + } + + @Override + public void insert(String tableName, Record record) { + StorageRecord storageRecord = new StorageRecord(record.getValues()); + Page page = bufferPool.findPageWithSpace(tableName, storageRecord); + page.addRecord(storageRecord); + } + + @Override + public List search(String tableName, Object key) { + List results = new ArrayList<>(); + + for (long pageNumber = 0; ; pageNumber++) { + TablePageKey tablePageKey = new TablePageKey(tableName, pageNumber); + Optional pageOpt = bufferPool.getPage(tablePageKey); + + if (pageOpt.isPresent()) { + Page page = pageOpt.get(); + List records = page.searchRecords(key).stream() + .map(storageRecord -> new Record(storageRecord.getValues())) + .toList(); + if (!records.isEmpty()) { + results.addAll(records); + } + } else { + break; + } + } + return results; + } + + @Override + public void update(String tableName, Object key, byte[] newRecord) { + // 디스크에서 레코드를 업데이트하는 로직 + } + + @Override + public void delete(String tableName, Object key) { + // 디스크에서 레코드를 삭제하는 로직 + } +} diff --git a/src/main/java/database/storageEngine/bufferpool/BufferPool.java b/src/main/java/database/storageEngine/bufferpool/BufferPool.java new file mode 100644 index 0000000..38e1f17 --- /dev/null +++ b/src/main/java/database/storageEngine/bufferpool/BufferPool.java @@ -0,0 +1,89 @@ +package database.storageEngine.bufferpool; + +import database.storageEngine.page.Page; +import database.storageEngine.page.FileManager; +import database.storageEngine.page.StorageRecord; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.IntStream; + +public class BufferPool { + + private final int capacity; + private final PageReplacementStrategy strategy; + private final Map pages; + private final FileManager fileManager; + + public BufferPool(int capacity, PageReplacementStrategy strategy) { + this.capacity = capacity; + this.strategy = strategy; + this.pages = new HashMap<>(); + this.fileManager = new FileManager(); + } + + public Optional getPage(TablePageKey key) { + if (pages.containsKey(key)) { + strategy.get(key); + return Optional.of(pages.get(key)); + } + + Optional optionalPage = fileManager.loadPage(key); + if (optionalPage.isPresent()) { + Page page = optionalPage.get(); + putPage(key, page); + return Optional.of(page); + } + + return Optional.empty(); + } + + public void putPage(TablePageKey key, Page page) { + if (!pages.containsKey(key)) { + if (pages.size() >= capacity) { + TablePageKey evictedKey = strategy.evict(); + if (evictedKey != null) { + flushPage(evictedKey); + pages.remove(evictedKey); + } + } + pages.put(key, page); + strategy.put(key); + } + } + + public void flushPage(TablePageKey key) { + if (pages.containsKey(key)) { + Page page = pages.get(key); + if (page.isDirty()) { + fileManager.savePage(key.tableName(), page); + page.clean(); + } + } + } + + public void flushAllPages() { + for (Map.Entry entry : pages.entrySet()) { + flushPage(entry.getKey()); + } + } + + public void removePage(TablePageKey key) { + if (pages.containsKey(key)) { + flushPage(key); + pages.remove(key); + strategy.evict(); + } + } + + public Page findPageWithSpace(String tableName, StorageRecord storageRecord) { + return IntStream.range(0, capacity) + .mapToObj(pageNumber -> { + TablePageKey key = new TablePageKey(tableName, pageNumber); + return pages.get(key); + }) + .filter(page -> page.getFreeSpace() >= storageRecord.getSize()) + .findFirst() + .orElseGet(fileManager::createNewDataPage); + } +} diff --git a/src/main/java/database/storageEngine/bufferpool/PageReplacementStrategy.java b/src/main/java/database/storageEngine/bufferpool/PageReplacementStrategy.java new file mode 100644 index 0000000..402a807 --- /dev/null +++ b/src/main/java/database/storageEngine/bufferpool/PageReplacementStrategy.java @@ -0,0 +1,14 @@ +package database.storageEngine.bufferpool; + +import java.util.Optional; + +public interface PageReplacementStrategy { + + Optional get(K key); + + void put(K key); + + K evict(); + + boolean contains(K key); +} diff --git a/src/main/java/database/storageEngine/bufferpool/TablePageKey.java b/src/main/java/database/storageEngine/bufferpool/TablePageKey.java new file mode 100644 index 0000000..de0a373 --- /dev/null +++ b/src/main/java/database/storageEngine/bufferpool/TablePageKey.java @@ -0,0 +1,5 @@ +package database.storageEngine.bufferpool; + +public record TablePageKey(String tableName, long pageNumber) { + +} diff --git a/src/main/java/database/storageEngine/bufferpool/pageReplacementStrategies/LFUStrategy.java b/src/main/java/database/storageEngine/bufferpool/pageReplacementStrategies/LFUStrategy.java new file mode 100644 index 0000000..75b445c --- /dev/null +++ b/src/main/java/database/storageEngine/bufferpool/pageReplacementStrategies/LFUStrategy.java @@ -0,0 +1,66 @@ +package database.storageEngine.bufferpool.pageReplacementStrategies; + +import database.storageEngine.bufferpool.PageReplacementStrategy; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; + +public class LFUStrategy implements PageReplacementStrategy { + + private final int capacity; + private final Map usageCount; + private final Map cache; + + public LFUStrategy(int capacity) { + this.capacity = capacity; + this.usageCount = new HashMap<>(); + this.cache = new HashMap<>(); + } + + @Override + public void put(K key) { + if (cache.size() >= capacity) { + evict(); + } + cache.put(key, System.nanoTime()); // 현재 시간으로 타임스탬프 저장 + usageCount.put(key, usageCount.getOrDefault(key, 0) + 1); + } + + @Override + public K evict() { + K leastUsedKey = findLeastUsedKey(); + if (leastUsedKey != null) { + cache.remove(leastUsedKey); + usageCount.remove(leastUsedKey); + } + return leastUsedKey; + } + + @Override + public boolean contains(K key) { + return cache.containsKey(key); + } + + private K findLeastUsedKey() { + Optional> minEntry = usageCount.entrySet().stream() + .min((entry1, entry2) -> { + int freqCompare = entry1.getValue().compareTo(entry2.getValue()); + if (freqCompare == 0) { + // 동일한 사용 빈도일 경우, 먼저 들어온 키를 우선 제거 + return cache.get(entry1.getKey()).compareTo(cache.get(entry2.getKey())); + } + return freqCompare; + }); + return minEntry.map(Map.Entry::getKey).orElse(null); + } + + @Override + public Optional get(K key) { + if (cache.containsKey(key)) { + usageCount.put(key, usageCount.get(key) + 1); + return Optional.of(key); + } + return Optional.empty(); + } +} diff --git a/src/main/java/database/storageEngine/bufferpool/pageReplacementStrategies/LRUStrategy.java b/src/main/java/database/storageEngine/bufferpool/pageReplacementStrategies/LRUStrategy.java new file mode 100644 index 0000000..cad4c56 --- /dev/null +++ b/src/main/java/database/storageEngine/bufferpool/pageReplacementStrategies/LRUStrategy.java @@ -0,0 +1,49 @@ +package database.storageEngine.bufferpool.pageReplacementStrategies; + +import database.storageEngine.bufferpool.PageReplacementStrategy; +import java.util.LinkedHashSet; +import java.util.Optional; + +public class LRUStrategy implements PageReplacementStrategy { + + private final int capacity; + private final LinkedHashSet cache; + + public LRUStrategy(int capacity) { + this.capacity = capacity; + this.cache = new LinkedHashSet<>(capacity); + } + + @Override + public void put(K key) { + if (cache.contains(key)) { + cache.remove(key); + } else if (cache.size() >= capacity) { + evict(); + } + cache.add(key); + } + + @Override + public K evict() { + K firstKey = cache.iterator().next(); + cache.remove(firstKey); + return firstKey; + } + + @Override + public boolean contains(K key) { + return cache.contains(key); + } + + @Override + public Optional get(K key) { + if (cache.contains(key)) { + cache.remove(key); + cache.add(key); + return Optional.of(key); + } + return Optional.empty(); + } +} + diff --git a/src/main/java/database/storageEngine/page/FileHeader.java b/src/main/java/database/storageEngine/page/FileHeader.java new file mode 100644 index 0000000..d4aab13 --- /dev/null +++ b/src/main/java/database/storageEngine/page/FileHeader.java @@ -0,0 +1,36 @@ +package database.storageEngine.page; + +import java.io.Serializable; + +public class FileHeader implements Serializable { + + private static final int HEADER_SIZE = 38; + + private final long pageNumber; + private final long checksum; + + public FileHeader(long pageNumber) { + this.pageNumber = pageNumber; + this.checksum = 0; + } + + public long getPageNumber() { + return pageNumber; + } + + public long getChecksum() { + return checksum; + } + + public int getHeaderSize() { + return HEADER_SIZE; + } + + @Override + public String toString() { + return "FileHeader{" + + "pageNumber=" + pageNumber + + ", checksum=" + checksum + + '}'; + } +} diff --git a/src/main/java/database/storageEngine/page/FileManager.java b/src/main/java/database/storageEngine/page/FileManager.java new file mode 100644 index 0000000..bc96cc1 --- /dev/null +++ b/src/main/java/database/storageEngine/page/FileManager.java @@ -0,0 +1,92 @@ +package database.storageEngine.page; + +import database.storageEngine.bufferpool.TablePageKey; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +public class FileManager { + + private static final int PAGE_SIZE = 16 * 1024; + private static final String DIRECTORY_PATH = "disk"; + private static final String FILE_EXTENSION = ".ibd"; + + private int pageSize; + + public FileManager() { + createDirectoryIfNotExists(); + this.pageSize = 0; + } + + private void createDirectoryIfNotExists() { + Path path = Paths.get(DIRECTORY_PATH); + if (!Files.exists(path)) { + try { + Files.createDirectories(path); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private void createTableIfNotExists(String tableName) { + String fileName = DIRECTORY_PATH + File.separator + tableName + FILE_EXTENSION; + File file = new File(fileName); + if (!file.exists()) { + try { + file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public void savePage(String tableName, Page page) { + createTableIfNotExists(tableName); + String fileName = DIRECTORY_PATH + File.separator + tableName + FILE_EXTENSION; + + try (RandomAccessFile file = new RandomAccessFile(fileName, "rw")) { + file.seek(page.getPageNumber() * PAGE_SIZE); + + try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file.getFD()))) { + out.writeObject(page); + } + pageSize++; + } catch (IOException e) { + e.printStackTrace(); + } + } + + public Optional loadPage(TablePageKey key) { + createTableIfNotExists(key.tableName()); + String fileName = DIRECTORY_PATH + File.separator + key.tableName() + FILE_EXTENSION; + + try (RandomAccessFile file = new RandomAccessFile(fileName, "r")) { + file.seek(key.pageNumber() * PAGE_SIZE); + + try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file.getFD()))) { + Page page = (Page) in.readObject(); + return Optional.of(page); + } + } catch (IOException | ClassNotFoundException e) { + return Optional.empty(); + } + } + + public Page createNewDataPage() { + long newPageNumber = getNewPageNumber(); + return PageFactory.createDataPage(newPageNumber); + } + + private int getNewPageNumber() { + return pageSize; + } +} diff --git a/src/main/java/database/storageEngine/page/Page.java b/src/main/java/database/storageEngine/page/Page.java new file mode 100644 index 0000000..6476d0a --- /dev/null +++ b/src/main/java/database/storageEngine/page/Page.java @@ -0,0 +1,84 @@ +package database.storageEngine.page; + +import database.engine.Record; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class Page implements Serializable { + + private static final int PAGE_SIZE = 16 * 1024; + + private final FileHeader fileHeader; + private final PageHeader pageHeader; + private final List userStorageRecords; + private final PageDirectory pageDirectory; + private int freeSpace; + + public Page(long pageNumber, PageType pageType) { + this.fileHeader = new FileHeader(pageNumber); + this.pageHeader = new PageHeader(pageType); + this.userStorageRecords = new ArrayList<>(); + this.pageDirectory = new PageDirectory(); + this.freeSpace = PAGE_SIZE - (this.fileHeader.getHeaderSize() + this.pageHeader.getHeaderSize()); + } + + public boolean addRecord(StorageRecord storageRecord) { + int recordSize = storageRecord.getSize(); + if (freeSpace >= recordSize) { + userStorageRecords.add(storageRecord); + pageDirectory.addRecordPosition(userStorageRecords.size() - 1); + freeSpace -= recordSize; + pageHeader.incrementRecordCount(); + markDirty(); + return true; + } else { + return false; + } + } + + private void markDirty() { + pageHeader.markDirty(); + } + + public boolean isDirty() { + return pageHeader.isDirty(); + } + + public void clean() { + pageHeader.clean(); + } + + public List searchRecords(Object key) { + return userStorageRecords.stream() + .filter(storageRecord -> storageRecord.contains(key)) + .toList(); + } + + public long getPageNumber() { + return fileHeader.getPageNumber(); + } + + public PageType getPageType() { + return pageHeader.getPageType(); + } + + public int getRecordCount() { + return pageHeader.getRecordCount(); + } + + public int getFreeSpace() { + return freeSpace; + } + + @Override + public String toString() { + return "Page{" + + "fileHeader=" + fileHeader + + ", pageHeader=" + pageHeader + + ", userStorageRecords=" + userStorageRecords + + ", pageDirectory=" + pageDirectory + + ", freeSpace=" + freeSpace + + '}'; + } +} diff --git a/src/main/java/database/storageEngine/page/PageDirectory.java b/src/main/java/database/storageEngine/page/PageDirectory.java new file mode 100644 index 0000000..ef6e38c --- /dev/null +++ b/src/main/java/database/storageEngine/page/PageDirectory.java @@ -0,0 +1,25 @@ +package database.storageEngine.page; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class PageDirectory implements Serializable { + + private final List recordPositions; + + public PageDirectory() { + this.recordPositions = new ArrayList<>(); + } + + public void addRecordPosition(int position) { + recordPositions.add(position); + } + + @Override + public String toString() { + return "PageDirectory{" + + "recordPositions=" + recordPositions + + '}'; + } +} diff --git a/src/main/java/database/storageEngine/page/PageFactory.java b/src/main/java/database/storageEngine/page/PageFactory.java new file mode 100644 index 0000000..5948a38 --- /dev/null +++ b/src/main/java/database/storageEngine/page/PageFactory.java @@ -0,0 +1,16 @@ +package database.storageEngine.page; + +public class PageFactory { + + public static Page createDataPage(long pageNumber) { + return new Page(pageNumber, PageType.PAGE_TYPE_CLUSTERED); + } + + public static Page createIndexPage(long pageNumber) { + return new Page(pageNumber, PageType.PAGE_TYPE_BTR_INDEX); + } + + public static Page createUndoPage(long pageNumber) { + return new Page(pageNumber, PageType.PAGE_TYPE_UNDO); + } +} diff --git a/src/main/java/database/storageEngine/page/PageHeader.java b/src/main/java/database/storageEngine/page/PageHeader.java new file mode 100644 index 0000000..a093dff --- /dev/null +++ b/src/main/java/database/storageEngine/page/PageHeader.java @@ -0,0 +1,54 @@ +package database.storageEngine.page; + +import java.io.Serializable; + +public class PageHeader implements Serializable { + + private static final int HEADER_SIZE = 56; + private final PageType pageType; + private int recordCount; + private boolean isDirty; + + public PageHeader(PageType pageType) { + this.recordCount = 0; + this.isDirty = false; + this.pageType = pageType; + } + + public void incrementRecordCount() { + recordCount++; + } + + public void markDirty() { + isDirty = true; + } + + public int getRecordCount() { + return recordCount; + } + + public boolean isDirty() { + return isDirty; + } + + public void clean() { + this.isDirty = false; + } + + public PageType getPageType() { + return pageType; + } + + public int getHeaderSize() { + return HEADER_SIZE; + } + + @Override + public String toString() { + return "PageHeader{" + + "recordCount=" + recordCount + + ", isDirty=" + isDirty + + ", pageType=" + pageType + + '}'; + } +} diff --git a/src/main/java/database/storageEngine/page/PageType.java b/src/main/java/database/storageEngine/page/PageType.java new file mode 100644 index 0000000..05798eb --- /dev/null +++ b/src/main/java/database/storageEngine/page/PageType.java @@ -0,0 +1,8 @@ +package database.storageEngine.page; + +public enum PageType { + PAGE_TYPE_CLUSTERED, + PAGE_TYPE_BTR_INDEX, + PAGE_TYPE_UNDO, + ; +} diff --git a/src/main/java/database/storageEngine/page/StorageRecord.java b/src/main/java/database/storageEngine/page/StorageRecord.java new file mode 100644 index 0000000..6715a33 --- /dev/null +++ b/src/main/java/database/storageEngine/page/StorageRecord.java @@ -0,0 +1,44 @@ +package database.storageEngine.page; + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.List; + +public class StorageRecord implements Serializable { + + private final List values; + + public StorageRecord(List values) { + this.values = values; + } + + public boolean contains(Object key){ + return values.contains(key); + } + + public List getValues() { + return values; + } + + public int getSize() { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(values); + oos.flush(); + oos.close(); + return baos.size(); + } catch (java.io.IOException e) { + e.printStackTrace(); + return 0; + } + } + + @Override + public String toString() { + return "StorageRecord{" + + "values=" + values + + '}'; + } +} diff --git a/src/test/java/database/storageEngine/bufferpool/BufferPoolTest.java b/src/test/java/database/storageEngine/bufferpool/BufferPoolTest.java new file mode 100644 index 0000000..4998060 --- /dev/null +++ b/src/test/java/database/storageEngine/bufferpool/BufferPoolTest.java @@ -0,0 +1,70 @@ +package database.storageEngine.bufferpool; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import database.storageEngine.bufferpool.pageReplacementStrategies.LRUStrategy; +import database.storageEngine.page.PageFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("버퍼풀 테스트") +class BufferPoolTest { + + private final int capacity = 2; + private final String tableName = "table"; + private BufferPool bufferPool; + + @BeforeEach + void setUp() { + PageReplacementStrategy lruStrategy = new LRUStrategy<>(capacity); + bufferPool = new BufferPool(capacity, lruStrategy); + } + + @DisplayName("버퍼풀 생성에 성공한다.") + @Test + void createBufferPool() { + // given + int pageNumber1 = 1; + TablePageKey tablePageKey1 = new TablePageKey(tableName, pageNumber1); + int pageNumber2 = 2; + TablePageKey tablePageKey2 = new TablePageKey(tableName, pageNumber2); + + // when + bufferPool.putPage(tablePageKey1, PageFactory.createDataPage(pageNumber1)); + bufferPool.putPage(tablePageKey2, PageFactory.createDataPage(pageNumber2)); + + // then + assertAll( + () -> assertThat(bufferPool.getPage(tablePageKey1)).isPresent(), + () -> assertThat(bufferPool.getPage(tablePageKey2)).isPresent() + ); + } + + @DisplayName("LRU 정책에 따라 가장 오래된 페이지가 제거된다.") + @Test + void lru() { + // given + int pageNumber1 = 1; + TablePageKey tablePageKey1 = new TablePageKey(tableName, pageNumber1); + int pageNumber2 = 2; + TablePageKey tablePageKey2 = new TablePageKey(tableName, pageNumber2); + int pageNumber3 = 3; + TablePageKey tablePageKey3 = new TablePageKey(tableName, pageNumber3); + + bufferPool.putPage(tablePageKey1, PageFactory.createDataPage(pageNumber1)); + bufferPool.putPage(tablePageKey2, PageFactory.createDataPage(pageNumber2)); + + // when + bufferPool.getPage(tablePageKey1); + bufferPool.putPage(tablePageKey3, PageFactory.createDataPage(pageNumber3)); + + // then + assertAll( + () -> assertThat(bufferPool.getPage(tablePageKey1)).isPresent(), + () -> assertThat(bufferPool.getPage(tablePageKey2)).isEmpty(), + () -> assertThat(bufferPool.getPage(tablePageKey3)).isPresent() + ); + } +} diff --git a/src/test/java/database/storageEngine/page/FileManagerTest.java b/src/test/java/database/storageEngine/page/FileManagerTest.java new file mode 100644 index 0000000..3371112 --- /dev/null +++ b/src/test/java/database/storageEngine/page/FileManagerTest.java @@ -0,0 +1,80 @@ +package database.storageEngine.page; + +import static org.assertj.core.api.Assertions.assertThat; + +import database.storageEngine.bufferpool.TablePageKey; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("파일 매니저 테스트") +class FileManagerTest { + + private static final String DIRECTORY_PATH = "disk"; + private static final String FILE_EXTENSION = ".ibd"; + + private FileManager fileManager; + private final String tableName = "table"; + + @BeforeEach + void setUp() { + fileManager = new FileManager(); + } + + @DisplayName("페이지 저장에 성공한다.") + @Test + void savePage() { + // given + Page page = PageFactory.createDataPage(0); + + // when + fileManager.savePage(tableName, page); + + // then + Path filePath = Paths.get(DIRECTORY_PATH, tableName + FILE_EXTENSION); + assertThat(Files.exists(filePath)).isTrue(); + } + + @DisplayName("페이지 조회에 성공한다.") + @Test + void loadPage() { + // given + int pageNumber1 = 1; + int pageNumber2 = 2; + + Page page1 = PageFactory.createDataPage(pageNumber1); + Page page2 = PageFactory.createUndoPage(pageNumber2); + + fileManager.savePage(tableName, page1); + fileManager.savePage(tableName, page2); + + // when + Page foundPage1 = fileManager.loadPage(new TablePageKey(tableName, pageNumber1)).get(); + Page foundPage2 = fileManager.loadPage(new TablePageKey(tableName, pageNumber2)).get(); + + // then + assertThat(foundPage1.getPageNumber()).isEqualTo(pageNumber1); + assertThat(foundPage2.getPageNumber()).isEqualTo(pageNumber2); + } + + @AfterEach + void tearDown() { + Path filePath = Paths.get(DIRECTORY_PATH, tableName + FILE_EXTENSION); + try { + Files.deleteIfExists(filePath); + } catch (IOException e) { + e.printStackTrace(); + } + + try { + Files.deleteIfExists(Paths.get(DIRECTORY_PATH)); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/test/java/database/storageEngine/page/PageFactoryTest.java b/src/test/java/database/storageEngine/page/PageFactoryTest.java new file mode 100644 index 0000000..92944d0 --- /dev/null +++ b/src/test/java/database/storageEngine/page/PageFactoryTest.java @@ -0,0 +1,45 @@ +package database.storageEngine.page; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("페이지 팩터리 테스트") +class PageFactoryTest { + + private final int pageNumber = 1234; + + @DisplayName("데이터 페이지 생성에 성공한다.") + @Test + void createDataPage() { + // given&when + Page page = PageFactory.createDataPage(pageNumber); + + // then + assertThat(page.getPageType()).isEqualTo(PageType.PAGE_TYPE_CLUSTERED); + assertThat(page.getPageNumber()).isEqualTo(pageNumber); + } + + @DisplayName("인덱스 페이지 생성에 성공한다.") + @Test + void createIndexPage() { + // given&when + Page page = PageFactory.createIndexPage(pageNumber); + + // then + assertThat(page.getPageType()).isEqualTo(PageType.PAGE_TYPE_BTR_INDEX); + assertThat(page.getPageNumber()).isEqualTo(pageNumber); + } + + @DisplayName("언두 페이지 생성에 성공한다.") + @Test + void createUndoPage() { + // given&when + Page page = PageFactory.createUndoPage(pageNumber); + + // then + assertThat(page.getPageType()).isEqualTo(PageType.PAGE_TYPE_UNDO); + assertThat(page.getPageNumber()).isEqualTo(pageNumber); + } +} diff --git a/src/test/java/database/storageEngine/page/PageHeaderTest.java b/src/test/java/database/storageEngine/page/PageHeaderTest.java new file mode 100644 index 0000000..befb67e --- /dev/null +++ b/src/test/java/database/storageEngine/page/PageHeaderTest.java @@ -0,0 +1,60 @@ +package database.storageEngine.page; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.stream.IntStream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("페이지 헤더 테스트") +class PageHeaderTest { + + @DisplayName("페이지 헤더를 생성한다.") + @Test + void createPageHeader() { + // given + PageType pageType = PageType.PAGE_TYPE_CLUSTERED; + int initialRecordCnt = 0; + + // when + PageHeader pageHeader = new PageHeader(pageType); + + // then + assertAll( + () -> assertThat(pageHeader.getPageType()).isEqualTo(pageType), + () -> assertThat(pageHeader.getRecordCount()).isEqualTo(initialRecordCnt), + () -> assertThat(pageHeader.isDirty()).isFalse() + ); + } + + @DisplayName("페이지 레코드 수가 증가한다.") + @Test + void incrementRecord() { + // given + PageType pageType = PageType.PAGE_TYPE_CLUSTERED; + int recordCnt = 2; + + // when + PageHeader pageHeader = new PageHeader(pageType); + IntStream.range(0, recordCnt) + .forEach(i -> pageHeader.incrementRecordCount()); + + // then + assertThat(pageHeader.getRecordCount()).isEqualTo(recordCnt); + } + + @DisplayName("더티 페이지로 설정한다.") + @Test + void markDirty() { + // given + PageType pageType = PageType.PAGE_TYPE_CLUSTERED; + PageHeader pageHeader = new PageHeader(pageType); + + // when + pageHeader.markDirty(); + + // then + assertThat(pageHeader.isDirty()).isTrue(); + } +} diff --git a/src/test/java/database/storageEngine/page/PageTest.java b/src/test/java/database/storageEngine/page/PageTest.java new file mode 100644 index 0000000..4d756e2 --- /dev/null +++ b/src/test/java/database/storageEngine/page/PageTest.java @@ -0,0 +1,51 @@ +package database.storageEngine.page; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("페이지 테스트") +class PageTest { + + @DisplayName("페이지 생성에 성공하다.") + @Test + void createPage() { + // given + int pageNumber = 1; + PageType pageType = PageType.PAGE_TYPE_CLUSTERED; + + // when + Page page = new Page(pageNumber, pageType); + + // then + assertAll( + () -> assertThat(page.getPageNumber()).isEqualTo(pageNumber), + () -> assertThat(page.getPageType()).isEqualTo(pageType), + () -> assertThat(page.getRecordCount()).isEqualTo(0) + ); + } + + @DisplayName("레코드 추가에 성공한다.") + @Test + void addRecord() { + // given + int pageNumber = 1; + PageType pageType = PageType.PAGE_TYPE_CLUSTERED; + Page page = new Page(pageNumber, pageType); + StorageRecord storageRecord1 = new StorageRecord(List.of(1, 2, 3, 4)); + StorageRecord storageRecord2 = new StorageRecord(List.of(5, 6, 7, 8)); + + // when + page.addRecord(storageRecord1); + page.addRecord(storageRecord2); + + // then + assertAll( + () -> assertThat(page.getRecordCount()).isEqualTo(2), + () -> assertThat(page.getPageNumber()).isEqualTo(pageNumber) + ); + } +} diff --git a/src/test/java/database/storageEngine/page/StorageRecordTest.java b/src/test/java/database/storageEngine/page/StorageRecordTest.java new file mode 100644 index 0000000..8b9cb61 --- /dev/null +++ b/src/test/java/database/storageEngine/page/StorageRecordTest.java @@ -0,0 +1,25 @@ +package database.storageEngine.page; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("레코드 테스트") +class StorageRecordTest { + + @DisplayName("레코드 생성에 성공한다.") + @Test + void createRecord() { + // given + List data = List.of("Chocochip", 123); + StorageRecord storageRecord = new StorageRecord(data); + + // when + List retrievedData = storageRecord.getValues(); + + // then + assertThat(retrievedData.size()).isEqualTo(data.size()); + } +}