From 931111953e37482cbe63bb63f23a71b86bca9be5 Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Fri, 1 Jan 2021 17:19:05 +0800 Subject: [PATCH 01/18] =?UTF-8?q?[+]=E5=A2=9E=E5=8A=A0=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E7=9A=84Mock=20(#1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 增加错误基类、简单的记录接口 * 增加.gitignore * 完成记录的mock * 增加记录接口的简易单元测试 --- .gitignore | 32 ++++++++ src/main/java/net/kaaass/rumbase/Main.java | 13 ++++ .../rumbase/exception/RumbaseException.java | 49 ++++++++++++ .../kaaass/rumbase/record/IRecordStorage.java | 64 +++++++++++++++ .../kaaass/rumbase/record/RecordManager.java | 22 ++++++ .../exception/RecordNotFoundException.java | 29 +++++++ .../record/mock/MockRecordStorage.java | 78 +++++++++++++++++++ .../transaction/TransactionContext.java | 20 +++++ .../rumbase/record/IRecordStorageTest.java | 73 +++++++++++++++++ 9 files changed, 380 insertions(+) create mode 100644 .gitignore create mode 100644 src/main/java/net/kaaass/rumbase/Main.java create mode 100644 src/main/java/net/kaaass/rumbase/exception/RumbaseException.java create mode 100644 src/main/java/net/kaaass/rumbase/record/IRecordStorage.java create mode 100644 src/main/java/net/kaaass/rumbase/record/RecordManager.java create mode 100644 src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java create mode 100644 src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java create mode 100644 src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f32518d --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# IntelliJ IDEA +.idea +*.iws +*.iml +*.ipr +build/ +.gradle + +*.srctrl* +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* diff --git a/src/main/java/net/kaaass/rumbase/Main.java b/src/main/java/net/kaaass/rumbase/Main.java new file mode 100644 index 0000000..cf89d22 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/Main.java @@ -0,0 +1,13 @@ +package net.kaaass.rumbase; + +/** + * 入口类 + * + * @author kaaass + */ +public class Main { + + public static void main(String[] args) { + System.out.println("Rumbase"); + } +} diff --git a/src/main/java/net/kaaass/rumbase/exception/RumbaseException.java b/src/main/java/net/kaaass/rumbase/exception/RumbaseException.java new file mode 100644 index 0000000..deaeba8 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/exception/RumbaseException.java @@ -0,0 +1,49 @@ +package net.kaaass.rumbase.exception; + +/** + * 错误基类 + *

+ * 错误分为主错误号、子错误号。 + * 主错误号应该体现错误的类型,由子类构造器直接赋值。主错误号由模块号确定十进制千位,其余位由模块编码。 + * 子错误号体现错误的细节、逻辑。在具体抛出错误的时候赋值。 + *

+ * 主错误号模块分配: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
功能起始主错误号
SQL 语句解析parse1000
查询执行、优化query2000
系统内数据库、表管理table3000
索引结构,使用 B+ 树index4000
记录管理,实现 MVCCrecord5000
实现事务的管理与 2PLtransaction6000
数据项管理dataitem7000
日志与恢复管理recovery8000
缓冲与页管理page9000
+ *

+ * 如对于 {@link net.kaaass.rumbase.record.exception.RecordNotFoundException},主错误号 + * 是5001,而子错误号不同表示不同发生原因。如物理记录不存在,或记录对事务不可见等等。 + * + * @author kaaass + */ +public class RumbaseException extends Exception { + + private int mainId; + + private int subId; + + /** + * 构造Rumbase异常 + * + * @param mainId 主错误号 + * @param subId 子错误号 + * @param reason 错误原因 + */ + public RumbaseException(int mainId, int subId, String reason) { + super(String.format("E%d-%d: %s", mainId, subId, reason)); + this.mainId = mainId; + this.subId = subId; + } +} diff --git a/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java new file mode 100644 index 0000000..a965774 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java @@ -0,0 +1,64 @@ +package net.kaaass.rumbase.record; + +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.util.Optional; +import java.util.UUID; + +/** + * 记录存储接口,提供由记录ID存取数据的容器 + * + * @author kaaass + */ +public interface IRecordStorage { + + /** + * 向存储中加入一串记录数据字节 + * + * @param txContext 事务上下文 + * @param rawData 字节数据 + * @return 记录ID + */ + UUID insert(TransactionContext txContext, byte[] rawData); + + /** + * 由记录ID查询记录数据 + * + * @param txContext 事务上下文 + * @param recordId 记录ID + * @return 记录数据字节 + */ + byte[] query(TransactionContext txContext, UUID recordId) throws RecordNotFoundException; + + default Optional queryOptional(TransactionContext txContext, UUID recordId) { + try { + return Optional.of(query(txContext, recordId)); + } catch (RecordNotFoundException ignore) { + return Optional.empty(); + } + } + + /** + * 由记录ID删除记录数据 + * + * @param txContext 事务上下文 + * @param recordId 记录ID + */ + void delete(TransactionContext txContext, UUID recordId); + + + /** + * 获得记录存储的元信息(与单个记录无关) + * + * @return 元信息数据 + */ + byte[] getMetadata(TransactionContext txContext); + + /** + * 更新记录存储的元信息 + * + * @param metadata 元信息数据 + */ + void setMetadata(TransactionContext txContext, byte[] metadata); +} diff --git a/src/main/java/net/kaaass/rumbase/record/RecordManager.java b/src/main/java/net/kaaass/rumbase/record/RecordManager.java new file mode 100644 index 0000000..865e233 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/record/RecordManager.java @@ -0,0 +1,22 @@ +package net.kaaass.rumbase.record; + +import net.kaaass.rumbase.record.mock.MockRecordStorage; + +/** + * 记录管理类 + * + * @author kaaass + */ +public class RecordManager { + + + /** + * 从文件获取记录存储对象 + * + * @param filepath 记录文件 + * @return 记录存储对象 + */ + public static IRecordStorage fromFile(String filepath) { + return MockRecordStorage.ofFile(filepath); + } +} diff --git a/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java b/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java new file mode 100644 index 0000000..120eb77 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java @@ -0,0 +1,29 @@ +package net.kaaass.rumbase.record.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E5001 记录不存在异常 + *

+ * E5001-1 物理记录不存在 + * + * @author kaaass + */ +public class RecordNotFoundException extends RumbaseException { + + public static final Map REASONS = new HashMap<>(){{ + put(1, "物理记录不存在"); + }}; + + /** + * 记录不存在异常 + * + * @param subId 子错误号 + */ + public RecordNotFoundException(int subId) { + super(5001, subId, REASONS.get(subId)); + } +} diff --git a/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java new file mode 100644 index 0000000..8a426bf --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java @@ -0,0 +1,78 @@ +package net.kaaass.rumbase.record.mock; + +import net.kaaass.rumbase.record.IRecordStorage; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** + * Mock记录存储,仅用于测试 + * + * @author kaaass + */ +@Deprecated +public class MockRecordStorage implements IRecordStorage { + + /** + * 内存中创建的Mock存储 + */ + private static final Map MOCK_STORAGES = new HashMap<>(); + + private final String mockId; + + private final Map memoryStorage = new HashMap<>(); + + private byte[] metadata = new byte[0]; + + private MockRecordStorage(String mockId) { + this.mockId = mockId; + } + + @Override + public UUID insert(TransactionContext txContext, byte[] rawData) { + var uuid = UUID.randomUUID(); + this.memoryStorage.put(uuid, rawData); + return uuid; + } + + @Override + public byte[] query(TransactionContext txContext, UUID recordId) throws RecordNotFoundException { + if (!this.memoryStorage.containsKey(recordId)) { + throw new RecordNotFoundException(1); + } + return this.memoryStorage.get(recordId); + } + + @Override + public void delete(TransactionContext txContext, UUID recordId) { + this.memoryStorage.remove(recordId); + } + + @Override + public byte[] getMetadata(TransactionContext txContext) { + return this.metadata; + } + + @Override + public void setMetadata(TransactionContext txContext, byte[] metadata) { + this.metadata = metadata; + } + + public static MockRecordStorage ofFile(String filepath) { + var mockId = "file" + filepath; + if (MOCK_STORAGES.containsKey(mockId)) { + return MOCK_STORAGES.get(mockId); + } + var result = new MockRecordStorage(mockId); + MOCK_STORAGES.put(mockId, result); + return result; + } + + public String getMockId() { + return mockId; + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java new file mode 100644 index 0000000..7f58128 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java @@ -0,0 +1,20 @@ +package net.kaaass.rumbase.transaction; + +/** + * 事务上下文 + * + * @author + */ +public class TransactionContext { + + private TransactionContext() { + } + + /** + * 返回空事务上下文。空事务或超级事务XID为0,不受事务限制,也不具备ACID性质。 + * @return 空事务上下文 + */ + public static TransactionContext empty() { + return new TransactionContext(); + } +} diff --git a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java new file mode 100644 index 0000000..6a48bd1 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java @@ -0,0 +1,73 @@ +package net.kaaass.rumbase.record; + +import junit.framework.TestCase; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.util.Arrays; +import java.util.UUID; + +import static org.junit.Assert.assertArrayEquals; + +/** + * 测试记录存储接口 + * + * @see net.kaaass.rumbase.record.IRecordStorage + * @author kaaass + */ +public class IRecordStorageTest extends TestCase { + + public void testQuery() { + var storage = RecordManager.fromFile("test_query"); + var context = TransactionContext.empty(); + + var result = storage.queryOptional(context, UUID.randomUUID()); + + assertTrue("unknown uuid should get empty", result.isEmpty()); + + try { + storage.query(context, UUID.randomUUID()); + fail("unknown uuid should get exception"); + } catch (RecordNotFoundException e) { + e.printStackTrace(); + } + } + + public void testInsert() { + var storage = RecordManager.fromFile("test_insert"); + var context = TransactionContext.empty(); + + var id = storage.insert(context, new byte[]{0x1, 0x2, 0x1f}); + var result = storage.queryOptional(context, id); + + assertTrue("result should present", result.isPresent()); + assertArrayEquals(new byte[]{0x1, 0x2, 0x1f}, result.get()); + } + + public void testDelete() { + var storage = RecordManager.fromFile("test_delete"); + var context = TransactionContext.empty(); + + storage.insert(context, new byte[]{0x1, 0x2}); + storage.insert(context, new byte[]{0x7, 0x3, 0x1f}); + var id = storage.insert(context, new byte[]{0x54, 0x23, 0x23, 0x44}); + var result = storage.queryOptional(context, id); + assertTrue("result should present", result.isPresent()); + + storage.delete(context, id); + result = storage.queryOptional(context, id); + + assertTrue("record should be deleted", result.isEmpty()); + } + + public void testMetadata() { + var storage = RecordManager.fromFile("test_metadata"); + var context = TransactionContext.empty(); + + var result = storage.getMetadata(context); + assertArrayEquals("default metadata should be empty", new byte[0], result); + + storage.setMetadata(context, new byte[]{0x23, 0x45, 0x67}); + assertArrayEquals(new byte[]{0x23, 0x45, 0x67}, storage.getMetadata(context)); + } +} \ No newline at end of file From e754d389b6930360804d2bcdfd2f21b30be884f5 Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Sat, 2 Jan 2021 16:46:48 +0800 Subject: [PATCH 02/18] =?UTF-8?q?[+]=E5=A2=9E=E5=8A=A0=E8=A1=A8=E7=AE=A1?= =?UTF-8?q?=E7=90=86mock=E3=80=81=E6=9B=B4=E6=96=B0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 增加错误基类、简单的记录接口 * 增加.gitignore * 完成记录的mock * 增加记录接口的简易单元测试 * 引入Slf4j、log4j、jbbp外部库 * [+]add table mock and unit test * 增加Travis配置 * fix: transform java14 to java11 * 更新Gradle配置以允许Scan ToS * 删除冗余Travis配置 * README增加Travis Badge Co-authored-by: Kevin Axel Manjaro Co-authored-by: Kevin Axel <41867249+KveinAxel@users.noreply.github.com> --- .travis.yml | 7 + README.md | 8 +- build.gradle | 19 ++ src/main/java/net/kaaass/rumbase/Main.java | 5 +- .../record/mock/MockRecordStorage.java | 13 +- .../java/net/kaaass/rumbase/table/Entry.java | 13 ++ .../net/kaaass/rumbase/table/FieldType.java | 16 ++ .../net/kaaass/rumbase/table/FieldValue.java | 56 +++++ .../java/net/kaaass/rumbase/table/IField.java | 100 +++++++++ .../java/net/kaaass/rumbase/table/ITable.java | 115 ++++++++++ .../kaaass/rumbase/table/TableManager.java | 189 ++++++++++++++++ .../exception/TableNotFoundException.java | 32 +++ .../exception/TypeIncompatibleException.java | 32 +++ .../kaaass/rumbase/table/mock/MockField.java | 201 ++++++++++++++++++ .../kaaass/rumbase/table/mock/MockTable.java | 148 +++++++++++++ src/main/resources/log4j.properties | 5 + .../rumbase/record/IRecordStorageTest.java | 4 +- .../net/kaaass/rumbase/table/IFieldTest.java | 120 +++++++++++ .../net/kaaass/rumbase/table/ITableTest.java | 102 +++++++++ .../rumbase/table/TableManagerTest.java | 63 ++++++ 20 files changed, 1237 insertions(+), 11 deletions(-) create mode 100644 .travis.yml create mode 100644 src/main/java/net/kaaass/rumbase/table/Entry.java create mode 100644 src/main/java/net/kaaass/rumbase/table/FieldType.java create mode 100644 src/main/java/net/kaaass/rumbase/table/FieldValue.java create mode 100644 src/main/java/net/kaaass/rumbase/table/IField.java create mode 100644 src/main/java/net/kaaass/rumbase/table/ITable.java create mode 100644 src/main/java/net/kaaass/rumbase/table/TableManager.java create mode 100644 src/main/java/net/kaaass/rumbase/table/exception/TableNotFoundException.java create mode 100644 src/main/java/net/kaaass/rumbase/table/exception/TypeIncompatibleException.java create mode 100644 src/main/java/net/kaaass/rumbase/table/mock/MockField.java create mode 100644 src/main/java/net/kaaass/rumbase/table/mock/MockTable.java create mode 100644 src/main/resources/log4j.properties create mode 100644 src/test/java/net/kaaass/rumbase/table/IFieldTest.java create mode 100644 src/test/java/net/kaaass/rumbase/table/ITableTest.java create mode 100644 src/test/java/net/kaaass/rumbase/table/TableManagerTest.java diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..336a154 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: java +jdk: oraclejdk11 +dist: trusty +group: edge + +script: + - ./gradlew build --scan -s diff --git a/README.md b/README.md index f46897d..9bc6ad7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # RumBase Java +| master | dev | feature-mock | +| ------ | --- | ------------ | +| [![Build Status](https://www.travis-ci.com/kaaass/rumbase_java.svg?token=7d6V7UKwzfD6augATNKx&branch=master)](https://www.travis-ci.com/kaaass/rumbase_java) | [![Build Status](https://www.travis-ci.com/kaaass/rumbase_java.svg?token=7d6V7UKwzfD6augATNKx&branch=dev)](https://www.travis-ci.com/kaaass/rumbase_java) | [![Build Status](https://www.travis-ci.com/kaaass/rumbase_java.svg?token=7d6V7UKwzfD6augATNKx&branch=feature-mock)](https://www.travis-ci.com/kaaass/rumbase_java) | + Java构建的高性能SQL关系型数据库。 本项目为吉林大学2018级数据库系统课程&系统软件综合实践(荣誉课)课程设计。 @@ -9,8 +13,8 @@ Java构建的高性能SQL关系型数据库。 | **模块** | **内容** | **负责人** | **包** | | ----------------------- | -------------------- | ---------- | ------------ | | Query Parse Module | SQL 语句解析 | @KAAAsS | parse | -| Query Execution Module | 查询执行、优化 | | query | -| Table Management Module | 系统内数据库、表管理 | | table | +| Query Execution Module | 查询执行、优化 | @KveinAxel | query | +| Table Management Module | 系统内数据库、表管理 | @KveinAxel | table | | Indexing Module | 索引结构,使用 B+ 树 | | index | | Record Module | 记录管理,实现 MVCC | @KAAAsS | record | | Transaction Module | 实现事务的管理与 2PL | | transaction | diff --git a/build.gradle b/build.gradle index 47e84f2..9a93815 100644 --- a/build.gradle +++ b/build.gradle @@ -10,5 +10,24 @@ repositories { } dependencies { + // java-binary-block-parser + compile 'com.igormaznitsa:jbbp:2.0.2' + // Lombok + compileOnly 'org.projectlombok:lombok:1.18.16' + annotationProcessor 'org.projectlombok:lombok:1.18.16' + testCompileOnly 'org.projectlombok:lombok:1.18.16' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.16' + // Slf4j + compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30' + compile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.30' + // JUnit testCompile group: 'junit', name: 'junit', version: '4.12' } + +// Agree --scan ToS +if (hasProperty('buildScan')) { + buildScan { + termsOfServiceUrl = 'https://gradle.com/terms-of-service' + termsOfServiceAgree = 'yes' + } +} diff --git a/src/main/java/net/kaaass/rumbase/Main.java b/src/main/java/net/kaaass/rumbase/Main.java index cf89d22..69e9221 100644 --- a/src/main/java/net/kaaass/rumbase/Main.java +++ b/src/main/java/net/kaaass/rumbase/Main.java @@ -1,13 +1,16 @@ package net.kaaass.rumbase; +import lombok.extern.slf4j.Slf4j; + /** * 入口类 * * @author kaaass */ +@Slf4j public class Main { public static void main(String[] args) { - System.out.println("Rumbase"); + log.debug("Rumbase"); } } diff --git a/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java index 8a426bf..3394821 100644 --- a/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java +++ b/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java @@ -1,5 +1,8 @@ package net.kaaass.rumbase.record.mock; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; import net.kaaass.rumbase.record.IRecordStorage; import net.kaaass.rumbase.record.exception.RecordNotFoundException; import net.kaaass.rumbase.transaction.TransactionContext; @@ -15,6 +18,7 @@ * @author kaaass */ @Deprecated +@RequiredArgsConstructor public class MockRecordStorage implements IRecordStorage { /** @@ -22,16 +26,14 @@ public class MockRecordStorage implements IRecordStorage { */ private static final Map MOCK_STORAGES = new HashMap<>(); + + @Getter private final String mockId; private final Map memoryStorage = new HashMap<>(); private byte[] metadata = new byte[0]; - private MockRecordStorage(String mockId) { - this.mockId = mockId; - } - @Override public UUID insert(TransactionContext txContext, byte[] rawData) { var uuid = UUID.randomUUID(); @@ -72,7 +74,4 @@ public static MockRecordStorage ofFile(String filepath) { return result; } - public String getMockId() { - return mockId; - } } diff --git a/src/main/java/net/kaaass/rumbase/table/Entry.java b/src/main/java/net/kaaass/rumbase/table/Entry.java new file mode 100644 index 0000000..74f3094 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/Entry.java @@ -0,0 +1,13 @@ +package net.kaaass.rumbase.table; + +import java.util.*; + +/** + * 一行记录数据 + *

+ * 代表表中的一行数据 + * + * @author @KveinAxel + */ +public class Entry extends HashMap { +} diff --git a/src/main/java/net/kaaass/rumbase/table/FieldType.java b/src/main/java/net/kaaass/rumbase/table/FieldType.java new file mode 100644 index 0000000..bcadec6 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/FieldType.java @@ -0,0 +1,16 @@ +package net.kaaass.rumbase.table; + +public enum FieldType { + INT, + FLOAT; + + public int getSize() { + if (this == FieldType.INT) { + return 4; + } else if (this == FieldType.FLOAT) { + return 4; + } else { + return 4; + } + } +} diff --git a/src/main/java/net/kaaass/rumbase/table/FieldValue.java b/src/main/java/net/kaaass/rumbase/table/FieldValue.java new file mode 100644 index 0000000..f227524 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/FieldValue.java @@ -0,0 +1,56 @@ +package net.kaaass.rumbase.table; + +public class FieldValue implements Comparable { + private T value; + private FieldType type; + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + public FieldType getType() { + return type; + } + + public void setType(FieldType type) { + this.type = type; + } + + public FieldValue(FieldType type) { + this.type = type; + } + + public FieldValue(T value, FieldType type) { + this.value = value; + this.type = type; + } + + + @Override + public int compareTo(Object o) { + if (o instanceof FieldValue) { + var another = (FieldValue) o; + if (this.type != another.type) { + return 0; + } else { + var res = 0; + if (this.type == FieldType.INT) { + res = (int) this.getValue() - (int) another.getValue(); + } else if (this.type == FieldType.FLOAT) { + var tmp = (float) this.getValue() - (float) another.getValue(); + if (tmp < 0) + res = -1; + else if (tmp > 0) + res = 1; + } + return res; + } + } else { + return 0; + } + } +} diff --git a/src/main/java/net/kaaass/rumbase/table/IField.java b/src/main/java/net/kaaass/rumbase/table/IField.java new file mode 100644 index 0000000..01ce46a --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/IField.java @@ -0,0 +1,100 @@ +package net.kaaass.rumbase.table; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * 字段结构 + *

+ * 提供字段解析服务 + *

+ * 提供字段的索引处理 + * + * @author @KveinAxel + */ +public interface IField { + + /** + * 从配置信息的字节数组获取字段的属性 + * + * @param raw 配置信息 + */ + void parseSelf(byte[] raw); + + /** + * 将自身的配置信息持久化成字节数组 + * + * @return 配置信息 + */ + byte[] persistSelf(); + + /** + * 是否已经被建立引用 + * @return true则已经建立引用,反之没建立 + */ + Boolean isIndexed(); + + /** + * 获取字段的类型 + * + * @return 字段类型 + */ + FieldType getType(); + + /** + * 字段所占用的字节数 + * + * @return 字节数 + */ + int getSize(); + + /** + * 在该字段的索引上进行范围搜索 + * + * @param left 查询左端点 + * @param right 查询右端点 + * @return 查询到的uuid + */ + List searchRange(FieldValue left, FieldValue right); + + /** + * 在该字段的索引上进行单点搜索 + * + * @param value 字段的值 + * @return 字段的uuid的Optional + */ + Optional search(FieldValue value); + + /** + * 将字符串转换成字段值 + * + * @param valStr 字符串 + * @return 字段值的Optional + */ + Optional strToValue(String valStr); + + /** + * 将字段值转成字节数组 + * + * @param value 字段值 + * @return 字节数组 + */ + Optional valueToRaw(FieldValue value); + + /** + * 将字节数组转成字段值 + * + * @param raw 字节数组 + * @return 字段值 + */ + Optional rawToValue(byte[] raw) ; + + /** + * 获取字段名 + * + * @return 字段名 + */ + String getFieldName(); + +} diff --git a/src/main/java/net/kaaass/rumbase/table/ITable.java b/src/main/java/net/kaaass/rumbase/table/ITable.java new file mode 100644 index 0000000..4e4f8db --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/ITable.java @@ -0,0 +1,115 @@ +package net.kaaass.rumbase.table; + +import net.kaaass.rumbase.record.IRecordStorage; +import net.kaaass.rumbase.table.exception.TableNotFoundException; +import net.kaaass.rumbase.table.exception.TypeIncompatibleException; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * 表结构 + *

+ * 验证数据是否满足表约束 + *

+ * 提供行数据解析服务 + * + * @author @KveinAxel + */ +public interface ITable { + + /** + * 根据配置信息解析获取表属性 + * @param raw 配置信息 + */ + void parseSelf(byte[] raw); + + /** + * 将自己持久化成字节数组 + */ + byte[] persistSelf(); + + /** + * 删除元组 + * + * @param context 事务context + * @param fieldName 字段名 + * @param uuid 元组的uuid + * @param recordStorage record层访问接口 + * @return 被删除的元组数 + */ + int delete(TransactionContext context, String fieldName, UUID uuid, IRecordStorage recordStorage); + + /** + * 更新元组 + * + * @param context 事务context + * @param fieldName 字段名 + * @param uuid 元组的uuid + * @param newEntry 新的元组 + * @return 被更新的元组数 + */ + int update(TransactionContext context, String fieldName, UUID uuid, Entry newEntry); + + /** + * 获取元组内容 + * + * @param context 事务context + * @param fieldName 字段名 + * @param uuids 元组的uuid列表 + * @param recordStorage record层访问接口 + * @return 元组entry列表 + * @throws TableNotFoundException 要查询的表不存在 + */ + List read(TransactionContext context, String fieldName, List uuids, IRecordStorage recordStorage) throws TableNotFoundException; + + /** + * 向表插入元组 + * + * @param context 事务context + * @param fieldName 字段名 + * @param newEntry 新的元组 + * @param recordStorage record层访问接口 + * @throws TableNotFoundException 要查询的表不存在 + * @throws TypeIncompatibleException 插入的元组不满足表约束 + */ + void insert(TransactionContext context, String fieldName, Entry newEntry, IRecordStorage recordStorage) throws TableNotFoundException, TypeIncompatibleException; + + /** + * + * + * @param fieldName 字段名 + * @param left 查询区间左端点 + * @param right 查询区间右端点 + * @return 查询到的uuid列表 + * @throws TableNotFoundException 要查询的表不存在 + */ + List search(String fieldName, FieldValue left, FieldValue right) throws TableNotFoundException; + + /** + * 将字符串列表转换成一个元组 + * + * @param values 字符串列表 + * @return 元组的Optional + */ + Optional strToEntry(List values); + + /** + * 将entry转换成字节数组 + * + * @param entry 元组 + * @return 字节数组 + */ + Optional entryToRaw(Entry entry); + + /** + * 将字节数组转换成entry + * + * @param raw 字节数组 + * @return 元组 + */ + Optional parseEntry(byte[] raw); + +} diff --git a/src/main/java/net/kaaass/rumbase/table/TableManager.java b/src/main/java/net/kaaass/rumbase/table/TableManager.java new file mode 100644 index 0000000..b14e27f --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/TableManager.java @@ -0,0 +1,189 @@ +package net.kaaass.rumbase.table; + +import net.kaaass.rumbase.record.IRecordStorage; +import net.kaaass.rumbase.table.exception.TableNotFoundException; +import net.kaaass.rumbase.table.exception.TypeIncompatibleException; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.util.*; + +/** + * 表管理器 + *

+ * ITableManager用于管理表结构, 已经为上层模块提供更加高级和抽象的接口. + *

+ * ITableManager会依赖index模块进行索引, 依赖record模块进行表单数据查找. + *

+ * + * @author @KveinAxel + */ +public class TableManager { + +// private DataItem dataItem; + + private final IRecordStorage recordStorage; + + private Map tableCache; + + private Map> transactionTableCache; + +// private Lock lock; + + public TableManager(IRecordStorage recordStorage) { + this.recordStorage = recordStorage; + this.tableCache = new HashMap<>(); + this.tableCache = new HashMap<>(); + } + + /** + * 开始一个事务 + * + * @param IsRepeatableRead 是否可重复读 + * @return 事务context + */ + TransactionContext begin(boolean IsRepeatableRead) { + return TransactionContext.empty(); + } + + /** + * 提交一个事务 + * + * @param context 事务context + */ + void commit(TransactionContext context) { + // todo commit tx + } + + /** + * 终止一个事务 + * + * @param context 事务context + */ + void abort(TransactionContext context) { + // todo abort tx + } + + /** + * 显示所有的表名 + * + * @return 表名的列表 + */ + List showTables() { + // todo show tables + return new ArrayList<>(); + } + + /** + * 创建一个表 + * + * @param context 事务context + * @param tableName 表名 + * @param fields 表的字段 + */ + void createTable(TransactionContext context, String tableName, List fields) { + // todo create Table + } + + /** + * 向一张表插入数据 + * + * @param context 事务context + * @param tableName 表名 + * @param fieldName 字段名 + * @param newEntry 新的Entry + * @throws TableNotFoundException 不存在该表 + * @throws TypeIncompatibleException 插入的Entry与表字段冲突 + */ + void insert(TransactionContext context, String tableName, String fieldName, Entry newEntry) throws TableNotFoundException, TypeIncompatibleException { + var table = tableCache.get(tableName); + if (table == null) { + throw new TableNotFoundException(1); + } + + table.insert(context, fieldName, newEntry, recordStorage); + } + + /** + * 删除一张表的数据 + * + * @param context 事务context + * @param tableName 表名 + * @param fieldName 字段名 + * @param uuids 待删除的元组的uuid列表 + * @return 被删除的元组数 + * @throws TableNotFoundException 不存在该表 + */ + int delete(TransactionContext context, String tableName, String fieldName, List uuids) throws TableNotFoundException { + var table = tableCache.get(tableName); + if (table == null) { + throw new TableNotFoundException(1); + } + + return uuids.stream() + .map(uuid -> table.delete(context, fieldName, uuid, recordStorage)) + .reduce(0, Integer::sum); + } + + /** + * 更新一张表的数据 + * + * @param context 事务context + * @param tableName 表名 + * @param fieldName 字段名 + * @param newEntries 待更新的entry,键为旧行的uuid,值为新行的entry + * @return 被更新的行数 + * @throws TableNotFoundException 不存在该表 + */ + int update(TransactionContext context, String tableName, String fieldName, Map newEntries) throws TableNotFoundException { + var table = tableCache.get(tableName); + if (table == null) { + throw new TableNotFoundException(1); + } + + return newEntries.entrySet().stream() + .map(e -> table.update(context, fieldName, e.getKey(), e.getValue())) + .reduce(0, Integer::sum); + } + + /** + * 读取一张表的数据 + * + * @param context 事务context + * @param tableName 表名 + * @param fieldName 字段名 + * @param uuids 待查询的uuid的列表 + * @return 查询到的行的Entry + * @throws TableNotFoundException 不存在该表 + */ + List read(TransactionContext context, String tableName, String fieldName, List uuids) throws TableNotFoundException { + var table = tableCache.get(tableName); + if (table == null) { + throw new TableNotFoundException(1); + } + + return table.read(context, fieldName, uuids, recordStorage); + } + + /** + * 查询满足 [left, right) 区间uuid列表 + * 如果 left 为空,则查询 [ begin, right ) + * 如果 right 为空,则查询 [ left, end ) + * 如果 left, right 都为空,查询 [ begin, end ) + * + * @param context 事务context + * @param tableName 表名 + * @param fieldName 字段名 + * @param left 查询区间左端点 + * @param right 查询区间右端点 + * @return 查询到的uuid + * @throws TableNotFoundException 不存在该表 + */ + List search(TransactionContext context, String tableName, String fieldName, FieldValue left, FieldValue right) throws TableNotFoundException { + var table = tableCache.get(tableName); + if (table == null) { + throw new TableNotFoundException(1); + } + + return table.search(fieldName, left, right); + } +} diff --git a/src/main/java/net/kaaass/rumbase/table/exception/TableNotFoundException.java b/src/main/java/net/kaaass/rumbase/table/exception/TableNotFoundException.java new file mode 100644 index 0000000..90f24c9 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/exception/TableNotFoundException.java @@ -0,0 +1,32 @@ +package net.kaaass.rumbase.table.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E3001 关系不存在异常 + *

+ * E3001-1 关系不存在 + * E3001-2 字段不存在 + * E3001-3 视图不存在 + * + * @author @KveinAxel + */ +public class TableNotFoundException extends RumbaseException { + public static final Map REASONS = new HashMap<>(){{ + put(1, "关系不存在"); + put(2, "字段不存在"); + put(3, "视图不存在"); + }}; + + /** + * 关系不存在异常 + * + * @param subId 子错误号 + */ + public TableNotFoundException(int subId) { + super(3001, subId, REASONS.get(subId)); + } +} diff --git a/src/main/java/net/kaaass/rumbase/table/exception/TypeIncompatibleException.java b/src/main/java/net/kaaass/rumbase/table/exception/TypeIncompatibleException.java new file mode 100644 index 0000000..91612ec --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/exception/TypeIncompatibleException.java @@ -0,0 +1,32 @@ +package net.kaaass.rumbase.table.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E3002 类型不匹配异常 + *

+ * E3002-1 字段类型不匹配 + * E3002-2 字段类型不存在 + * E3002-3 Entry类型不匹配 + * + * @author @KveinAxel + */ +public class TypeIncompatibleException extends RumbaseException { + public static final Map REASONS = new HashMap<>(){{ + put(1, "字段类型不匹配"); + put(2, "字段类型不存在"); + put(3, "Entry不匹配"); + }}; + + /** + * 类型不匹配异常 + * + * @param subId 子错误号 + */ + public TypeIncompatibleException(int subId) { + super(3002, subId, REASONS.get(subId)); + } +} diff --git a/src/main/java/net/kaaass/rumbase/table/mock/MockField.java b/src/main/java/net/kaaass/rumbase/table/mock/MockField.java new file mode 100644 index 0000000..b7ccf6b --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/mock/MockField.java @@ -0,0 +1,201 @@ +package net.kaaass.rumbase.table.mock; + +import net.kaaass.rumbase.table.FieldType; +import net.kaaass.rumbase.table.FieldValue; +import net.kaaass.rumbase.table.IField; +import net.kaaass.rumbase.table.ITable; + +import java.io.*; +import java.util.*; + +/** + * Mock字段结构,仅用于测试 + * + * @author @KveinAxel + */ +@Deprecated +public class MockField implements IField { + + private String name; + + private FieldType type; + + private UUID selfUUID; + + private ITable table; + + private UUID index; +// private BTree bTree; + + + /** + * 内存中创建的Mock存储 + */ + private static final Map MOCK_STORAGES = new HashMap<>(); + + private final String mockId; + + private final Map memoryStorage = new HashMap<>(); + + private MockField(String mockId, String name, FieldType fieldType) { + this.mockId = mockId; + index = null; + type = fieldType; + this.name = name; + } + + @Override + public void parseSelf(byte[] raw) { + + } + + @Override + public byte[] persistSelf() { + return new byte[0]; + } + + @Override + public Boolean isIndexed() { + return index != null; + } + + @Override + public FieldType getType() { + return type; + } + + @Override + public int getSize() { + if (type == FieldType.INT) { + return 4; + } else if (type == FieldType.FLOAT) { + return 4; + } + return 4; + } + + @Override + public List searchRange(FieldValue left, FieldValue right) { + if (left.compareTo(right) >= 0) { + return new ArrayList<>(); + } + return null; + } + + @Override + public Optional search(FieldValue value) { + return Optional.empty(); + } + + + @Override + public Optional strToValue(String valStr) { + if (type == FieldType.INT) { + try { + var value = Integer.parseInt(valStr); + var fieldValue = new FieldValue(type); + + fieldValue.setValue(value); + + return Optional.of(fieldValue); + } catch (NumberFormatException e) { + return Optional.empty(); + } + + } else if (type == FieldType.FLOAT) { + try { + var value = Float.parseFloat(valStr); + var fieldValue = new FieldValue(type); + + fieldValue.setValue(value); + + return Optional.of(fieldValue); + } catch (NumberFormatException e) { + return Optional.empty(); + } + } + + return Optional.empty(); + } + + @Override + public Optional valueToRaw(FieldValue value) { + try ( + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(bos) + ) { + if (type == FieldType.INT) { + int intVal = (int) value.getValue(); + + out.writeInt(intVal); + out.close(); + + return Optional.of(bos.toByteArray()); + + } else if (type == FieldType.FLOAT) { + float floatVal = (float) value.getValue(); + + out.writeFloat(floatVal); + out.close(); + + return Optional.of(bos.toByteArray()); + } + + } catch (Exception e) { + return Optional.empty(); + } + return Optional.empty(); + } + + @Override + public Optional rawToValue(byte[] raw) { + try ( + var bais = new ByteArrayInputStream(raw); + var dis = new DataInputStream(bais) + ) { + if (type == FieldType.INT) { + var fieldValue = new FieldValue(FieldType.INT); + + if (raw.length != fieldValue.getType().getSize()) { + return Optional.empty(); + } + + fieldValue.setValue(dis.readInt()); + return Optional.of(fieldValue); + + } else if (type == FieldType.FLOAT) { + var fieldValue = new FieldValue(FieldType.FLOAT); + + if (raw.length != fieldValue.getType().getSize()) { + return Optional.empty(); + } + + fieldValue.setValue(dis.readFloat()); + return Optional.of(fieldValue); + } + + } catch (Exception e) { + return Optional.empty(); + } + return Optional.empty(); + } + + @Override + public String getFieldName() { + return name; + } + + public static MockField ofFile(String filepath, String name, FieldType fieldType) { + var mockId = "file" + filepath; + if (MOCK_STORAGES.containsKey(mockId)) { + return MOCK_STORAGES.get(mockId); + } + var result = new MockField(mockId, name, fieldType); + MOCK_STORAGES.put(mockId, result); + return result; + } + + public String getMockId() { + return mockId; + } +} diff --git a/src/main/java/net/kaaass/rumbase/table/mock/MockTable.java b/src/main/java/net/kaaass/rumbase/table/mock/MockTable.java new file mode 100644 index 0000000..620ccd3 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/mock/MockTable.java @@ -0,0 +1,148 @@ +package net.kaaass.rumbase.table.mock; + +import net.kaaass.rumbase.record.IRecordStorage; +import net.kaaass.rumbase.table.*; +import net.kaaass.rumbase.table.exception.TableNotFoundException; +import net.kaaass.rumbase.table.exception.TypeIncompatibleException; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Mock表结构,仅用于测试 + * + * @author @KveinAxel + */ +@Deprecated +public class MockTable implements ITable { + String tableName; + List fields; + + public MockTable(String tableName, List fields) { + this.tableName = tableName; + this.fields = fields; + } + + @Override + public void parseSelf(byte[] raw) { + + } + + @Override + public byte[] persistSelf() { + return new byte[0]; + } + + @Override + public int delete(TransactionContext context, String fieldName, UUID uuid, IRecordStorage recordStorage) { + recordStorage.delete(context, uuid); + // todo delete index + return 0; + } + + @Override + public int update(TransactionContext context, String fieldName, UUID uuid, Entry newEntry) { + // todo update + return 0; + } + + @Override + public List read(TransactionContext context, String fieldName, List uuids, IRecordStorage recordStorage) throws TableNotFoundException { + fields.stream() + .filter(f -> f.getFieldName().equals(fieldName)) + .findAny() + .orElseThrow(() -> new TableNotFoundException(2)); + + var list = new ArrayList(); + uuids.forEach( + uuid -> recordStorage + .queryOptional(context, uuid) + .flatMap(this::parseEntry) + .ifPresent(list::add) + ); + return list; + } + + @Override + public void insert(TransactionContext context, String fieldName, Entry newEntry, IRecordStorage recordStorage) throws TableNotFoundException, TypeIncompatibleException { + fields.stream() + .filter(f -> f.getFieldName().equals(fieldName)) + .findAny() + .orElseThrow(() -> new TableNotFoundException(2)); + + if (!newEntry.containsKey(fieldName)) { + throw new TypeIncompatibleException(2); + } + + entryToRaw(newEntry).ifPresent((entry) -> recordStorage.insert(context, entry)); + + // todo add to index + } + + @Override + public List search(String fieldName, FieldValue left, FieldValue right) throws TableNotFoundException { + return fields + .stream() + .filter(f -> f.getFieldName().equals(fieldName)) + .findAny() + .orElseThrow(() -> new TableNotFoundException(2)) + .searchRange(left, right); + } + + @Override + public Optional strToEntry(List values) { + var entry = new Entry(); + if (values.size() != fields.size()) { + return Optional.empty(); + } + + var iter = values.iterator(); + fields.forEach(f -> f.strToValue(iter.next()).ifPresent(value -> entry.put(f.getFieldName(), value))); + + return Optional.of(entry); + } + + @Override + public Optional entryToRaw(Entry entry) { + + try ( + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos) + ) { + for (var e : entry.entrySet()) { + var fieldValue = e.getValue(); + if (fieldValue.getValue() == FieldType.INT) { + dos.writeInt((int) fieldValue.getValue()); + } else if (fieldValue.getValue() == FieldType.FLOAT) { + dos.writeFloat((float) fieldValue.getValue()); + } + + } + return Optional.of(baos.toByteArray()); + } catch (Exception e) { + return Optional.empty(); + } + } + + @Override + public Optional parseEntry(byte[] raw) { + AtomicInteger offset = new AtomicInteger(); + var entry = new Entry(); + + var tot = fields.stream().map(IField::getSize).reduce(0, Integer::sum); + if (tot != raw.length) { + return Optional.empty(); + } + + fields.forEach(f -> { + f.rawToValue(Arrays.copyOfRange(raw, offset.get(), offset.get() + f.getSize())) + .ifPresent(value -> entry.put(f.getFieldName(), value)); + offset.addAndGet(f.getSize()); + }); + + return entry.isEmpty() ? Optional.empty() : Optional.of(entry); + } +} diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000..97db8c7 --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootLogger=debug, console +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.Target=System.out +log4j.appender.console.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1.}:%L - %m%n \ No newline at end of file diff --git a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java index 6a48bd1..87c823c 100644 --- a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java @@ -1,6 +1,7 @@ package net.kaaass.rumbase.record; import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; import net.kaaass.rumbase.record.exception.RecordNotFoundException; import net.kaaass.rumbase.transaction.TransactionContext; @@ -15,6 +16,7 @@ * @see net.kaaass.rumbase.record.IRecordStorage * @author kaaass */ +@Slf4j public class IRecordStorageTest extends TestCase { public void testQuery() { @@ -29,7 +31,7 @@ public void testQuery() { storage.query(context, UUID.randomUUID()); fail("unknown uuid should get exception"); } catch (RecordNotFoundException e) { - e.printStackTrace(); + log.error("Exception expected: ", e); } } diff --git a/src/test/java/net/kaaass/rumbase/table/IFieldTest.java b/src/test/java/net/kaaass/rumbase/table/IFieldTest.java new file mode 100644 index 0000000..961d8ee --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/table/IFieldTest.java @@ -0,0 +1,120 @@ +package net.kaaass.rumbase.table; + +import junit.framework.TestCase; +import net.kaaass.rumbase.table.mock.MockField; + +import java.util.ArrayList; +import java.util.Arrays; + +import static org.junit.Assert.assertArrayEquals; + +/** + * 字段结构测试 + * + * @author @KveinAxel + * @see net.kaaass.rumbase.table.IField + */ +public class IFieldTest extends TestCase { + public void testStrToValue() { + + IField field = MockField.ofFile("test_str2value_int", "id", FieldType.INT); + var value = field.strToValue("1"); + assertTrue("compatible value should parse successfully.", value.isPresent()); + assertEquals("compatible value should be parsed in a true way", 1, value.get().getValue()); + + IField field2 = MockField.ofFile("test_str2value_float", "id", FieldType.FLOAT); + var value2 = field2.strToValue("1.2"); + assertTrue("compatible value should be successful to parse.", value2.isPresent()); + assertEquals("compatible value should be parsed in a proper way", 1.2f, (float) value2.get().getValue()); + + IField field3 = MockField.ofFile("test_str2value_failed", "id", FieldType.INT); + var value3 = field3.strToValue("a"); + assertTrue("incompatible value should fail to parse", value3.isEmpty()); + } + + public void testValueToRaw() { + FieldValue fieldValue = new FieldValue<>(FieldType.INT); + fieldValue.setValue(1); + IField field = MockField.ofFile("test_value2raw_int", "id", FieldType.INT); + var raw = field.valueToRaw(fieldValue); + assertTrue("compatible value should be successful to transform.", raw.isPresent()); + assertArrayEquals("compatible value should be transform in a proper way", raw.get(), new byte[]{0x00, 0x00, 0x00, 0x01}); + + FieldValue fieldValue2 = new FieldValue<>(FieldType.FLOAT); + fieldValue2.setValue(1.2f); + IField field2 = MockField.ofFile("test_value2raw_float", "id", FieldType.FLOAT); + var raw2 = field2.valueToRaw(fieldValue2); + assertTrue("compatible value should be successful to transform.", raw2.isPresent()); + assertArrayEquals("compatible value should be transform in a proper way", raw2.get(), new byte[]{0x3f, (byte) 0x99, (byte) 0x99, (byte) 0x9a}); + + } + + public void testRawToValue() { + var bytes = new byte[]{0x00, 0x00, 0x00, 0x01}; + IField field = MockField.ofFile("test_raw2value_int", "id", FieldType.INT); + var value = field.rawToValue(bytes); + assertTrue("compatible value should be successful to transform.", value.isPresent()); + assertEquals("compatible value should be transform in a proper way", 1, (int) value.get().getValue()); + + var bytes2 = new byte[]{0x3f, (byte) 0x99, (byte) 0x99, (byte) 0x9a}; + IField field2 = MockField.ofFile("test_raw2value_float", "id", FieldType.FLOAT); + var value2 = field2.rawToValue(bytes2); + assertTrue("compatible value should be successful to transform.", value2.isPresent()); + assertEquals("compatible value should be transform in a proper way", 1.2f, (float) value2.get().getValue()); + + var bytes3 = new byte[]{0x00, 0x00, 0x00, 0x00, 0x01}; + IField field3 = MockField.ofFile("test_raw2value_failed", "id", FieldType.INT); + var value3 = field.rawToValue(bytes3); + assertTrue("incompatible value should be failed to transform.", value3.isEmpty()); + + } + + public void testSearch() { + + } + + public void testSearchRange() { + var fieldValue = new FieldValue(FieldType.INT); + var fieldValue2 = new FieldValue(FieldType.INT); + fieldValue.setValue(2); + fieldValue2.setValue(1); + IField field = MockField.ofFile("test_search_range_int", "id", FieldType.INT); + + var res = field.searchRange(fieldValue, fieldValue2); + assertEquals("bad seq can only get a 0 size list", 0, res.size()); + var res2 = field.searchRange(fieldValue2, fieldValue); + assertEquals("good seq can get the proper answer", null, res2); + + var fieldValue3 = new FieldValue(FieldType.FLOAT); + var fieldValue4 = new FieldValue(FieldType.FLOAT); + fieldValue3.setValue(2.1f); + fieldValue4.setValue(1.1f); + IField field2 = MockField.ofFile("test_search_range_float", "id", FieldType.INT); + + var res3 = field2.searchRange(fieldValue3, fieldValue4); + assertEquals("bad seq can only get a 0 size list", 0, res3.size()); + var res4 = field2.searchRange(fieldValue4, fieldValue3); + assertEquals("good seq can get the proper answer", null, res4); + + var fieldValue5 = new FieldValue(FieldType.INT); + var fieldValue6 = new FieldValue(FieldType.FLOAT); + fieldValue5.setValue(2); + fieldValue6.setValue(1.1f); + IField field3 = MockField.ofFile("test_search_range_failed", "id", FieldType.INT); + + var res5 = field3.searchRange(fieldValue5, fieldValue6); + assertEquals("incompatible can only get a 0 size list", 0, res5.size()); + var res6 = field3.searchRange(fieldValue6, fieldValue5); + assertEquals("incompatible can only get a 0 size list", 0, res6.size()); + + + } + + public void testParseSelf() { + // todo + } + + public void testPersistSelf() { + // todo + } +} diff --git a/src/test/java/net/kaaass/rumbase/table/ITableTest.java b/src/test/java/net/kaaass/rumbase/table/ITableTest.java new file mode 100644 index 0000000..f1ff634 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/table/ITableTest.java @@ -0,0 +1,102 @@ +package net.kaaass.rumbase.table; + +import junit.framework.TestCase; +import net.kaaass.rumbase.table.mock.MockField; +import net.kaaass.rumbase.table.mock.MockTable; + +import java.util.ArrayList; + +/** + * 表结构测试 + * + * @author @KveinAxel + * @see net.kaaass.rumbase.table.ITable + */ +public class ITableTest extends TestCase { + public void testParseSelf() { + // todo + } + + public void testPersistSelf() { + // todo + } + + public void testDelete() { + // todo + } + + public void testUpdate() { + // todo + } + + public void testRead() { + // todo + } + + public void testInsert() { + // todo + } + + public void testSearch() { + // todo + } + + public void testStrToEntry() { + var str = new ArrayList(); + str.add("1"); + str.add("1.2"); + str.add("3"); + + var fields = new ArrayList(); + fields.add(MockField.ofFile("id", "id", FieldType.INT)); + fields.add(MockField.ofFile("f", "f", FieldType.FLOAT)); + fields.add(MockField.ofFile("i", "i", FieldType.INT)); + var table = new MockTable("mockTable", fields); + + var res = table.strToEntry(str); + assertTrue("compatible fields can be transformed", res.isPresent()); + var entry = res.get(); + assertEquals("compatible fields can be transformed properly", entry.get("id").getValue(), 1); + assertEquals("compatible fields can be transformed properly", entry.get("f").getValue(), 1.2f); + assertEquals("compatible fields can be transformed properly", entry.get("i").getValue(), 3); + assertNull("dummy fields can not exist", entry.get("dummy")); + + str.add("4"); + var res2 = table.strToEntry(str); + assertTrue("incompatible type can't be transformed properly", res2.isEmpty()); + } + + public void testParseEntry() { + var fields = new ArrayList(); + fields.add(MockField.ofFile("id", "id", FieldType.INT)); + fields.add(MockField.ofFile("f", "f", FieldType.FLOAT)); + fields.add(MockField.ofFile("i", "i", FieldType.INT)); + var table = new MockTable("mockTable", fields); + + var bytes = new byte[]{ + 0x00, 0x00, 0x00, 0x01, 0x3f, + (byte) 0x99, (byte) 0x99, (byte) 0x9a, + 0x00, 0x00, 0x00, 0x03 + }; + + var res = table.parseEntry(bytes); + assertTrue("compatible fields can be transformed properly", res.isPresent()); + var entry = res.get(); + assertEquals("compatible fields can be transformed properly", 1, entry.get("id").getValue()); + assertEquals("compatible fields can be transformed properly", 1.2f, entry.get("f").getValue()); + assertEquals("compatible fields can be transformed properly", 3, entry.get("i").getValue()); + + var bytes2 = new byte[]{ + 0x00, 0x00, 0x00, 0x01, 0x3f, + (byte) 0x99, (byte) 0x99, (byte) 0x9a, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x03 + }; + var res2 = table.parseEntry(bytes2); + assertTrue("dummy fields can not exist", res2.isEmpty()); + + + } + + +} diff --git a/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java b/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java new file mode 100644 index 0000000..65c894a --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java @@ -0,0 +1,63 @@ +package net.kaaass.rumbase.table; + +import junit.framework.TestCase; + +/** + * 表管理器的测试 + * + * @see net.kaaass.rumbase.table.TableManager + * @author @KveinAxel + */ +public class TableManagerTest extends TestCase { + + public void testBegin() { + // todo + } + + public void testCommit() { + // todo + + } + + public void testAbort() { + // todo + + } + + public void testShowTables() { + // todo + + } + + public void testCreateTable() { + // todo + + } + + public void testInsert() { + // todo + + } + + public void testDelete() { + // todo + + } + + public void testUpdate() { + // todo + + } + + public void testRead() { + // todo + + } + + public void testSearch() { + // todo + + } + + +} From ea7d61eda2684cde4074b99a270644744b3502a9 Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Sun, 10 Jan 2021 15:39:53 +0800 Subject: [PATCH 03/18] =?UTF-8?q?[+]=E5=AE=8C=E6=88=90=E5=90=84=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E6=8E=A5=E5=8F=A3=E3=80=81Mock=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 增加错误基类、简单的记录接口 * 增加.gitignore * 完成记录的mock * 增加记录接口的简易单元测试 * 引入Slf4j、log4j、jbbp外部库 * [+]add table mock and unit test * 增加Travis配置 * fix: transform java14 to java11 * 更新Gradle配置以允许Scan ToS * 删除冗余Travis配置 * README增加Travis Badge * 增加UTF-8 gradle配置 * 对于数据项管理的mock和测试部分 * 修改测试用例的文件名 * index mock * 增加事务的Mock * testPage * index mock plus test * 对于数据项管理的mock和测试部分的修改 * testPage1.01 * dataitem模块修改建议 * 修正编码风格 * 对于数据项管理的mock和测试部分 * 对于数据项管理的mock和测试部分 * Index审计、修改建议 * 增加JBBP的使用示例 * page模块审计、修改建议 * 删除table模块,更改record模块 * recovery模块审计 * transaction模块审计 * 对于日志恢复的接口和测试 * 完善事务模块接口 * testPage1.02 * testPage1.03 * 复审recovery,tx * 格式化代码 * 更新README Co-authored-by: Kevin Axel Manjaro Co-authored-by: Kevin Axel <41867249+KveinAxel@users.noreply.github.com> Co-authored-by: xiaoxineryi <529086017@qq.com> Co-authored-by: DctorWei1314 <61504793+DctorWei1314@users.noreply.github.com> Co-authored-by: Criterionist <1229089076@qq.com> Co-authored-by: XuanLaoYee <1115810634@qq.com> --- .gitignore | 2 + README.md | 10 +- build.gradle | 4 + .../kaaass/rumbase/dataitem/IItemStorage.java | 82 +++++++ .../kaaass/rumbase/dataitem/ItemManager.java | 60 +++++ .../dataitem/exception/FileException.java | 22 ++ .../dataitem/exception/UUIDException.java | 23 ++ .../dataitem/mock/MockItemStorage.java | 155 +++++++++++++ .../java/net/kaaass/rumbase/index/Index.java | 125 +++++++++++ .../java/net/kaaass/rumbase/index/Pair.java | 14 ++ .../exception/IndexAlreadyExistException.java | 36 +++ .../exception/IndexNotFoundException.java | 35 +++ .../rumbase/index/mock/MockBtreeIndex.java | 57 +++++ .../rumbase/index/mock/MockIterator.java | 74 +++++++ .../java/net/kaaass/rumbase/page/Page.java | 99 +++++++++ .../net/kaaass/rumbase/page/PageManager.java | 25 +++ .../net/kaaass/rumbase/page/PageStorage.java | 22 ++ .../page/exception/BufferExeception.java | 28 +++ .../rumbase/page/exception/FileException.java | 34 +++ .../rumbase/page/exception/PageException.java | 28 +++ .../kaaass/rumbase/page/mock/MockBuffer.java | 71 ++++++ .../kaaass/rumbase/page/mock/MockPage.java | 54 +++++ .../rumbase/page/mock/MockPageStorage.java | 55 +++++ .../kaaass/rumbase/record/IRecordStorage.java | 24 +- .../exception/RecordNotFoundException.java | 4 +- .../record/mock/MockRecordStorage.java | 19 +- .../rumbase/recovery/IRecoveryStorage.java | 63 ++++++ .../rumbase/recovery/RecoveryManager.java | 21 ++ .../recovery/mock/MockRecoveryStorage.java | 73 +++++++ .../java/net/kaaass/rumbase/table/Entry.java | 13 -- .../net/kaaass/rumbase/table/FieldType.java | 16 -- .../net/kaaass/rumbase/table/FieldValue.java | 56 ----- .../java/net/kaaass/rumbase/table/IField.java | 100 --------- .../java/net/kaaass/rumbase/table/ITable.java | 115 ---------- .../kaaass/rumbase/table/TableManager.java | 189 ---------------- .../exception/TableNotFoundException.java | 32 --- .../exception/TypeIncompatibleException.java | 32 --- .../kaaass/rumbase/table/mock/MockField.java | 201 ----------------- .../kaaass/rumbase/table/mock/MockTable.java | 148 ------------- .../kaaass/rumbase/transaction/LockTable.java | 37 ++++ .../rumbase/transaction/LockTableManager.java | 37 ++++ .../transaction/TransactionContext.java | 67 +++++- .../transaction/TransactionIsolation.java | 42 ++++ .../transaction/TransactionManager.java | 28 +++ .../transaction/TransactionStatus.java | 42 ++++ .../exception/DeadlockException.java | 30 +++ .../transaction/mock/MockLockTable.java | 29 +++ .../mock/MockTransactionContext.java | 100 +++++++++ .../mock/MockTransactionManager.java | 81 +++++++ .../java/net/kaaass/rumbase/JBBPDemo.java | 84 +++++++ .../rumbase/dataitem/IItemStorageTest.java | 205 ++++++++++++++++++ .../net/kaaass/rumbase/index/IndexTest.java | 127 +++++++++++ .../kaaass/rumbase/page/PageStorageTest.java | 43 ++++ .../net/kaaass/rumbase/page/PageTest.java | 83 +++++++ .../rumbase/record/IRecordStorageTest.java | 4 +- .../rumbase/recovery/IRecoveryTest.java | 35 +++ .../net/kaaass/rumbase/table/IFieldTest.java | 120 ---------- .../net/kaaass/rumbase/table/ITableTest.java | 102 --------- .../rumbase/table/TableManagerTest.java | 63 ------ .../transaction/TransactionContextTest.java | 99 +++++++++ 60 files changed, 2460 insertions(+), 1219 deletions(-) create mode 100644 src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java create mode 100644 src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java create mode 100644 src/main/java/net/kaaass/rumbase/dataitem/exception/FileException.java create mode 100644 src/main/java/net/kaaass/rumbase/dataitem/exception/UUIDException.java create mode 100644 src/main/java/net/kaaass/rumbase/dataitem/mock/MockItemStorage.java create mode 100644 src/main/java/net/kaaass/rumbase/index/Index.java create mode 100644 src/main/java/net/kaaass/rumbase/index/Pair.java create mode 100644 src/main/java/net/kaaass/rumbase/index/exception/IndexAlreadyExistException.java create mode 100644 src/main/java/net/kaaass/rumbase/index/exception/IndexNotFoundException.java create mode 100644 src/main/java/net/kaaass/rumbase/index/mock/MockBtreeIndex.java create mode 100644 src/main/java/net/kaaass/rumbase/index/mock/MockIterator.java create mode 100644 src/main/java/net/kaaass/rumbase/page/Page.java create mode 100644 src/main/java/net/kaaass/rumbase/page/PageManager.java create mode 100644 src/main/java/net/kaaass/rumbase/page/PageStorage.java create mode 100644 src/main/java/net/kaaass/rumbase/page/exception/BufferExeception.java create mode 100644 src/main/java/net/kaaass/rumbase/page/exception/FileException.java create mode 100644 src/main/java/net/kaaass/rumbase/page/exception/PageException.java create mode 100644 src/main/java/net/kaaass/rumbase/page/mock/MockBuffer.java create mode 100644 src/main/java/net/kaaass/rumbase/page/mock/MockPage.java create mode 100644 src/main/java/net/kaaass/rumbase/page/mock/MockPageStorage.java create mode 100644 src/main/java/net/kaaass/rumbase/recovery/IRecoveryStorage.java create mode 100644 src/main/java/net/kaaass/rumbase/recovery/RecoveryManager.java create mode 100644 src/main/java/net/kaaass/rumbase/recovery/mock/MockRecoveryStorage.java delete mode 100644 src/main/java/net/kaaass/rumbase/table/Entry.java delete mode 100644 src/main/java/net/kaaass/rumbase/table/FieldType.java delete mode 100644 src/main/java/net/kaaass/rumbase/table/FieldValue.java delete mode 100644 src/main/java/net/kaaass/rumbase/table/IField.java delete mode 100644 src/main/java/net/kaaass/rumbase/table/ITable.java delete mode 100644 src/main/java/net/kaaass/rumbase/table/TableManager.java delete mode 100644 src/main/java/net/kaaass/rumbase/table/exception/TableNotFoundException.java delete mode 100644 src/main/java/net/kaaass/rumbase/table/exception/TypeIncompatibleException.java delete mode 100644 src/main/java/net/kaaass/rumbase/table/mock/MockField.java delete mode 100644 src/main/java/net/kaaass/rumbase/table/mock/MockTable.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/LockTable.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/LockTableManager.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/TransactionIsolation.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/TransactionManager.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/TransactionStatus.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/exception/DeadlockException.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/mock/MockLockTable.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionContext.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionManager.java create mode 100644 src/test/java/net/kaaass/rumbase/JBBPDemo.java create mode 100644 src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java create mode 100644 src/test/java/net/kaaass/rumbase/index/IndexTest.java create mode 100644 src/test/java/net/kaaass/rumbase/page/PageStorageTest.java create mode 100644 src/test/java/net/kaaass/rumbase/page/PageTest.java create mode 100644 src/test/java/net/kaaass/rumbase/recovery/IRecoveryTest.java delete mode 100644 src/test/java/net/kaaass/rumbase/table/IFieldTest.java delete mode 100644 src/test/java/net/kaaass/rumbase/table/ITableTest.java delete mode 100644 src/test/java/net/kaaass/rumbase/table/TableManagerTest.java create mode 100644 src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java diff --git a/.gitignore b/.gitignore index f32518d..4540085 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +testFile + # IntelliJ IDEA .idea *.iws diff --git a/README.md b/README.md index 9bc6ad7..7ee4f5d 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ Java构建的高性能SQL关系型数据库。 | Query Parse Module | SQL 语句解析 | @KAAAsS | parse | | Query Execution Module | 查询执行、优化 | @KveinAxel | query | | Table Management Module | 系统内数据库、表管理 | @KveinAxel | table | -| Indexing Module | 索引结构,使用 B+ 树 | | index | +| Indexing Module | 索引结构,使用 B+ 树 | @DoctorWei1314 | index | | Record Module | 记录管理,实现 MVCC | @KAAAsS | record | -| Transaction Module | 实现事务的管理与 2PL | | transaction | -| Data Item Module | 数据项管理 | | dataitem | -| Recovery Log Module | 日志与恢复管理 | | recovery | -| Page Caching Module | 缓冲与页管理 | | page | \ No newline at end of file +| Transaction Module | 实现事务的管理与 2PL | @criki | transaction | +| Data Item Module | 数据项管理 | @kaito | dataitem | +| Recovery Log Module | 日志与恢复管理 | @kaito | recovery | +| Page Caching Module | 缓冲与页管理 | @XuanLaoYee | page | \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9a93815..31aac2b 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,10 @@ plugins { id 'java' } +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + group 'net.kaaass' version '1.0-SNAPSHOT' diff --git a/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java b/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java new file mode 100644 index 0000000..2f57144 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java @@ -0,0 +1,82 @@ +package net.kaaass.rumbase.dataitem; + +import net.kaaass.rumbase.dataitem.exception.UUIDException; + +import java.util.List; + +/** + * 数据项管理接口 + * + * @author kaito + */ +public interface IItemStorage { + /** + * 插入数据项 + * + * @param item 数据项 + * @return 返回数据项的UUID + */ + long insertItem(byte[] item); + + /** + * 插入一个有UUID的数据项,唯一使用的地方是日志恢复时使用 + *

+ * 如果数据项已经存在,就将数据更新在已存在的数据项所在空间上; + * 如果数据项不存在,则以此UUID插入数据项 + *

+ * + * @param item 数据项 + * @param uuid 编号 + */ + void insertItemWithUuid(byte[] item, long uuid); + + /** + * 通过UUID查询数据项 + * + * @param uuid 编号 + * @return 数据项 + * @throws UUIDException UUID找不到的异常 + */ + byte[] queryItemByUuid(long uuid) throws UUIDException; + + + /** + * 列出页中所有的记录 + * + * @param pageId 页号 + * @return list的一堆数据项 + */ + List listItemByPageId(int pageId); + + /** + * 根据UUID更新数据项 + * + * @param uuid 编号 + * @param item 数据项 + * @throws UUIDException 没有找到对应UUID的异常 + */ + void updateItemByUuid(long uuid, byte[] item) throws UUIDException; + + /** + * 获得数据项存储的元数据(可以用于头) + * + * @return 元数据 + */ + byte[] getMetadata(); + + /** + * 设置数据项存储的元数据(可以用于头) + * + * @param metadata 头信息 + */ + void setMetadata(byte[] metadata); + + /** + * 清理多余的数据项,空间清理时使用。 + * + * @param uuids 数据项UUID的编号列表 + */ + void removeItems(List uuids); +} + + diff --git a/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java b/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java new file mode 100644 index 0000000..ff4064d --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java @@ -0,0 +1,60 @@ +package net.kaaass.rumbase.dataitem; + +import net.kaaass.rumbase.dataitem.exception.FileException; +import net.kaaass.rumbase.dataitem.mock.MockItemStorage; + +import java.util.HashMap; +import java.util.Map; + +/** + * 数据项管理类 + * + * @author kaito + */ + +public class ItemManager { + + static Map maps = new HashMap<>(); + + /** + * 通过文件名解析得到数据项管理器 + * + * @param fileName 文件名 + * @return 数据项管理器,用于管理数据项 + */ + public static IItemStorage fromFile(String fileName) throws FileException { + String errorFileName = "error.db"; + if (errorFileName.equals(fileName)) { + throw new FileException(2); + } + + if (maps.containsKey(fileName)) { + return maps.get(fileName); + } else { + IItemStorage iItemStorage = MockItemStorage.ofFile(fileName); + maps.put(fileName, iItemStorage); + return iItemStorage; + } + } + + /** + * 新建一个数据库,并且将上层提供的头信息写入。 + * + * @param fileName 文件名 + * @param metadata 上层提供的表头信息 + * @return 数据项管理器 + * @throws FileException 想新建的文件已经存在的异常 + */ + public static IItemStorage createFile(String fileName, byte[] metadata) throws FileException { + // 如果文件已经存在,那么就抛出文件已存在异常 + if (maps.containsKey(fileName)) { + throw new FileException(1); + } else { + // 若文件不存在,则创建文件。 + IItemStorage iItemStorage = MockItemStorage.ofNewFile(fileName, metadata); + maps.put(fileName, iItemStorage); + return iItemStorage; + } + + } +} diff --git a/src/main/java/net/kaaass/rumbase/dataitem/exception/FileException.java b/src/main/java/net/kaaass/rumbase/dataitem/exception/FileException.java new file mode 100644 index 0000000..429a233 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/dataitem/exception/FileException.java @@ -0,0 +1,22 @@ +package net.kaaass.rumbase.dataitem.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * 在创建、打开数据库时 + * + * @author kaito + */ +public class FileException extends RumbaseException { + public static final Map REASONS = new HashMap<>() {{ + put(1, "要创建的文件已存在"); + put(2, "查找的文件不存在"); + }}; + + public FileException(int subID) { + super(6001, subID, REASONS.get(subID)); + } +} diff --git a/src/main/java/net/kaaass/rumbase/dataitem/exception/UUIDException.java b/src/main/java/net/kaaass/rumbase/dataitem/exception/UUIDException.java new file mode 100644 index 0000000..e551ae1 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/dataitem/exception/UUIDException.java @@ -0,0 +1,23 @@ +package net.kaaass.rumbase.dataitem.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * 关于Uuid的相关错误 + * + * @author kaito + */ +public class UUIDException extends RumbaseException { + + public static final Map REASONS = new HashMap<>() {{ + put(1, "要插入的UUID已存在"); + put(2, "要查找的UUID不存在"); + }}; + + public UUIDException(int subID) { + super(7001, subID, REASONS.get(subID)); + } +} diff --git a/src/main/java/net/kaaass/rumbase/dataitem/mock/MockItemStorage.java b/src/main/java/net/kaaass/rumbase/dataitem/mock/MockItemStorage.java new file mode 100644 index 0000000..a4f555a --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/dataitem/mock/MockItemStorage.java @@ -0,0 +1,155 @@ +package net.kaaass.rumbase.dataitem.mock; + +import lombok.Data; +import net.kaaass.rumbase.dataitem.IItemStorage; +import net.kaaass.rumbase.dataitem.exception.UUIDException; + +import java.util.*; + +/** + * 数据项管理的Mock,进行数据项的增删改查 + * + * @author kaito + */ +@Data +public class MockItemStorage implements IItemStorage { + + private String fileName; + /** + * 当前第一个空闲的页,用于插入时作为起始页来进行操作。 + */ + private int tempFreePage; + /** + * 表信息头对应的UUID + */ + private long headerUuid; + + + /** + * 模拟的数据信息 + */ + private Map maps; + /** + * 模拟的文件头信息 + */ + private byte[] meta; + + /** + * 模拟的构造函数 + * + * @param fileName 文件名 + * @param tempFreePage 当前第一个空闲页号 + * @param headerUuid 头信息对应UUID + */ + public MockItemStorage(String fileName, int tempFreePage, long headerUuid) { + this.fileName = fileName; + this.tempFreePage = tempFreePage; + this.headerUuid = headerUuid; + maps = new HashMap<>(); + meta = new byte[1024]; + } + + public Map getMap() { + return maps; + } + + /** + * 通过已存在的文件名解析得到数据项管理器 + * + * @param fileName 文件名 + * @return 数据项管理器 + */ + + public static IItemStorage ofFile(String fileName) { + // TODO: 实际通过文件名建立数据项管理器,还需要获取到文件头信息来解析得到可以插入的起始页 + return new MockItemStorage(fileName, 0, 0); + } + + /** + * 新建数据库,并写入表头 + * + * @param fileName 文件名 + * @param tableHeader 表头信息 + * @return 返回数据项管理器 + */ + public static IItemStorage ofNewFile(String fileName, byte[] tableHeader) { + // TODO: 因为是新建的文件,所以需要给文件头写入头信息数据。 + return new MockItemStorage(fileName, 0, 0); + } + + + @Override + public long insertItem(byte[] item) { + Random ran = new Random(); + long r = ran.nextLong(); + maps.put(r, item); + return r; + } + + @Override + public void insertItemWithUuid(byte[] item, long uuid) { + maps.put(uuid, item); + } + + @Override + public byte[] queryItemByUuid(long uuid) throws UUIDException { + if (maps.containsKey(uuid)) { + return maps.get(uuid); + } else { + throw new UUIDException(2); + } + } + + @Override + public List listItemByPageId(int pageId) { + return new ArrayList<>(maps.values()); + } + + @Override + public void updateItemByUuid(long uuid, byte[] item) throws UUIDException { + if (maps.containsKey(uuid)) { + maps.put(uuid, item); + } else { + throw new UUIDException(2); + } + } + + @Override + public byte[] getMetadata() { + return meta; + } + + @Override + public void setMetadata(byte[] metadata) { + this.meta = metadata; + } + + + @Override + public void removeItems(List uuids) { + System.out.println("已经清除文件对应uuid的信息"); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MockItemStorage that = (MockItemStorage) o; + return tempFreePage == that.tempFreePage && + headerUuid == that.headerUuid && + Objects.equals(fileName, that.fileName) && + Objects.equals(maps, that.maps) && + Arrays.equals(meta, that.meta); + } + + @Override + public int hashCode() { + int result = Objects.hash(fileName, tempFreePage, headerUuid, maps); + result = 31 * result + Arrays.hashCode(meta); + return result; + } +} diff --git a/src/main/java/net/kaaass/rumbase/index/Index.java b/src/main/java/net/kaaass/rumbase/index/Index.java new file mode 100644 index 0000000..28f90ff --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/index/Index.java @@ -0,0 +1,125 @@ +package net.kaaass.rumbase.index; + +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.index.exception.IndexNotFoundException; +import net.kaaass.rumbase.index.mock.MockBtreeIndex; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * 索引 + * + * @author DoctorWei1314 + */ + +public interface Index extends Iterable { + /** + * 通过文件名拿到索引,如果不存在,则抛出异常 + * + * @param indexFileName + * @return + * @throws IndexNotFoundException + */ + static Index getIndex(String indexFileName) throws IndexNotFoundException { + if (MockBtreeIndex.MOCK_BTREE_INDEX_MAP.get(indexFileName) == null) { + throw new IndexNotFoundException(1); + } + return MockBtreeIndex.MOCK_BTREE_INDEX_MAP.get(indexFileName); + } + + /** + * 查看索引文件是否存在 + * + * @param indexFileName + * @return + */ + static boolean exists(String indexFileName) { + return MockBtreeIndex.MOCK_BTREE_INDEX_MAP.get(indexFileName) != null; + } + + /** + * 创建一个空的索引 + * + * @param indexFileName + * @return + * @throws IndexAlreadyExistException + */ + static Index createEmptyIndex(String indexFileName) throws IndexAlreadyExistException { + if (MockBtreeIndex.MOCK_BTREE_INDEX_MAP.get(indexFileName) == null) { + MockBtreeIndex.MOCK_BTREE_INDEX_MAP.put(indexFileName, new MockBtreeIndex()); + } else { + throw new IndexAlreadyExistException(1); + } + return MockBtreeIndex.MOCK_BTREE_INDEX_MAP.get(indexFileName); + } + + /** + * 对索引节点的uuid值进行批量替换 + * + * @param uuidMap UUID替换表,键为旧UUID,值为替换的目标UUID + */ + void replace(Map uuidMap); + + /** + * 向索引中加入键值对“数据Hash-UUID” + * + * @param dataHash 数据Hash + * @param uuid 对应记录UUID + */ + void insert(long dataHash, long uuid); + + /** + * 查询所有数据Hash对应的UUID + * + * @param dataHash 数据Hash + * @return 返回所有对应的UUID,若不存在则为空 + */ + List query(long dataHash); + + /** + * 查找第一个键为dataHash的键值对 + *

+ * 如 dataHash = 4,当前键值有: + *

+     * 3 3 [4 4] 5 5 7 7
+     *      ↑迭代器指向
+     * 
+     *
+     * @param dataHash 数据Hash
+     * @return 指向该键值对的迭代器
+     */
+    Iterator findFirst(long dataHash);
+
+    /**
+     * 查找键为dataHash的上界
+     * 

+ * 如 dataHash = 4,当前键值有: + *

+     * 3 3 [4 4] 5 5 7 7
+     *           ↑迭代器指向
+     * 
+     *
+     * @param dataHash 数据Hash
+     * @return 指向该键值对的迭代器
+     */
+    Iterator findUpperbound(long dataHash);
+
+    /**
+     * 返回第一个键值对的迭代器
+     *
+     * @return 指向该键值对的迭代器
+     */
+    Iterator findFirst();
+
+    /**
+     * 返回遍历索引的迭代器
+     *
+     * @return 第一个键值对的迭代器
+     */
+    @Override
+    default Iterator iterator() {
+        return findFirst();
+    }
+}
diff --git a/src/main/java/net/kaaass/rumbase/index/Pair.java b/src/main/java/net/kaaass/rumbase/index/Pair.java
new file mode 100644
index 0000000..b426567
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/index/Pair.java
@@ -0,0 +1,14 @@
+package net.kaaass.rumbase.index;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * TODO 文档
+ */
+@Data
+@AllArgsConstructor
+public class Pair {
+    private long key;
+    private long uuid;
+}
diff --git a/src/main/java/net/kaaass/rumbase/index/exception/IndexAlreadyExistException.java b/src/main/java/net/kaaass/rumbase/index/exception/IndexAlreadyExistException.java
new file mode 100644
index 0000000..f0fd104
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/index/exception/IndexAlreadyExistException.java
@@ -0,0 +1,36 @@
+package net.kaaass.rumbase.index.exception;
+
+import net.kaaass.rumbase.exception.RumbaseException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO 文档
+ */
+public class IndexAlreadyExistException extends RumbaseException {
+
+    public static final Map REASONS = new HashMap<>() {{
+        put(1, "索引已经存在");
+    }};
+
+    /**
+     * 构造Rumbase异常
+     *
+     * @param mainId 主错误号
+     * @param subId  子错误号
+     * @param reason 错误原因
+     */
+    private IndexAlreadyExistException(int mainId, int subId, String reason) {
+        super(mainId, subId, reason);
+    }
+
+    /**
+     * 索引不存在
+     *
+     * @param subId 子错误号
+     */
+    public IndexAlreadyExistException(int subId) {
+        super(9002, subId, REASONS.get(subId));
+    }
+}
diff --git a/src/main/java/net/kaaass/rumbase/index/exception/IndexNotFoundException.java b/src/main/java/net/kaaass/rumbase/index/exception/IndexNotFoundException.java
new file mode 100644
index 0000000..c485f0e
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/index/exception/IndexNotFoundException.java
@@ -0,0 +1,35 @@
+package net.kaaass.rumbase.index.exception;
+
+import net.kaaass.rumbase.exception.RumbaseException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO 文档
+ */
+public class IndexNotFoundException extends RumbaseException {
+    /**
+     * 构造Rumbase异常
+     *
+     * @param mainId 主错误号
+     * @param subId  子错误号
+     * @param reason 错误原因
+     */
+    public IndexNotFoundException(int mainId, int subId, String reason) {
+        super(mainId, subId, reason);
+    }
+
+    public static final Map REASONS = new HashMap<>() {{
+        put(1, "索引不存在");
+    }};
+
+    /**
+     * 索引不存在
+     *
+     * @param subId 子错误号
+     */
+    public IndexNotFoundException(int subId) {
+        super(9001, subId, REASONS.get(subId));
+    }
+}
diff --git a/src/main/java/net/kaaass/rumbase/index/mock/MockBtreeIndex.java b/src/main/java/net/kaaass/rumbase/index/mock/MockBtreeIndex.java
new file mode 100644
index 0000000..4c04748
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/index/mock/MockBtreeIndex.java
@@ -0,0 +1,57 @@
+package net.kaaass.rumbase.index.mock;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import net.kaaass.rumbase.index.Index;
+import net.kaaass.rumbase.index.Pair;
+
+import java.util.*;
+
+@RequiredArgsConstructor
+public class MockBtreeIndex implements Index {
+    /**
+     * 内存中创建的MockBtreeIndex
+     */
+    public static final Map MOCK_BTREE_INDEX_MAP = new HashMap<>();
+
+    @Getter
+    private HashMap> hashMap = new HashMap<>();
+
+    @Override
+    public void replace(Map uuidMap) {
+        var values = hashMap.values();
+        for (List value : values) {
+            for (Long old : value) {
+                if (uuidMap.containsKey(old)) {
+                    value.remove(old);
+                    value.add(uuidMap.get(old));
+                }
+            }
+        }
+    }
+
+    @Override
+    public void insert(long dataHash, long uuid) {
+        hashMap.computeIfAbsent(dataHash, k -> new ArrayList<>()).add(uuid);
+    }
+
+    @Override
+    public List query(long dataHash) {
+        return hashMap.get(dataHash) == null ? new LinkedList<>() : hashMap.get(dataHash);
+    }
+
+    @Override
+    public Iterator findFirst(long keyHash) {
+        return new MockIterator(this, keyHash, true);
+    }
+
+    @Override
+    public Iterator findUpperbound(long keyHash) {
+        return new MockIterator(this, keyHash, false);
+    }
+
+    @Override
+    public Iterator findFirst() {
+        return new MockIterator(this);
+    }
+}
diff --git a/src/main/java/net/kaaass/rumbase/index/mock/MockIterator.java b/src/main/java/net/kaaass/rumbase/index/mock/MockIterator.java
new file mode 100644
index 0000000..957604f
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/index/mock/MockIterator.java
@@ -0,0 +1,74 @@
+package net.kaaass.rumbase.index.mock;
+
+import net.kaaass.rumbase.index.Pair;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class MockIterator implements Iterator {
+    private Iterator>> indexIterator;
+    private Iterator tempIterator;
+    private long tempKey;
+    private boolean state = true;
+
+    public MockIterator(MockBtreeIndex mockBtreeIndex) {
+        indexIterator = mockBtreeIndex.getHashMap().entrySet().iterator();
+        if (!indexIterator.hasNext()) {
+            state = false;
+            return;
+        }
+        var f = indexIterator.next();
+        tempIterator = f.getValue().iterator();
+        tempKey = f.getKey();
+    }
+
+    public MockIterator(MockBtreeIndex mockBtreeIndex, long keyHash, boolean isWith) {
+        indexIterator = mockBtreeIndex.getHashMap().entrySet().iterator();
+        if (isWith == true) {
+            do {
+                var v = indexIterator.next();
+                if (v == null) {
+                    state = false;
+                    break;
+                }
+                tempIterator = v.getValue().iterator();
+                tempKey = v.getKey();
+            } while (tempKey < keyHash);
+        } else {
+            do {
+                var v = indexIterator.next();
+                if (v == null) {
+                    state = false;
+                    break;
+                }
+                tempIterator = v.getValue().iterator();
+                tempKey = v.getKey();
+            } while (tempKey <= keyHash);
+        }
+    }
+
+    @Override
+    public boolean hasNext() {
+        return indexIterator.hasNext() || (tempIterator != null && tempIterator.hasNext());
+    }
+
+
+    @Override
+    public Pair next() {
+        if (state == false) {
+            return null;
+        }
+        Pair res = new Pair(tempKey, tempIterator.next());
+        if (!tempIterator.hasNext()) {
+            if (!indexIterator.hasNext()) {
+                state = false;
+                return res;
+            }
+            var v = indexIterator.next();
+            tempIterator = v.getValue().iterator();
+            tempKey = v.getKey();
+        }
+        return res;
+    }
+}
diff --git a/src/main/java/net/kaaass/rumbase/page/Page.java b/src/main/java/net/kaaass/rumbase/page/Page.java
new file mode 100644
index 0000000..77b13b3
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/page/Page.java
@@ -0,0 +1,99 @@
+package net.kaaass.rumbase.page;
+
+import net.kaaass.rumbase.page.exception.FileException;
+import net.kaaass.rumbase.page.exception.PageException;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * 单页管理
+ * 

+ * Page对象通过PageCache获得。获得的对象【不可以长期保存】(如保存在对象中作为对象的字段)。 + * 使用页面需要注意使用pin,以防止Page对象被提前回收。 + *

+ *     var page = pageCache.get(1);
+ *     page.pin();
+ *     // ...
+ *     page.getData();
+ *     // ...
+ *     // 如果不pin,则此处page对象可能因为被淘汰失效
+ *     page.patchData(2, patch);
+ *     page.unpin();
+ * 
+ * 如果操作有错误风险(有可能发生不造成停机的错误),必须加上try-catch-finally
+ * 
+ *     var page = pageCache.get(1);
+ *     page.pin();
+ *     try {
+ *         page.getData();
+ *         // ...
+ *         page.patchData(2, patch);
+ *     } catch (Exception e) {
+ *         // ...
+ *     } finally {
+ *         page.unpin(); // 重要:防止内存泄漏,必须在finally释放
+ *     }
+ * 
+ * 若操作数量为1,不复用Page对象的情形,可以忽略。
+ * 
+ *     pageCache.get(1).getData();
+ * 
+ *
+ * @author XuanLaoYee
+ */
+public interface Page {
+
+    /**
+     * (性能原因,建议使用getData)获得页内数据
+     *
+     * @return 字节数组,通常为4K
+     */
+    byte[] getDataBytes();
+
+    /**
+     * 获得页内数据的字节流
+     * 

+ * 目前使用default方法实现,留作将来优化性能用 + * + * @return 字节流 + */ + default InputStream getData() { + return new ByteArrayInputStream(getDataBytes()); + } + + /** + * 将字节数据写入页 + * + * @param data 待写入字节数据 + * @throws PageException 若写入的数据超出页的范围则抛出异常 + */ + default void writeData(byte[] data) throws PageException { + patchData(0, data); + } + + /** + * 在页指定位置写入数据 + * + * @param offset 页内偏移值,以字节为单位 + * @param data 待写入数据 + * @throws PageException 若写入的数据超出页的范围则抛出异常 + */ + void patchData(int offset, byte[] data) throws PageException; + + void flush() throws FileException; + + /** + * 将页固定在内存中 + *

+ * 操作规约:必须在系列操作之前使用pin,以防止页被回收 + */ + void pin(); + + /** + * 将页从内存中取消固定 + *

+ * 操作规约:必须在系列操作之后使用unpin,以防止内存泄露 + */ + void unpin(); +} diff --git a/src/main/java/net/kaaass/rumbase/page/PageManager.java b/src/main/java/net/kaaass/rumbase/page/PageManager.java new file mode 100644 index 0000000..60cea7e --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/page/PageManager.java @@ -0,0 +1,25 @@ +package net.kaaass.rumbase.page; + +import net.kaaass.rumbase.page.exception.FileException; +import net.kaaass.rumbase.page.mock.MockPageStorage; + +/** + * @author XuanLaoYee + */ +public class PageManager { + public static int PAGE_SIZE = 1024 * 4; // 页面大小是4KB + public static long FILE_HEAD_SIZE = 5; // 文件头留5页 + public static int PAGE_NUM = 50; + public static int BYTE_BUFFER_SIZE = 1024 * 4 * PAGE_NUM; + + /** + * 取数据库文件生成文件管理的对象 + * + * @param filepath 每个表文件路径 + * @return + * @throws FileException 若文件不存在则创建,创建过程中出现错误会抛出错误 + */ + public static PageStorage fromFile(String filepath) throws FileException { + return new MockPageStorage(filepath); + } +} diff --git a/src/main/java/net/kaaass/rumbase/page/PageStorage.java b/src/main/java/net/kaaass/rumbase/page/PageStorage.java new file mode 100644 index 0000000..5d67685 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/page/PageStorage.java @@ -0,0 +1,22 @@ +package net.kaaass.rumbase.page; + +/** + * 用于管理一系列连续页的存储对象,隐藏任何关于存储的物理细节 + * + * @author XuanLaoYee + */ +public interface PageStorage { + + /** + * 获取该页存储中的某一页 + * + * @param pageId 页号 + * @return 该页页对象 + */ + Page get(long pageId); + + /** + * 将页存储中的所有脏页写回文件 + */ + void flush(); +} diff --git a/src/main/java/net/kaaass/rumbase/page/exception/BufferExeception.java b/src/main/java/net/kaaass/rumbase/page/exception/BufferExeception.java new file mode 100644 index 0000000..4025d82 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/page/exception/BufferExeception.java @@ -0,0 +1,28 @@ +package net.kaaass.rumbase.page.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E9003 缓冲异常 + *

+ * E9003-1 内存不足无法换入 + * + * @author XuanLaoYee + */ +public class BufferExeception extends RumbaseException { + public static final Map REASONS = new HashMap() {{ + put(1, "内存不足无法换入"); + }}; + + /** + * 文件异常 + * + * @param subId 子错误号 + */ + public BufferExeception(int subId) { + super(9003, subId, REASONS.get(subId)); + } +} diff --git a/src/main/java/net/kaaass/rumbase/page/exception/FileException.java b/src/main/java/net/kaaass/rumbase/page/exception/FileException.java new file mode 100644 index 0000000..8358beb --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/page/exception/FileException.java @@ -0,0 +1,34 @@ +package net.kaaass.rumbase.page.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E9001 文件异常 + *

+ * E9001-1 创建文件失败 + * E9001-2 写入文件失败 + * E9001-3 文件打开失败 + * E9001-4 游标越界 + * + * @author XuanLaoYee + */ +public class FileException extends RumbaseException { + public static final Map REASONS = new HashMap() {{ + put(1, "创建文件失败"); + put(2, "写入文件失败"); + put(3, "文件打开失败"); + put(4, "offset越界"); + }}; + + /** + * 文件异常 + * + * @param subId 子错误号 + */ + public FileException(int subId) { + super(9001, subId, REASONS.get(subId)); + } +} diff --git a/src/main/java/net/kaaass/rumbase/page/exception/PageException.java b/src/main/java/net/kaaass/rumbase/page/exception/PageException.java new file mode 100644 index 0000000..356e5b4 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/page/exception/PageException.java @@ -0,0 +1,28 @@ +package net.kaaass.rumbase.page.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E9002 文件异常 + *

+ * E9002-1 回写数据偏移与大小之和超过规定 + * + * @author XuanLaoYee + */ +public class PageException extends RumbaseException { + public static final Map REASONS = new HashMap() {{ + put(1, "回写数据偏移与大小之和超过规定"); + }}; + + /** + * 页操作异常 + * + * @param subId 主错误号 + */ + public PageException(int subId) { + super(9002, subId, REASONS.get(subId)); + } +} diff --git a/src/main/java/net/kaaass/rumbase/page/mock/MockBuffer.java b/src/main/java/net/kaaass/rumbase/page/mock/MockBuffer.java new file mode 100644 index 0000000..71da150 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/page/mock/MockBuffer.java @@ -0,0 +1,71 @@ +package net.kaaass.rumbase.page.mock; + +import net.kaaass.rumbase.page.PageManager; +import net.kaaass.rumbase.page.exception.BufferExeception; + +import java.util.concurrent.locks.ReentrantLock; + +public class MockBuffer { + private static MockBuffer instance = null; + private int size = 0; + + private MockBuffer() { + this.lock = new ReentrantLock(); + size = PageManager.PAGE_NUM; + } + + public static MockBuffer getInstance() { + if (instance == null) { + synchronized (MockBuffer.class) { + if (instance == null) { + instance = new MockBuffer(); + } + } + } + return instance; + } + + private final byte[] byteBuffer = new byte[PageManager.BYTE_BUFFER_SIZE]; + + public void put(int offset, byte[] bytes) throws BufferExeception { + if (this.size <= 0) { + throw new BufferExeception(1); + } + lock.lock(); + try { + System.arraycopy(bytes, 0, this.byteBuffer, offset, bytes.length); + this.size--; + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + } + + public byte[] get(int offset) { + lock.lock(); + try { + byte[] temp = new byte[PageManager.PAGE_SIZE]; + System.arraycopy(this.byteBuffer, offset, temp, offset, PageManager.PAGE_SIZE); + return temp; + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + return null; + } + + public void free(int offset) { + lock.lock(); + try { + System.arraycopy(this.byteBuffer, offset, new byte[PageManager.PAGE_SIZE], 0, PageManager.PAGE_SIZE); + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + } + + private ReentrantLock lock = null; +} diff --git a/src/main/java/net/kaaass/rumbase/page/mock/MockPage.java b/src/main/java/net/kaaass/rumbase/page/mock/MockPage.java new file mode 100644 index 0000000..e96a8c2 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/page/mock/MockPage.java @@ -0,0 +1,54 @@ +package net.kaaass.rumbase.page.mock; + +import net.kaaass.rumbase.page.Page; +import net.kaaass.rumbase.page.PageManager; +import net.kaaass.rumbase.page.exception.FileException; +import net.kaaass.rumbase.page.exception.PageException; + +public class MockPage implements Page { + private byte[] data; + long pageId; + boolean dirty; + int pinned = 0; + String filepath; + + MockPage(byte[] data, long pageId, String filepath) { + this.data = data; + this.pageId = pageId; + this.dirty = false; + this.filepath = filepath; + } + + @Override + public byte[] getDataBytes() { + synchronized (this) { + return data; + } + } + + @Override + public void patchData(int offset, byte[] data) throws PageException { + if (offset + data.length > PageManager.PAGE_SIZE) { + throw new PageException(1); + } + System.arraycopy(data, 0, this.data, offset, data.length); + } + + @Override + public void flush() throws FileException { + } + + @Override + public void pin() { + synchronized (this) { + pinned++; + } + } + + @Override + public void unpin() { + synchronized (this) { + pinned--; + } + } +} diff --git a/src/main/java/net/kaaass/rumbase/page/mock/MockPageStorage.java b/src/main/java/net/kaaass/rumbase/page/mock/MockPageStorage.java new file mode 100644 index 0000000..9f48cfc --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/page/mock/MockPageStorage.java @@ -0,0 +1,55 @@ +package net.kaaass.rumbase.page.mock; + +import net.kaaass.rumbase.page.Page; +import net.kaaass.rumbase.page.PageManager; +import net.kaaass.rumbase.page.PageStorage; +import net.kaaass.rumbase.page.exception.FileException; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author XuanLaoYee + */ +public class MockPageStorage implements PageStorage { + + + public MockPageStorage(String filepath) throws FileException { + this.filepath = filepath; + pageMap = new HashMap<>(); + this.fakeFile = new byte[1024 * 4 * 20]; + for (int i = 0; i < 20; i++) { + for (int j = 0; j < 1024 * 4; j++) { + this.fakeFile[1024 * 4 * i + j] = (byte) i; + } + } + } + + @Override + public Page get(long pageId) { + //文件会预留5页作为文件头 + try { + byte[] data = new byte[PageManager.PAGE_SIZE]; + System.arraycopy(fakeFile, (int) (pageId + PageManager.FILE_HEAD_SIZE) * PageManager.PAGE_SIZE, data, 0, data.length); + Integer tmpId = (int) pageId; + if (pageMap.containsKey(tmpId)) { + return pageMap.get(tmpId); + } + Page page = new MockPage(data, pageId, this.filepath); + pageMap.put(tmpId, page); + return page; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @Override + public void flush() { + } + + private Map pageMap; + + private String filepath; + private byte[] fakeFile; +} diff --git a/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java index a965774..b7137d8 100644 --- a/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java +++ b/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java @@ -4,7 +4,6 @@ import net.kaaass.rumbase.transaction.TransactionContext; import java.util.Optional; -import java.util.UUID; /** * 记录存储接口,提供由记录ID存取数据的容器 @@ -20,7 +19,7 @@ public interface IRecordStorage { * @param rawData 字节数据 * @return 记录ID */ - UUID insert(TransactionContext txContext, byte[] rawData); + long insert(TransactionContext txContext, byte[] rawData); /** * 由记录ID查询记录数据 @@ -28,16 +27,18 @@ public interface IRecordStorage { * @param txContext 事务上下文 * @param recordId 记录ID * @return 记录数据字节 + * @throws RecordNotFoundException 若记录不存在、不可见,抛出错误 */ - byte[] query(TransactionContext txContext, UUID recordId) throws RecordNotFoundException; + byte[] query(TransactionContext txContext, long recordId) throws RecordNotFoundException; - default Optional queryOptional(TransactionContext txContext, UUID recordId) { - try { - return Optional.of(query(txContext, recordId)); - } catch (RecordNotFoundException ignore) { - return Optional.empty(); - } - } + /** + * 由记录ID查询记录数据,并忽略由事务造成的记录不可见。若物理记录不存在,将抛出运行时错误 + * + * @param txContext 事务上下文 + * @param recordId 记录ID + * @return 记录数据字节,若记录不可见则返回Optional.empty() + */ + Optional queryOptional(TransactionContext txContext, long recordId); /** * 由记录ID删除记录数据 @@ -45,8 +46,7 @@ default Optional queryOptional(TransactionContext txContext, UUID record * @param txContext 事务上下文 * @param recordId 记录ID */ - void delete(TransactionContext txContext, UUID recordId); - + void delete(TransactionContext txContext, long recordId); /** * 获得记录存储的元信息(与单个记录无关) diff --git a/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java b/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java index 120eb77..51f8ccb 100644 --- a/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java +++ b/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java @@ -14,14 +14,14 @@ */ public class RecordNotFoundException extends RumbaseException { - public static final Map REASONS = new HashMap<>(){{ + public static final Map REASONS = new HashMap<>() {{ put(1, "物理记录不存在"); }}; /** * 记录不存在异常 * - * @param subId 子错误号 + * @param subId 子错误号 */ public RecordNotFoundException(int subId) { super(5001, subId, REASONS.get(subId)); diff --git a/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java index 3394821..3e11d23 100644 --- a/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java +++ b/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java @@ -30,19 +30,19 @@ public class MockRecordStorage implements IRecordStorage { @Getter private final String mockId; - private final Map memoryStorage = new HashMap<>(); + private final Map memoryStorage = new HashMap<>(); private byte[] metadata = new byte[0]; @Override - public UUID insert(TransactionContext txContext, byte[] rawData) { - var uuid = UUID.randomUUID(); + public long insert(TransactionContext txContext, byte[] rawData) { + var uuid = UUID.randomUUID().getLeastSignificantBits(); this.memoryStorage.put(uuid, rawData); return uuid; } @Override - public byte[] query(TransactionContext txContext, UUID recordId) throws RecordNotFoundException { + public byte[] query(TransactionContext txContext, long recordId) throws RecordNotFoundException { if (!this.memoryStorage.containsKey(recordId)) { throw new RecordNotFoundException(1); } @@ -50,7 +50,16 @@ public byte[] query(TransactionContext txContext, UUID recordId) throws RecordNo } @Override - public void delete(TransactionContext txContext, UUID recordId) { + public Optional queryOptional(TransactionContext txContext, long recordId) { + try { + return Optional.ofNullable(query(txContext, recordId)); + } catch (RecordNotFoundException e) { + return Optional.empty(); + } + } + + @Override + public void delete(TransactionContext txContext, long recordId) { this.memoryStorage.remove(recordId); } diff --git a/src/main/java/net/kaaass/rumbase/recovery/IRecoveryStorage.java b/src/main/java/net/kaaass/rumbase/recovery/IRecoveryStorage.java new file mode 100644 index 0000000..0684071 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/recovery/IRecoveryStorage.java @@ -0,0 +1,63 @@ +package net.kaaass.rumbase.recovery; + +import java.util.List; + +/** + * 日志管理的接口,用来进行日志的写入和恢复 + * + * @author kaito + */ +public interface IRecoveryStorage { + /** + * 记录事务开始 + * + * @param xid 事务编号 + * @param snapshots 快照集合 + */ + void begin(int xid, List snapshots); + + /** + * 记录事务失败回滚 + * + * @param xid + */ + void rollback(int xid); + + /** + * 记录事务完成 + * + * @param xid + */ + void commit(int xid); + + /** + * 插入数据项的日志记录 + * + * @param xid 日志编号 + * @param uuid 数据项的对应编号 + * @param item 插入的数据内容 + */ + void insert(int xid, long uuid, byte[] item); + + /** + * 更新数据项的日志记录 + * + * @param xid + * @param uuid + * @param item + */ + void update(int xid, long uuid, byte[] item); + + /** + * 更新数据项的日志头 + * + * @param xid + * @param metaUUID 头信息的UUID + */ + void updateMeta(int xid, long metaUUID); + + /** + * 模拟打印日志资料 + */ + List getContent(); +} diff --git a/src/main/java/net/kaaass/rumbase/recovery/RecoveryManager.java b/src/main/java/net/kaaass/rumbase/recovery/RecoveryManager.java new file mode 100644 index 0000000..4fbc18a --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/recovery/RecoveryManager.java @@ -0,0 +1,21 @@ +package net.kaaass.rumbase.recovery; + +import net.kaaass.rumbase.recovery.mock.MockRecoveryStorage; + +/** + * 日志恢复的管理器,用来对每个数据库文件进行恢复 + * + * @author kaito + */ +public class RecoveryManager { + /** + * 对某个数据库文件进行恢复,并且返回对应的日志管理器 + * + * @param fileName 文件名 + * @return 数据库日志管理器 + */ + public static IRecoveryStorage recovery(String fileName) { + // TODO:对数据进行恢复 + return new MockRecoveryStorage(); + } +} diff --git a/src/main/java/net/kaaass/rumbase/recovery/mock/MockRecoveryStorage.java b/src/main/java/net/kaaass/rumbase/recovery/mock/MockRecoveryStorage.java new file mode 100644 index 0000000..fa8c4e9 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/recovery/mock/MockRecoveryStorage.java @@ -0,0 +1,73 @@ +package net.kaaass.rumbase.recovery.mock; + +import net.kaaass.rumbase.recovery.IRecoveryStorage; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +public class MockRecoveryStorage implements IRecoveryStorage { + + public MockRecoveryStorage() { + bytes = new ArrayList<>(); + } + + /** + * 模拟日志记录 + */ + public List bytes; + + @Override + public void begin(int xid, List snapshots) { + String beginStr = "begin " + xid; + bytes.add(beginStr.getBytes()); + String snapStr = "snap " + snapshots.toString(); + bytes.add(snapStr.getBytes()); + } + + @Override + public void rollback(int xid) { + String backStr = "abort " + xid; + bytes.add(backStr.getBytes()); + } + + @Override + public void commit(int xid) { + String commitStr = "commit " + xid; + bytes.add(commitStr.getBytes()); + } + + @Override + public void insert(int xid, long uuid, byte[] item) { + String insertStr = "insert " + xid + " " + uuid + " "; + // 对控制语句、数据两部分进行合并得到最终日志记录 + byte[] first = insertStr.getBytes(); + byte[] result = Arrays.copyOf(first, first.length + item.length); + System.arraycopy(item, 0, result, first.length, item.length); + bytes.add(result); + } + + @Override + public void update(int xid, long uuid, byte[] item) { + String updateStr = "update " + xid + " " + uuid + " "; + // 对控制语句、数据两部分进行合并得到最终日志记录 + byte[] first = updateStr.getBytes(); + byte[] result = Arrays.copyOf(first, first.length + item.length); + System.arraycopy(item, 0, result, first.length, item.length); + bytes.add(result); + } + + @Override + public void updateMeta(int xid, long metaUUID) { + String updateMetaStr = "meta " + xid + " " + metaUUID; + bytes.add(updateMetaStr.getBytes()); + } + + @Override + public List getContent() { + return bytes; + } + + +} diff --git a/src/main/java/net/kaaass/rumbase/table/Entry.java b/src/main/java/net/kaaass/rumbase/table/Entry.java deleted file mode 100644 index 74f3094..0000000 --- a/src/main/java/net/kaaass/rumbase/table/Entry.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.kaaass.rumbase.table; - -import java.util.*; - -/** - * 一行记录数据 - *

- * 代表表中的一行数据 - * - * @author @KveinAxel - */ -public class Entry extends HashMap { -} diff --git a/src/main/java/net/kaaass/rumbase/table/FieldType.java b/src/main/java/net/kaaass/rumbase/table/FieldType.java deleted file mode 100644 index bcadec6..0000000 --- a/src/main/java/net/kaaass/rumbase/table/FieldType.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.kaaass.rumbase.table; - -public enum FieldType { - INT, - FLOAT; - - public int getSize() { - if (this == FieldType.INT) { - return 4; - } else if (this == FieldType.FLOAT) { - return 4; - } else { - return 4; - } - } -} diff --git a/src/main/java/net/kaaass/rumbase/table/FieldValue.java b/src/main/java/net/kaaass/rumbase/table/FieldValue.java deleted file mode 100644 index f227524..0000000 --- a/src/main/java/net/kaaass/rumbase/table/FieldValue.java +++ /dev/null @@ -1,56 +0,0 @@ -package net.kaaass.rumbase.table; - -public class FieldValue implements Comparable { - private T value; - private FieldType type; - - public T getValue() { - return value; - } - - public void setValue(T value) { - this.value = value; - } - - public FieldType getType() { - return type; - } - - public void setType(FieldType type) { - this.type = type; - } - - public FieldValue(FieldType type) { - this.type = type; - } - - public FieldValue(T value, FieldType type) { - this.value = value; - this.type = type; - } - - - @Override - public int compareTo(Object o) { - if (o instanceof FieldValue) { - var another = (FieldValue) o; - if (this.type != another.type) { - return 0; - } else { - var res = 0; - if (this.type == FieldType.INT) { - res = (int) this.getValue() - (int) another.getValue(); - } else if (this.type == FieldType.FLOAT) { - var tmp = (float) this.getValue() - (float) another.getValue(); - if (tmp < 0) - res = -1; - else if (tmp > 0) - res = 1; - } - return res; - } - } else { - return 0; - } - } -} diff --git a/src/main/java/net/kaaass/rumbase/table/IField.java b/src/main/java/net/kaaass/rumbase/table/IField.java deleted file mode 100644 index 01ce46a..0000000 --- a/src/main/java/net/kaaass/rumbase/table/IField.java +++ /dev/null @@ -1,100 +0,0 @@ -package net.kaaass.rumbase.table; - -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -/** - * 字段结构 - *

- * 提供字段解析服务 - *

- * 提供字段的索引处理 - * - * @author @KveinAxel - */ -public interface IField { - - /** - * 从配置信息的字节数组获取字段的属性 - * - * @param raw 配置信息 - */ - void parseSelf(byte[] raw); - - /** - * 将自身的配置信息持久化成字节数组 - * - * @return 配置信息 - */ - byte[] persistSelf(); - - /** - * 是否已经被建立引用 - * @return true则已经建立引用,反之没建立 - */ - Boolean isIndexed(); - - /** - * 获取字段的类型 - * - * @return 字段类型 - */ - FieldType getType(); - - /** - * 字段所占用的字节数 - * - * @return 字节数 - */ - int getSize(); - - /** - * 在该字段的索引上进行范围搜索 - * - * @param left 查询左端点 - * @param right 查询右端点 - * @return 查询到的uuid - */ - List searchRange(FieldValue left, FieldValue right); - - /** - * 在该字段的索引上进行单点搜索 - * - * @param value 字段的值 - * @return 字段的uuid的Optional - */ - Optional search(FieldValue value); - - /** - * 将字符串转换成字段值 - * - * @param valStr 字符串 - * @return 字段值的Optional - */ - Optional strToValue(String valStr); - - /** - * 将字段值转成字节数组 - * - * @param value 字段值 - * @return 字节数组 - */ - Optional valueToRaw(FieldValue value); - - /** - * 将字节数组转成字段值 - * - * @param raw 字节数组 - * @return 字段值 - */ - Optional rawToValue(byte[] raw) ; - - /** - * 获取字段名 - * - * @return 字段名 - */ - String getFieldName(); - -} diff --git a/src/main/java/net/kaaass/rumbase/table/ITable.java b/src/main/java/net/kaaass/rumbase/table/ITable.java deleted file mode 100644 index 4e4f8db..0000000 --- a/src/main/java/net/kaaass/rumbase/table/ITable.java +++ /dev/null @@ -1,115 +0,0 @@ -package net.kaaass.rumbase.table; - -import net.kaaass.rumbase.record.IRecordStorage; -import net.kaaass.rumbase.table.exception.TableNotFoundException; -import net.kaaass.rumbase.table.exception.TypeIncompatibleException; -import net.kaaass.rumbase.transaction.TransactionContext; - -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -/** - * 表结构 - *

- * 验证数据是否满足表约束 - *

- * 提供行数据解析服务 - * - * @author @KveinAxel - */ -public interface ITable { - - /** - * 根据配置信息解析获取表属性 - * @param raw 配置信息 - */ - void parseSelf(byte[] raw); - - /** - * 将自己持久化成字节数组 - */ - byte[] persistSelf(); - - /** - * 删除元组 - * - * @param context 事务context - * @param fieldName 字段名 - * @param uuid 元组的uuid - * @param recordStorage record层访问接口 - * @return 被删除的元组数 - */ - int delete(TransactionContext context, String fieldName, UUID uuid, IRecordStorage recordStorage); - - /** - * 更新元组 - * - * @param context 事务context - * @param fieldName 字段名 - * @param uuid 元组的uuid - * @param newEntry 新的元组 - * @return 被更新的元组数 - */ - int update(TransactionContext context, String fieldName, UUID uuid, Entry newEntry); - - /** - * 获取元组内容 - * - * @param context 事务context - * @param fieldName 字段名 - * @param uuids 元组的uuid列表 - * @param recordStorage record层访问接口 - * @return 元组entry列表 - * @throws TableNotFoundException 要查询的表不存在 - */ - List read(TransactionContext context, String fieldName, List uuids, IRecordStorage recordStorage) throws TableNotFoundException; - - /** - * 向表插入元组 - * - * @param context 事务context - * @param fieldName 字段名 - * @param newEntry 新的元组 - * @param recordStorage record层访问接口 - * @throws TableNotFoundException 要查询的表不存在 - * @throws TypeIncompatibleException 插入的元组不满足表约束 - */ - void insert(TransactionContext context, String fieldName, Entry newEntry, IRecordStorage recordStorage) throws TableNotFoundException, TypeIncompatibleException; - - /** - * - * - * @param fieldName 字段名 - * @param left 查询区间左端点 - * @param right 查询区间右端点 - * @return 查询到的uuid列表 - * @throws TableNotFoundException 要查询的表不存在 - */ - List search(String fieldName, FieldValue left, FieldValue right) throws TableNotFoundException; - - /** - * 将字符串列表转换成一个元组 - * - * @param values 字符串列表 - * @return 元组的Optional - */ - Optional strToEntry(List values); - - /** - * 将entry转换成字节数组 - * - * @param entry 元组 - * @return 字节数组 - */ - Optional entryToRaw(Entry entry); - - /** - * 将字节数组转换成entry - * - * @param raw 字节数组 - * @return 元组 - */ - Optional parseEntry(byte[] raw); - -} diff --git a/src/main/java/net/kaaass/rumbase/table/TableManager.java b/src/main/java/net/kaaass/rumbase/table/TableManager.java deleted file mode 100644 index b14e27f..0000000 --- a/src/main/java/net/kaaass/rumbase/table/TableManager.java +++ /dev/null @@ -1,189 +0,0 @@ -package net.kaaass.rumbase.table; - -import net.kaaass.rumbase.record.IRecordStorage; -import net.kaaass.rumbase.table.exception.TableNotFoundException; -import net.kaaass.rumbase.table.exception.TypeIncompatibleException; -import net.kaaass.rumbase.transaction.TransactionContext; - -import java.util.*; - -/** - * 表管理器 - *

- * ITableManager用于管理表结构, 已经为上层模块提供更加高级和抽象的接口. - *

- * ITableManager会依赖index模块进行索引, 依赖record模块进行表单数据查找. - *

- * - * @author @KveinAxel - */ -public class TableManager { - -// private DataItem dataItem; - - private final IRecordStorage recordStorage; - - private Map tableCache; - - private Map> transactionTableCache; - -// private Lock lock; - - public TableManager(IRecordStorage recordStorage) { - this.recordStorage = recordStorage; - this.tableCache = new HashMap<>(); - this.tableCache = new HashMap<>(); - } - - /** - * 开始一个事务 - * - * @param IsRepeatableRead 是否可重复读 - * @return 事务context - */ - TransactionContext begin(boolean IsRepeatableRead) { - return TransactionContext.empty(); - } - - /** - * 提交一个事务 - * - * @param context 事务context - */ - void commit(TransactionContext context) { - // todo commit tx - } - - /** - * 终止一个事务 - * - * @param context 事务context - */ - void abort(TransactionContext context) { - // todo abort tx - } - - /** - * 显示所有的表名 - * - * @return 表名的列表 - */ - List showTables() { - // todo show tables - return new ArrayList<>(); - } - - /** - * 创建一个表 - * - * @param context 事务context - * @param tableName 表名 - * @param fields 表的字段 - */ - void createTable(TransactionContext context, String tableName, List fields) { - // todo create Table - } - - /** - * 向一张表插入数据 - * - * @param context 事务context - * @param tableName 表名 - * @param fieldName 字段名 - * @param newEntry 新的Entry - * @throws TableNotFoundException 不存在该表 - * @throws TypeIncompatibleException 插入的Entry与表字段冲突 - */ - void insert(TransactionContext context, String tableName, String fieldName, Entry newEntry) throws TableNotFoundException, TypeIncompatibleException { - var table = tableCache.get(tableName); - if (table == null) { - throw new TableNotFoundException(1); - } - - table.insert(context, fieldName, newEntry, recordStorage); - } - - /** - * 删除一张表的数据 - * - * @param context 事务context - * @param tableName 表名 - * @param fieldName 字段名 - * @param uuids 待删除的元组的uuid列表 - * @return 被删除的元组数 - * @throws TableNotFoundException 不存在该表 - */ - int delete(TransactionContext context, String tableName, String fieldName, List uuids) throws TableNotFoundException { - var table = tableCache.get(tableName); - if (table == null) { - throw new TableNotFoundException(1); - } - - return uuids.stream() - .map(uuid -> table.delete(context, fieldName, uuid, recordStorage)) - .reduce(0, Integer::sum); - } - - /** - * 更新一张表的数据 - * - * @param context 事务context - * @param tableName 表名 - * @param fieldName 字段名 - * @param newEntries 待更新的entry,键为旧行的uuid,值为新行的entry - * @return 被更新的行数 - * @throws TableNotFoundException 不存在该表 - */ - int update(TransactionContext context, String tableName, String fieldName, Map newEntries) throws TableNotFoundException { - var table = tableCache.get(tableName); - if (table == null) { - throw new TableNotFoundException(1); - } - - return newEntries.entrySet().stream() - .map(e -> table.update(context, fieldName, e.getKey(), e.getValue())) - .reduce(0, Integer::sum); - } - - /** - * 读取一张表的数据 - * - * @param context 事务context - * @param tableName 表名 - * @param fieldName 字段名 - * @param uuids 待查询的uuid的列表 - * @return 查询到的行的Entry - * @throws TableNotFoundException 不存在该表 - */ - List read(TransactionContext context, String tableName, String fieldName, List uuids) throws TableNotFoundException { - var table = tableCache.get(tableName); - if (table == null) { - throw new TableNotFoundException(1); - } - - return table.read(context, fieldName, uuids, recordStorage); - } - - /** - * 查询满足 [left, right) 区间uuid列表 - * 如果 left 为空,则查询 [ begin, right ) - * 如果 right 为空,则查询 [ left, end ) - * 如果 left, right 都为空,查询 [ begin, end ) - * - * @param context 事务context - * @param tableName 表名 - * @param fieldName 字段名 - * @param left 查询区间左端点 - * @param right 查询区间右端点 - * @return 查询到的uuid - * @throws TableNotFoundException 不存在该表 - */ - List search(TransactionContext context, String tableName, String fieldName, FieldValue left, FieldValue right) throws TableNotFoundException { - var table = tableCache.get(tableName); - if (table == null) { - throw new TableNotFoundException(1); - } - - return table.search(fieldName, left, right); - } -} diff --git a/src/main/java/net/kaaass/rumbase/table/exception/TableNotFoundException.java b/src/main/java/net/kaaass/rumbase/table/exception/TableNotFoundException.java deleted file mode 100644 index 90f24c9..0000000 --- a/src/main/java/net/kaaass/rumbase/table/exception/TableNotFoundException.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.kaaass.rumbase.table.exception; - -import net.kaaass.rumbase.exception.RumbaseException; - -import java.util.HashMap; -import java.util.Map; - -/** - * E3001 关系不存在异常 - *

- * E3001-1 关系不存在 - * E3001-2 字段不存在 - * E3001-3 视图不存在 - * - * @author @KveinAxel - */ -public class TableNotFoundException extends RumbaseException { - public static final Map REASONS = new HashMap<>(){{ - put(1, "关系不存在"); - put(2, "字段不存在"); - put(3, "视图不存在"); - }}; - - /** - * 关系不存在异常 - * - * @param subId 子错误号 - */ - public TableNotFoundException(int subId) { - super(3001, subId, REASONS.get(subId)); - } -} diff --git a/src/main/java/net/kaaass/rumbase/table/exception/TypeIncompatibleException.java b/src/main/java/net/kaaass/rumbase/table/exception/TypeIncompatibleException.java deleted file mode 100644 index 91612ec..0000000 --- a/src/main/java/net/kaaass/rumbase/table/exception/TypeIncompatibleException.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.kaaass.rumbase.table.exception; - -import net.kaaass.rumbase.exception.RumbaseException; - -import java.util.HashMap; -import java.util.Map; - -/** - * E3002 类型不匹配异常 - *

- * E3002-1 字段类型不匹配 - * E3002-2 字段类型不存在 - * E3002-3 Entry类型不匹配 - * - * @author @KveinAxel - */ -public class TypeIncompatibleException extends RumbaseException { - public static final Map REASONS = new HashMap<>(){{ - put(1, "字段类型不匹配"); - put(2, "字段类型不存在"); - put(3, "Entry不匹配"); - }}; - - /** - * 类型不匹配异常 - * - * @param subId 子错误号 - */ - public TypeIncompatibleException(int subId) { - super(3002, subId, REASONS.get(subId)); - } -} diff --git a/src/main/java/net/kaaass/rumbase/table/mock/MockField.java b/src/main/java/net/kaaass/rumbase/table/mock/MockField.java deleted file mode 100644 index b7ccf6b..0000000 --- a/src/main/java/net/kaaass/rumbase/table/mock/MockField.java +++ /dev/null @@ -1,201 +0,0 @@ -package net.kaaass.rumbase.table.mock; - -import net.kaaass.rumbase.table.FieldType; -import net.kaaass.rumbase.table.FieldValue; -import net.kaaass.rumbase.table.IField; -import net.kaaass.rumbase.table.ITable; - -import java.io.*; -import java.util.*; - -/** - * Mock字段结构,仅用于测试 - * - * @author @KveinAxel - */ -@Deprecated -public class MockField implements IField { - - private String name; - - private FieldType type; - - private UUID selfUUID; - - private ITable table; - - private UUID index; -// private BTree bTree; - - - /** - * 内存中创建的Mock存储 - */ - private static final Map MOCK_STORAGES = new HashMap<>(); - - private final String mockId; - - private final Map memoryStorage = new HashMap<>(); - - private MockField(String mockId, String name, FieldType fieldType) { - this.mockId = mockId; - index = null; - type = fieldType; - this.name = name; - } - - @Override - public void parseSelf(byte[] raw) { - - } - - @Override - public byte[] persistSelf() { - return new byte[0]; - } - - @Override - public Boolean isIndexed() { - return index != null; - } - - @Override - public FieldType getType() { - return type; - } - - @Override - public int getSize() { - if (type == FieldType.INT) { - return 4; - } else if (type == FieldType.FLOAT) { - return 4; - } - return 4; - } - - @Override - public List searchRange(FieldValue left, FieldValue right) { - if (left.compareTo(right) >= 0) { - return new ArrayList<>(); - } - return null; - } - - @Override - public Optional search(FieldValue value) { - return Optional.empty(); - } - - - @Override - public Optional strToValue(String valStr) { - if (type == FieldType.INT) { - try { - var value = Integer.parseInt(valStr); - var fieldValue = new FieldValue(type); - - fieldValue.setValue(value); - - return Optional.of(fieldValue); - } catch (NumberFormatException e) { - return Optional.empty(); - } - - } else if (type == FieldType.FLOAT) { - try { - var value = Float.parseFloat(valStr); - var fieldValue = new FieldValue(type); - - fieldValue.setValue(value); - - return Optional.of(fieldValue); - } catch (NumberFormatException e) { - return Optional.empty(); - } - } - - return Optional.empty(); - } - - @Override - public Optional valueToRaw(FieldValue value) { - try ( - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(bos) - ) { - if (type == FieldType.INT) { - int intVal = (int) value.getValue(); - - out.writeInt(intVal); - out.close(); - - return Optional.of(bos.toByteArray()); - - } else if (type == FieldType.FLOAT) { - float floatVal = (float) value.getValue(); - - out.writeFloat(floatVal); - out.close(); - - return Optional.of(bos.toByteArray()); - } - - } catch (Exception e) { - return Optional.empty(); - } - return Optional.empty(); - } - - @Override - public Optional rawToValue(byte[] raw) { - try ( - var bais = new ByteArrayInputStream(raw); - var dis = new DataInputStream(bais) - ) { - if (type == FieldType.INT) { - var fieldValue = new FieldValue(FieldType.INT); - - if (raw.length != fieldValue.getType().getSize()) { - return Optional.empty(); - } - - fieldValue.setValue(dis.readInt()); - return Optional.of(fieldValue); - - } else if (type == FieldType.FLOAT) { - var fieldValue = new FieldValue(FieldType.FLOAT); - - if (raw.length != fieldValue.getType().getSize()) { - return Optional.empty(); - } - - fieldValue.setValue(dis.readFloat()); - return Optional.of(fieldValue); - } - - } catch (Exception e) { - return Optional.empty(); - } - return Optional.empty(); - } - - @Override - public String getFieldName() { - return name; - } - - public static MockField ofFile(String filepath, String name, FieldType fieldType) { - var mockId = "file" + filepath; - if (MOCK_STORAGES.containsKey(mockId)) { - return MOCK_STORAGES.get(mockId); - } - var result = new MockField(mockId, name, fieldType); - MOCK_STORAGES.put(mockId, result); - return result; - } - - public String getMockId() { - return mockId; - } -} diff --git a/src/main/java/net/kaaass/rumbase/table/mock/MockTable.java b/src/main/java/net/kaaass/rumbase/table/mock/MockTable.java deleted file mode 100644 index 620ccd3..0000000 --- a/src/main/java/net/kaaass/rumbase/table/mock/MockTable.java +++ /dev/null @@ -1,148 +0,0 @@ -package net.kaaass.rumbase.table.mock; - -import net.kaaass.rumbase.record.IRecordStorage; -import net.kaaass.rumbase.table.*; -import net.kaaass.rumbase.table.exception.TableNotFoundException; -import net.kaaass.rumbase.table.exception.TypeIncompatibleException; -import net.kaaass.rumbase.transaction.TransactionContext; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Mock表结构,仅用于测试 - * - * @author @KveinAxel - */ -@Deprecated -public class MockTable implements ITable { - String tableName; - List fields; - - public MockTable(String tableName, List fields) { - this.tableName = tableName; - this.fields = fields; - } - - @Override - public void parseSelf(byte[] raw) { - - } - - @Override - public byte[] persistSelf() { - return new byte[0]; - } - - @Override - public int delete(TransactionContext context, String fieldName, UUID uuid, IRecordStorage recordStorage) { - recordStorage.delete(context, uuid); - // todo delete index - return 0; - } - - @Override - public int update(TransactionContext context, String fieldName, UUID uuid, Entry newEntry) { - // todo update - return 0; - } - - @Override - public List read(TransactionContext context, String fieldName, List uuids, IRecordStorage recordStorage) throws TableNotFoundException { - fields.stream() - .filter(f -> f.getFieldName().equals(fieldName)) - .findAny() - .orElseThrow(() -> new TableNotFoundException(2)); - - var list = new ArrayList(); - uuids.forEach( - uuid -> recordStorage - .queryOptional(context, uuid) - .flatMap(this::parseEntry) - .ifPresent(list::add) - ); - return list; - } - - @Override - public void insert(TransactionContext context, String fieldName, Entry newEntry, IRecordStorage recordStorage) throws TableNotFoundException, TypeIncompatibleException { - fields.stream() - .filter(f -> f.getFieldName().equals(fieldName)) - .findAny() - .orElseThrow(() -> new TableNotFoundException(2)); - - if (!newEntry.containsKey(fieldName)) { - throw new TypeIncompatibleException(2); - } - - entryToRaw(newEntry).ifPresent((entry) -> recordStorage.insert(context, entry)); - - // todo add to index - } - - @Override - public List search(String fieldName, FieldValue left, FieldValue right) throws TableNotFoundException { - return fields - .stream() - .filter(f -> f.getFieldName().equals(fieldName)) - .findAny() - .orElseThrow(() -> new TableNotFoundException(2)) - .searchRange(left, right); - } - - @Override - public Optional strToEntry(List values) { - var entry = new Entry(); - if (values.size() != fields.size()) { - return Optional.empty(); - } - - var iter = values.iterator(); - fields.forEach(f -> f.strToValue(iter.next()).ifPresent(value -> entry.put(f.getFieldName(), value))); - - return Optional.of(entry); - } - - @Override - public Optional entryToRaw(Entry entry) { - - try ( - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(baos) - ) { - for (var e : entry.entrySet()) { - var fieldValue = e.getValue(); - if (fieldValue.getValue() == FieldType.INT) { - dos.writeInt((int) fieldValue.getValue()); - } else if (fieldValue.getValue() == FieldType.FLOAT) { - dos.writeFloat((float) fieldValue.getValue()); - } - - } - return Optional.of(baos.toByteArray()); - } catch (Exception e) { - return Optional.empty(); - } - } - - @Override - public Optional parseEntry(byte[] raw) { - AtomicInteger offset = new AtomicInteger(); - var entry = new Entry(); - - var tot = fields.stream().map(IField::getSize).reduce(0, Integer::sum); - if (tot != raw.length) { - return Optional.empty(); - } - - fields.forEach(f -> { - f.rawToValue(Arrays.copyOfRange(raw, offset.get(), offset.get() + f.getSize())) - .ifPresent(value -> entry.put(f.getFieldName(), value)); - offset.addAndGet(f.getSize()); - }); - - return entry.isEmpty() ? Optional.empty() : Optional.of(entry); - } -} diff --git a/src/main/java/net/kaaass/rumbase/transaction/LockTable.java b/src/main/java/net/kaaass/rumbase/transaction/LockTable.java new file mode 100644 index 0000000..5527887 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/LockTable.java @@ -0,0 +1,37 @@ +package net.kaaass.rumbase.transaction; + +/** + * 锁表 + *

+ * 记录锁的信息 + * + * @author criki + */ +public interface LockTable { + /** + * 添加共享锁 + * + * @param xid 事务id + * @param uuid 记录id + * @param tableName 表名 + */ + void addSharedLock(int xid, long uuid, String tableName); + + /** + * 添加排他锁 + * + * @param xid 事务id + * @param uuid 记录id + * @param tableName 表名 + */ + void addExclusiveLock(int xid, long uuid, String tableName); + + /** + * 释放事务的锁 + * + * @param xid 事务id + */ + void release(int xid); + + +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/LockTableManager.java b/src/main/java/net/kaaass/rumbase/transaction/LockTableManager.java new file mode 100644 index 0000000..a69daad --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/LockTableManager.java @@ -0,0 +1,37 @@ +package net.kaaass.rumbase.transaction; + +import net.kaaass.rumbase.transaction.mock.MockLockTable; + +import java.util.HashMap; +import java.util.Map; + +/** + * 锁表管理器 + *

+ * 管理锁表 + * + * @author criki + */ +public class LockTableManager { + /** + * 表名与锁表的映射 + */ + private final Map lockTables; + + /** + * 锁表管理器 + */ + public LockTableManager() { + this.lockTables = new HashMap<>(); + } + + public LockTable getTable(String tableName) { + if (lockTables.containsKey(tableName)) { + return lockTables.get(tableName); + } else { + LockTable table = new MockLockTable(); + lockTables.put(tableName, table); + return table; + } + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java index 7f58128..0c1a78d 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java @@ -1,20 +1,75 @@ package net.kaaass.rumbase.transaction; +import net.kaaass.rumbase.transaction.mock.MockTransactionContext; + /** * 事务上下文 + *

+ * 存储事务状态,对事务进行操作 * - * @author + * @author criki */ -public class TransactionContext { +public interface TransactionContext { - private TransactionContext() { - } /** * 返回空事务上下文。空事务或超级事务XID为0,不受事务限制,也不具备ACID性质。 + * * @return 空事务上下文 */ - public static TransactionContext empty() { - return new TransactionContext(); + static TransactionContext empty() { + return new MockTransactionContext(); } + + /** + * 获取事务隔离度 + * + * @return 事务隔离度 + */ + TransactionIsolation getIsolation(); + + /** + * 获取事务状态 + * + * @return 事务状态 + */ + TransactionStatus getStatus(); + + /** + * 获取事务id + * + * @return 事务id + */ + int getXid(); + + /** + * 事务开始 + */ + void start(); + + /** + * 事务提交 + */ + void commit(); + + /** + * 事务撤销 + */ + void rollback(); + + /** + * 对记录加共享锁 + * + * @param uuid 记录id + * @param tableName 表字段 + */ + void sharedLock(long uuid, String tableName); + + /** + * 对记录加排他锁 + * + * @param uuid 记录id + * @param tableName 表字段 + */ + void exclusiveLock(long uuid, String tableName); } diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionIsolation.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionIsolation.java new file mode 100644 index 0000000..1990495 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionIsolation.java @@ -0,0 +1,42 @@ +package net.kaaass.rumbase.transaction; + +import lombok.Getter; + +/** + * 事务隔离度 + * + * @author criki + */ +public enum TransactionIsolation { + /** + * 读未提交 + */ + READ_UNCOMMITTED(0), + /** + * 读已提交 + */ + READ_COMMITTED(1), + /** + * 重复读 + */ + REPEATABLE_READ(2), + /** + * 串行化 + */ + SERIALIZABLE(3); + + /** + * 事务隔离度Id + */ + @Getter + private final int isolationId; + + /** + * 事务隔离度 + * + * @param isolationId 事务隔离度Id + */ + TransactionIsolation(int isolationId) { + this.isolationId = isolationId; + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionManager.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionManager.java new file mode 100644 index 0000000..4d0e0fb --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionManager.java @@ -0,0 +1,28 @@ +package net.kaaass.rumbase.transaction; + +/** + * 事务管理器 + *

+ * 管理事务的状态 + * + * @author criki + */ +public interface TransactionManager { + + /** + * 创建新事务 + * + * @param isolation 事务隔离度 + * @return 创建的事物对象 + */ + TransactionContext createTransactionContext(TransactionIsolation isolation); + + /** + * 改变事务日志中的事务状态 + * + * @param xid 事务id + * @param status 新事务状态 + */ + void changeTransactionStatus(int xid, TransactionStatus status); + +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionStatus.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionStatus.java new file mode 100644 index 0000000..847cdf9 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionStatus.java @@ -0,0 +1,42 @@ +package net.kaaass.rumbase.transaction; + +import lombok.Getter; + +/** + * 事务状态 + * + * @author criki + */ +public enum TransactionStatus { + /** + * 未开始 + */ + PREPARING(0), + /** + * 正在进行 + */ + ACTIVE(1), + /** + * 已提交 + */ + COMMITTED(2), + /** + * 被撤销 + */ + ABORTED(3); + + /** + * 事务状态Id + */ + @Getter + private final byte statusId; + + /** + * 事务状态 + * + * @param statusId 事务状态Id + */ + TransactionStatus(int statusId) { + this.statusId = (byte) statusId; + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/exception/DeadlockException.java b/src/main/java/net/kaaass/rumbase/transaction/exception/DeadlockException.java new file mode 100644 index 0000000..aa158b8 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/exception/DeadlockException.java @@ -0,0 +1,30 @@ +package net.kaaass.rumbase.transaction.exception; + + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E6001 死锁异常 + *

+ * E6001-1 发生死锁 + * + * @author criki + */ +public class DeadlockException extends RumbaseException { + + public static final Map REASONS = new HashMap<>() {{ + put(1, "发生死锁"); + }}; + + /** + * 死锁异常 + * + * @param subId 子错误号 + */ + public DeadlockException(int subId) { + super(6001, subId, REASONS.get(subId)); + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/mock/MockLockTable.java b/src/main/java/net/kaaass/rumbase/transaction/mock/MockLockTable.java new file mode 100644 index 0000000..84ee697 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/mock/MockLockTable.java @@ -0,0 +1,29 @@ +package net.kaaass.rumbase.transaction.mock; + +import net.kaaass.rumbase.transaction.LockTable; + +/** + * Mock锁表 + * + * @author criki + */ + +@Deprecated +public class MockLockTable implements LockTable { + + + @Override + public void addSharedLock(int xid, long uuid, String tableName) { + + } + + @Override + public void addExclusiveLock(int xid, long uuid, String tableName) { + + } + + @Override + public void release(int xid) { + + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionContext.java b/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionContext.java new file mode 100644 index 0000000..c8b51b9 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionContext.java @@ -0,0 +1,100 @@ +package net.kaaass.rumbase.transaction.mock; + +import lombok.Getter; +import net.kaaass.rumbase.transaction.TransactionContext; +import net.kaaass.rumbase.transaction.TransactionIsolation; +import net.kaaass.rumbase.transaction.TransactionManager; +import net.kaaass.rumbase.transaction.TransactionStatus; + +import java.util.ArrayList; +import java.util.List; + +/** + * Mock事务上下文 + * + * @author criki + */ +@Deprecated +public class MockTransactionContext implements TransactionContext { + + @Getter + private static final List SNAPSHOT; + + static { + SNAPSHOT = new ArrayList<>(); + } + + /** + * 事务Id + */ + @Getter + private final int xid; + /** + * 事务隔离度 + */ + @Getter + private final TransactionIsolation isolation; + /** + * 存储创建它的管理器 + */ + private final TransactionManager manager; + /** + * 事务状态 + */ + @Getter + private TransactionStatus status; + + public MockTransactionContext() { + this.xid = 0; + this.status = TransactionStatus.COMMITTED; + this.isolation = TransactionIsolation.READ_UNCOMMITTED; + this.manager = null; + } + + /** + * 事务上下文 + * + * @param isolation 事务隔离度 + * @param manager 创建事务的管理器 + */ + public MockTransactionContext(int xid, TransactionIsolation isolation, TransactionManager manager) { + this.xid = xid; + this.status = TransactionStatus.PREPARING; + this.isolation = isolation; + this.manager = manager; + } + + @Override + public void start() { + this.status = TransactionStatus.ACTIVE; + if (manager != null) { + manager.changeTransactionStatus(xid, TransactionStatus.ACTIVE); + } + } + + @Override + public void commit() { + this.status = TransactionStatus.COMMITTED; + if (manager != null) { + manager.changeTransactionStatus(xid, TransactionStatus.COMMITTED); + } + } + + @Override + public void rollback() { + this.status = TransactionStatus.ABORTED; + if (manager != null) { + manager.changeTransactionStatus(xid, TransactionStatus.ABORTED); + } + } + + @Override + public void sharedLock(long uuid, String tableName) { + + } + + @Override + public void exclusiveLock(long uuid, String tableName) { + + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionManager.java b/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionManager.java new file mode 100644 index 0000000..5a12779 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionManager.java @@ -0,0 +1,81 @@ +package net.kaaass.rumbase.transaction.mock; + +import net.kaaass.rumbase.transaction.TransactionContext; +import net.kaaass.rumbase.transaction.TransactionIsolation; +import net.kaaass.rumbase.transaction.TransactionManager; +import net.kaaass.rumbase.transaction.TransactionStatus; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Mock事务管理器 + * + * @author criki + */ +@Deprecated +public class MockTransactionManager implements TransactionManager { + + /** + * 事务状态持久化文件名 + */ + private static final String LOG_FILE_NAME = "xid.log"; + + /** + * Mock事务数量存储 + */ + public static int TransactionSize; + + /** + * Mock事务状态持久化日志 + */ + public static Map XidLog; + + static { + XidLog = new HashMap<>(); + } + + /** + * 事务ID计数器 + */ + private final AtomicInteger SIZE; + + /** + * Mock事务管理器 + */ + public MockTransactionManager() { + this.SIZE = new AtomicInteger(0); + } + + /** + * 创建新事务 + * + * @param isolation 事务隔离度 + * @return 事务对象 + */ + @Override + public TransactionContext createTransactionContext(TransactionIsolation isolation) { + //获取最新事务id + int xid = SIZE.incrementAndGet(); + //更新日志中的SIZE + TransactionSize = SIZE.get(); + //更新日志中的事务状态 + changeTransactionStatus(xid, TransactionStatus.PREPARING); + + return new MockTransactionContext(xid, isolation, this); + } + + /** + * 改变日志中的事务状态 + * + * @param xid 事务id + * @param status 新事务状态 + */ + @Override + public void changeTransactionStatus(int xid, TransactionStatus status) { + XidLog.put(xid, status.getStatusId()); + } + + +} diff --git a/src/test/java/net/kaaass/rumbase/JBBPDemo.java b/src/test/java/net/kaaass/rumbase/JBBPDemo.java new file mode 100644 index 0000000..83a5289 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/JBBPDemo.java @@ -0,0 +1,84 @@ +package net.kaaass.rumbase; + +import com.igormaznitsa.jbbp.JBBPParser; +import com.igormaznitsa.jbbp.io.JBBPBitInputStream; +import com.igormaznitsa.jbbp.io.JBBPByteOrder; +import com.igormaznitsa.jbbp.io.JBBPOut; +import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; +import com.igormaznitsa.jbbp.model.JBBPFieldShort; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Arrays; + +/** + * JBBP库的使用示例 + */ +@Slf4j +public class JBBPDemo { + + /** + * 解析字节流 + */ + @Test + public void parseFromStream() throws IOException { + // 首次解析 + var parser = JBBPParser.prepare( + "ushort shortVal;" + + "int intVal;" + + "byte[4] bytes;" + ); + var stream = new ByteArrayInputStream( + new byte[]{0, 12, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + 'a', 'b', 'c', 'd', + 66, 0, 12}); + var result = parser.parse(stream); + Arrays.stream(result.getArray()).forEach(field -> + log.info("解析字段: {}", field.getFieldPath())); + log.info("bytes = {}", + result + .findFieldForNameAndType("bytes", JBBPFieldArrayByte.class) + .getArray()); + // 再次解析 + parser = JBBPParser.prepare( + "byte byteVal;" + + "short shortVal;" + ); + result = parser.parse(stream); + log.info("shortVal = {}", + result + .findFieldForNameAndType("shortVal", JBBPFieldShort.class) + .getAsInt()); + // + stream.close(); + } + + /** + * 不编写脚本进行解析 + */ + @Test + public void parseWithoutScript() throws IOException { + var data = new byte[]{1, 2, 0, 4, 0, 4, 5, 'c', 'h', -28, -72, -83}; + var stream = new JBBPBitInputStream(new ByteArrayInputStream(data)); + // 解析过程就是读入一个个字段 + log.info("读入short {}", stream.readUnsignedShort(JBBPByteOrder.LITTLE_ENDIAN)); + log.info("读入数组 {}", stream.readShortArray(2, JBBPByteOrder.BIG_ENDIAN)); + log.info("读入字符串 {}", stream.readString(JBBPByteOrder.BIG_ENDIAN)); + } + + /** + * 把数据写到字符串数组 + */ + @Test + public void writeToBinary() throws IOException { + var result = JBBPOut.BeginBin(). + Bit(1, 0, 0, 1, 1, 1). // 0b111001 + Short(233). + String("汉字"). + End().toByteArray(); + log.info("{}", result); + } +} diff --git a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java new file mode 100644 index 0000000..a78b7fc --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java @@ -0,0 +1,205 @@ +package net.kaaass.rumbase.dataitem; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.dataitem.exception.FileException; +import net.kaaass.rumbase.dataitem.exception.UUIDException; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * 对数据项管理部分进行测试 + * + * @author kaito + * @see net.kaaass.rumbase.dataitem.IItemStorage + */ + +@Slf4j +public class IItemStorageTest extends TestCase { + + /** + * 测试能否从已有文件中解析得到数据项管理器 + */ + public void testGetFromFile() throws FileException { + String fileName = "testGetFromFile.db"; + var itemStorage = ItemManager.fromFile(fileName); + + // 如果表中没有对应的文件,那么就抛出错误 + String failFileName = "error.db"; + try { + IItemStorage iItemStorage1 = ItemManager.fromFile(failFileName); + } catch (FileException f) { + log.error("Exception Error :", f); + } + } + + /** + * 测试能否新建文件并得到数据项管理器 + */ + public void testCreateFile() { + String fileName = "testCreateFile.db"; + byte[] metadata = new byte[1024]; + // 第一次执行的时候,表中没有数据,不会报错 + try { + var iItemStorage = ItemManager.createFile(fileName, metadata); + } catch (Exception e) { + e.printStackTrace(); + } + + try { + IItemStorage iItemStorage = ItemManager.createFile(fileName, metadata); + } catch (FileException e) { + log.error("Exception Error :", e); + } + } + + /** + * 进行插入的测试 + */ + public void testInsert() throws FileException { + String fileName = "testInsert.db"; + IItemStorage iItemStorage = ItemManager.fromFile(fileName); + byte[] bytes = new byte[]{1, 2, 3, 4}; + long uuid = iItemStorage.insertItem(bytes); + try { + assertEquals(bytes, iItemStorage.queryItemByUuid(uuid)); + } catch (UUIDException e) { + e.printStackTrace(); + } + } + + /** + * 对插入一个已分配UUID的测试 + */ + public void testInsertWithUUID() throws FileException { + String fileName = "testInsertWithUUID.db"; + IItemStorage iItemStorage = ItemManager.fromFile(fileName); + byte[] bytes = new byte[]{1, 2, 3, 4}; + long uuid = 50; + // 第一次插入,表中没有该UUID,可以正常执行 + iItemStorage.insertItemWithUuid(bytes, uuid); + try { + assertEquals(bytes, iItemStorage.queryItemByUuid(uuid)); + } catch (UUIDException e) { + e.printStackTrace(); + } + + // 第二次插入 + iItemStorage.insertItemWithUuid(bytes, uuid); + + } + + /** + * 对查询进行测试 + */ + public void testQuery() throws FileException { + String fileName = "testQuery.db"; + IItemStorage iItemStorage = ItemManager.fromFile(fileName); + byte[] bytes = new byte[]{1, 2, 3, 4}; + long uuid = iItemStorage.insertItem(bytes); + // 查询可以正常执行 + try { + assertEquals(bytes, iItemStorage.queryItemByUuid(uuid)); + } catch (UUIDException e) { + e.printStackTrace(); + } + // 找一个跟UUID不同的,也就是说不在数据库中的UUID进行查询 + long s = 1; + if (s == uuid) { + s += 1; + } + try { + var b = iItemStorage.queryItemByUuid(s); + } catch (UUIDException e) { + log.error("Exception Error :", e); + } + } + + /** + * 获取整个页的数据项进行测试 + */ + public void testQueryByPageID() throws FileException { + String fileName = "testQueryByPageID.db"; + IItemStorage iItemStorage = ItemManager.fromFile(fileName); + byte[] bytes = new byte[]{1, 2, 3, 4}; + long uuid = iItemStorage.insertItem(bytes); + + byte[] bytes1 = new byte[]{2, 3, 4, 5}; + long uuid1 = iItemStorage.insertItem(bytes1); + + Comparator comparator = new Comparator() { + @Override + public int compare(byte[] o1, byte[] o2) { + int length = Math.min(o1.length, o2.length); + for (int i = 0; i < length; i++) { + if (o2[i] > o1[i]) { + return -1; + } else if (o2[i] < o1[i]) { + return 1; + } + } + + return Integer.compare(o1.length, o2.length); + } + }; + + try { + List bs = new ArrayList<>(); + bs.add(bytes); + bs.add(bytes1); + bs.sort(comparator); + // 获取pageID对应的数据项,在这里Mock是获取所有list中的数据 + var result = iItemStorage.listItemByPageId(0); + result.sort(comparator); + assertEquals(bs, result); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 对更新进行测试 + */ + public void testUpdate() throws FileException { + String fileName = "testUpdate.db"; + IItemStorage iItemStorage = ItemManager.fromFile(fileName); + byte[] bytes = new byte[]{1, 2, 3, 4}; + long uuid = iItemStorage.insertItem(bytes); + // 正常情况下进行修改 + byte[] result = new byte[]{2, 3, 4, 5}; + try { + iItemStorage.updateItemByUuid(uuid, result); + byte[] bs = iItemStorage.queryItemByUuid(uuid); + assertEquals(bs, result); + } catch (UUIDException e) { + e.printStackTrace(); + } + // 修改一个不存在的UUID + long s = 1; + if (s == uuid) { + s += 1; + } + try { + iItemStorage.updateItemByUuid(s, result); + } catch (UUIDException e) { + e.printStackTrace(); + } + } + + /** + * 测试修改和获取表头信息 + */ + public void testMeta() throws FileException { + String fileName = "testMeta.db"; + IItemStorage iItemStorage = ItemManager.fromFile(fileName); + byte[] result = new byte[]{1, 2, 3, 4}; + + iItemStorage.setMetadata(result); + byte[] bs = iItemStorage.getMetadata(); + assertEquals(result, bs); + + } + +} diff --git a/src/test/java/net/kaaass/rumbase/index/IndexTest.java b/src/test/java/net/kaaass/rumbase/index/IndexTest.java new file mode 100644 index 0000000..27a89d4 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/index/IndexTest.java @@ -0,0 +1,127 @@ +package net.kaaass.rumbase.index; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.index.exception.IndexNotFoundException; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Random; + +/** + * 对索引部分进行测试 + * + * @author DoctorWei1314 + * @see net.kaaass.rumbase.index.Index + */ +@Slf4j +public class IndexTest extends TestCase { + /** + * 测试索引对象管理与拿取 + */ + public void testIndexManagement() { + // 测试索引是否存在,表示student表的id字段索引,table_name$field_name + assertFalse("don't exists such a index", Index.exists("student$id")); + + // 创建一个空索引,如果已经存在,则抛出异常 + try { + Index.createEmptyIndex("student$id"); + Index.createEmptyIndex("student$name"); + Index.createEmptyIndex("student$score"); + Index.createEmptyIndex("student$score"); + } catch (IndexAlreadyExistException e) { + log.error("Exception Error :", e); + } + + // 拿到这个索引,若没有则抛出异常 + try { + Index.getIndex("student$id"); + Index.getIndex("employee$id"); + } catch (IndexNotFoundException e) { + log.error("Exception Error :", e); + } + } + + /** + * 测试索引的插入与第一个迭代器功能 + */ + public void testInsert() { + Index testIndex = null; + var standardRand = new ArrayList(); + try { + testIndex = Index.createEmptyIndex("testInsert$id"); + } catch (IndexAlreadyExistException e) { + log.error("Exception Error :", e); + } + + for (int i = 0; i < 50; i++) { + var rand = new Random().nextLong(); + + standardRand.add(rand); + assert testIndex != null; + testIndex.insert(i, rand); + + standardRand.add(rand = new Random().nextLong()); + testIndex.insert(i, rand); + } + + // 测试数据是否符合预期 + int cnt = 0; + for (var pair : testIndex) { + assertEquals(standardRand.get(cnt).longValue(), + pair.getUuid()); + cnt++; + } + } + + /** + * 测试索引的查询功能 + */ + public void testQuery() { + Index testIndex = null; + var standardRand = new ArrayList(); + + try { + testIndex = Index.createEmptyIndex("testQuery$id"); + } catch (IndexAlreadyExistException e) { + log.error("Exception Error :", e); + } + + // 倒序添加若干随机数据 + for (int i = 5; i > 0; i--) { + var rand = new Random().nextLong(); + + standardRand.add(rand); + assert testIndex != null; + testIndex.insert(i, rand); + + standardRand.add(rand = new Random().nextLong()); + testIndex.insert(i, rand); + } + + // 打印当前索引情况 + for (var pair : testIndex) { + log.debug("{}", pair); + } + + // 测试 findFirst 方法 + // keyHash在内的迭代器 1122->334455 + Iterator it1 = testIndex.findFirst(3); + assertTrue(it1.hasNext()); + assertEquals(standardRand.get(2 * 2).longValue(), it1.next().getUuid()); + assertEquals(standardRand.get(2 * 2 + 1).longValue(), it1.next().getUuid()); + + // 测试 findUpperbound 方法 + // 不包括keyHash在内的迭代器 112233->4455 + Iterator it2 = testIndex.findUpperbound(3); + assertTrue(it2.hasNext()); + assertEquals(standardRand.get(2).longValue(), it2.next().getUuid()); + assertEquals(standardRand.get(2 + 1).longValue(), it2.next().getUuid()); + + // 测试 query 方法 + var results = testIndex.query(4); + assertTrue(results.contains(standardRand.get(2))); + assertTrue(results.contains(standardRand.get(2 + 1))); + } +} diff --git a/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java b/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java new file mode 100644 index 0000000..ee91045 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java @@ -0,0 +1,43 @@ +package net.kaaass.rumbase.page; + +import junit.framework.TestCase; + +import java.io.File; +import java.io.FileInputStream; + +import static org.junit.Assert.assertArrayEquals; + +/** + * 文件管理测试 + * + * @author XuanLaoYee + * @see net.kaaass.rumbase.page.PageStorage + */ +public class PageStorageTest extends TestCase { + public void testGet() { + try { + PageStorage pc = PageManager.fromFile("testFile"); + Page p0 = pc.get(0); + Page p3 = pc.get(3); + byte[] data0 = new byte[1024 * 4]; + byte[] data3 = new byte[1024 * 4]; + p0.getData().read(data0); + p3.getData().read(data3); + + byte[] testData0 = new byte[1024 * 4]; + for (int j = 0; j < 1024 * 4; j++) { + testData0[j] = (byte)5; + } + byte[] testData3 = new byte[1024 * 4]; + for (int j = 0; j < 1024 * 4; j++) { + testData3[j] = (byte)8; + } + assertArrayEquals(testData0,data0); + assertArrayEquals(testData3,data3); + + } catch (Exception e) { + e.printStackTrace(); + } + + } +} \ No newline at end of file diff --git a/src/test/java/net/kaaass/rumbase/page/PageTest.java b/src/test/java/net/kaaass/rumbase/page/PageTest.java new file mode 100644 index 0000000..a00de9b --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/page/PageTest.java @@ -0,0 +1,83 @@ +package net.kaaass.rumbase.page; + +import junit.framework.TestCase; + +import java.io.File; +import java.io.FileInputStream; + +import static org.junit.Assert.assertArrayEquals; + +/** + * 页管理测试 + * + * @author XuanLaoYee + * @see net.kaaass.rumbase.page.Page + */ +public class PageTest extends TestCase { + + public void testGetData() { + try { + PageStorage pc = PageManager.fromFile("testFile"); + Page p0 = pc.get(0); + Page p3 = pc.get(3); + byte[] data0 = new byte[1024 * 4]; + byte[] data3 = new byte[1024 * 4]; + p0.getData().read(data0); + p3.getData().read(data3); + byte[] testData0 = new byte[1024 * 4]; + for (int j = 0; j < 1024 * 4; j++) { + testData0[j] = (byte)5; + } + byte[] testData3 = new byte[1024 * 4]; + for (int j = 0; j < 1024 * 4; j++) { + testData3[j] = (byte)8; + } + assertArrayEquals(testData0,data0); + assertArrayEquals(testData3,data3); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void testWriteData() { + byte[] data = new byte[PageManager.PAGE_SIZE]; + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (i % 120); + } + + try { + PageStorage pc = PageManager.fromFile("testFile"); + Page p0 = pc.get(0); + //write之前需要先pin + p0.pin(); + p0.writeData(data); + //pin和unpin成对出现 + p0.unpin(); + assertArrayEquals(data, pc.get(0).getDataBytes()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void testPatchData() { + int offset = 99; + byte[] data = new byte[PageManager.PAGE_SIZE - offset]; + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (i % 120); + } + + try { + PageStorage pc = PageManager.fromFile("testFile"); + Page p0 = pc.get(0); + byte[] originalData = p0.getDataBytes(); + p0.patchData(offset, data); + byte[] newData = new byte[PageManager.PAGE_SIZE]; + System.arraycopy(originalData, 0, newData, 0, offset); + System.arraycopy(data, 0, newData, offset, data.length); + assertArrayEquals(newData, p0.getDataBytes()); + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java index 87c823c..583da7c 100644 --- a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java @@ -23,12 +23,12 @@ public void testQuery() { var storage = RecordManager.fromFile("test_query"); var context = TransactionContext.empty(); - var result = storage.queryOptional(context, UUID.randomUUID()); + var result = storage.queryOptional(context, UUID.randomUUID().getLeastSignificantBits()); assertTrue("unknown uuid should get empty", result.isEmpty()); try { - storage.query(context, UUID.randomUUID()); + storage.query(context, UUID.randomUUID().getLeastSignificantBits()); fail("unknown uuid should get exception"); } catch (RecordNotFoundException e) { log.error("Exception expected: ", e); diff --git a/src/test/java/net/kaaass/rumbase/recovery/IRecoveryTest.java b/src/test/java/net/kaaass/rumbase/recovery/IRecoveryTest.java new file mode 100644 index 0000000..a94f553 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/recovery/IRecoveryTest.java @@ -0,0 +1,35 @@ +package net.kaaass.rumbase.recovery; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertArrayEquals; + +/** + * TODO 文档 + */ +@Slf4j +public class IRecoveryTest extends TestCase { + + public void testBegin() { + IRecoveryStorage iRecoveryStorage = RecoveryManager.recovery("user.db"); + List l = new ArrayList<>(); + int xid = 1; + l.add(3); + l.add(2); + iRecoveryStorage.begin(xid, l); + + var content = iRecoveryStorage.getContent(); + String beginStr = "begin " + xid; + String snapStr = "snap " + l.toString(); + List result = new ArrayList<>(); + result.add(beginStr.getBytes()); + result.add(snapStr.getBytes()); + assertEquals(result.size(), content.size()); + assertArrayEquals(result.get(0), content.get(0)); + assertArrayEquals(result.get(1), content.get(1)); + } +} diff --git a/src/test/java/net/kaaass/rumbase/table/IFieldTest.java b/src/test/java/net/kaaass/rumbase/table/IFieldTest.java deleted file mode 100644 index 961d8ee..0000000 --- a/src/test/java/net/kaaass/rumbase/table/IFieldTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package net.kaaass.rumbase.table; - -import junit.framework.TestCase; -import net.kaaass.rumbase.table.mock.MockField; - -import java.util.ArrayList; -import java.util.Arrays; - -import static org.junit.Assert.assertArrayEquals; - -/** - * 字段结构测试 - * - * @author @KveinAxel - * @see net.kaaass.rumbase.table.IField - */ -public class IFieldTest extends TestCase { - public void testStrToValue() { - - IField field = MockField.ofFile("test_str2value_int", "id", FieldType.INT); - var value = field.strToValue("1"); - assertTrue("compatible value should parse successfully.", value.isPresent()); - assertEquals("compatible value should be parsed in a true way", 1, value.get().getValue()); - - IField field2 = MockField.ofFile("test_str2value_float", "id", FieldType.FLOAT); - var value2 = field2.strToValue("1.2"); - assertTrue("compatible value should be successful to parse.", value2.isPresent()); - assertEquals("compatible value should be parsed in a proper way", 1.2f, (float) value2.get().getValue()); - - IField field3 = MockField.ofFile("test_str2value_failed", "id", FieldType.INT); - var value3 = field3.strToValue("a"); - assertTrue("incompatible value should fail to parse", value3.isEmpty()); - } - - public void testValueToRaw() { - FieldValue fieldValue = new FieldValue<>(FieldType.INT); - fieldValue.setValue(1); - IField field = MockField.ofFile("test_value2raw_int", "id", FieldType.INT); - var raw = field.valueToRaw(fieldValue); - assertTrue("compatible value should be successful to transform.", raw.isPresent()); - assertArrayEquals("compatible value should be transform in a proper way", raw.get(), new byte[]{0x00, 0x00, 0x00, 0x01}); - - FieldValue fieldValue2 = new FieldValue<>(FieldType.FLOAT); - fieldValue2.setValue(1.2f); - IField field2 = MockField.ofFile("test_value2raw_float", "id", FieldType.FLOAT); - var raw2 = field2.valueToRaw(fieldValue2); - assertTrue("compatible value should be successful to transform.", raw2.isPresent()); - assertArrayEquals("compatible value should be transform in a proper way", raw2.get(), new byte[]{0x3f, (byte) 0x99, (byte) 0x99, (byte) 0x9a}); - - } - - public void testRawToValue() { - var bytes = new byte[]{0x00, 0x00, 0x00, 0x01}; - IField field = MockField.ofFile("test_raw2value_int", "id", FieldType.INT); - var value = field.rawToValue(bytes); - assertTrue("compatible value should be successful to transform.", value.isPresent()); - assertEquals("compatible value should be transform in a proper way", 1, (int) value.get().getValue()); - - var bytes2 = new byte[]{0x3f, (byte) 0x99, (byte) 0x99, (byte) 0x9a}; - IField field2 = MockField.ofFile("test_raw2value_float", "id", FieldType.FLOAT); - var value2 = field2.rawToValue(bytes2); - assertTrue("compatible value should be successful to transform.", value2.isPresent()); - assertEquals("compatible value should be transform in a proper way", 1.2f, (float) value2.get().getValue()); - - var bytes3 = new byte[]{0x00, 0x00, 0x00, 0x00, 0x01}; - IField field3 = MockField.ofFile("test_raw2value_failed", "id", FieldType.INT); - var value3 = field.rawToValue(bytes3); - assertTrue("incompatible value should be failed to transform.", value3.isEmpty()); - - } - - public void testSearch() { - - } - - public void testSearchRange() { - var fieldValue = new FieldValue(FieldType.INT); - var fieldValue2 = new FieldValue(FieldType.INT); - fieldValue.setValue(2); - fieldValue2.setValue(1); - IField field = MockField.ofFile("test_search_range_int", "id", FieldType.INT); - - var res = field.searchRange(fieldValue, fieldValue2); - assertEquals("bad seq can only get a 0 size list", 0, res.size()); - var res2 = field.searchRange(fieldValue2, fieldValue); - assertEquals("good seq can get the proper answer", null, res2); - - var fieldValue3 = new FieldValue(FieldType.FLOAT); - var fieldValue4 = new FieldValue(FieldType.FLOAT); - fieldValue3.setValue(2.1f); - fieldValue4.setValue(1.1f); - IField field2 = MockField.ofFile("test_search_range_float", "id", FieldType.INT); - - var res3 = field2.searchRange(fieldValue3, fieldValue4); - assertEquals("bad seq can only get a 0 size list", 0, res3.size()); - var res4 = field2.searchRange(fieldValue4, fieldValue3); - assertEquals("good seq can get the proper answer", null, res4); - - var fieldValue5 = new FieldValue(FieldType.INT); - var fieldValue6 = new FieldValue(FieldType.FLOAT); - fieldValue5.setValue(2); - fieldValue6.setValue(1.1f); - IField field3 = MockField.ofFile("test_search_range_failed", "id", FieldType.INT); - - var res5 = field3.searchRange(fieldValue5, fieldValue6); - assertEquals("incompatible can only get a 0 size list", 0, res5.size()); - var res6 = field3.searchRange(fieldValue6, fieldValue5); - assertEquals("incompatible can only get a 0 size list", 0, res6.size()); - - - } - - public void testParseSelf() { - // todo - } - - public void testPersistSelf() { - // todo - } -} diff --git a/src/test/java/net/kaaass/rumbase/table/ITableTest.java b/src/test/java/net/kaaass/rumbase/table/ITableTest.java deleted file mode 100644 index f1ff634..0000000 --- a/src/test/java/net/kaaass/rumbase/table/ITableTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package net.kaaass.rumbase.table; - -import junit.framework.TestCase; -import net.kaaass.rumbase.table.mock.MockField; -import net.kaaass.rumbase.table.mock.MockTable; - -import java.util.ArrayList; - -/** - * 表结构测试 - * - * @author @KveinAxel - * @see net.kaaass.rumbase.table.ITable - */ -public class ITableTest extends TestCase { - public void testParseSelf() { - // todo - } - - public void testPersistSelf() { - // todo - } - - public void testDelete() { - // todo - } - - public void testUpdate() { - // todo - } - - public void testRead() { - // todo - } - - public void testInsert() { - // todo - } - - public void testSearch() { - // todo - } - - public void testStrToEntry() { - var str = new ArrayList(); - str.add("1"); - str.add("1.2"); - str.add("3"); - - var fields = new ArrayList(); - fields.add(MockField.ofFile("id", "id", FieldType.INT)); - fields.add(MockField.ofFile("f", "f", FieldType.FLOAT)); - fields.add(MockField.ofFile("i", "i", FieldType.INT)); - var table = new MockTable("mockTable", fields); - - var res = table.strToEntry(str); - assertTrue("compatible fields can be transformed", res.isPresent()); - var entry = res.get(); - assertEquals("compatible fields can be transformed properly", entry.get("id").getValue(), 1); - assertEquals("compatible fields can be transformed properly", entry.get("f").getValue(), 1.2f); - assertEquals("compatible fields can be transformed properly", entry.get("i").getValue(), 3); - assertNull("dummy fields can not exist", entry.get("dummy")); - - str.add("4"); - var res2 = table.strToEntry(str); - assertTrue("incompatible type can't be transformed properly", res2.isEmpty()); - } - - public void testParseEntry() { - var fields = new ArrayList(); - fields.add(MockField.ofFile("id", "id", FieldType.INT)); - fields.add(MockField.ofFile("f", "f", FieldType.FLOAT)); - fields.add(MockField.ofFile("i", "i", FieldType.INT)); - var table = new MockTable("mockTable", fields); - - var bytes = new byte[]{ - 0x00, 0x00, 0x00, 0x01, 0x3f, - (byte) 0x99, (byte) 0x99, (byte) 0x9a, - 0x00, 0x00, 0x00, 0x03 - }; - - var res = table.parseEntry(bytes); - assertTrue("compatible fields can be transformed properly", res.isPresent()); - var entry = res.get(); - assertEquals("compatible fields can be transformed properly", 1, entry.get("id").getValue()); - assertEquals("compatible fields can be transformed properly", 1.2f, entry.get("f").getValue()); - assertEquals("compatible fields can be transformed properly", 3, entry.get("i").getValue()); - - var bytes2 = new byte[]{ - 0x00, 0x00, 0x00, 0x01, 0x3f, - (byte) 0x99, (byte) 0x99, (byte) 0x9a, - 0x00, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x03 - }; - var res2 = table.parseEntry(bytes2); - assertTrue("dummy fields can not exist", res2.isEmpty()); - - - } - - -} diff --git a/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java b/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java deleted file mode 100644 index 65c894a..0000000 --- a/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package net.kaaass.rumbase.table; - -import junit.framework.TestCase; - -/** - * 表管理器的测试 - * - * @see net.kaaass.rumbase.table.TableManager - * @author @KveinAxel - */ -public class TableManagerTest extends TestCase { - - public void testBegin() { - // todo - } - - public void testCommit() { - // todo - - } - - public void testAbort() { - // todo - - } - - public void testShowTables() { - // todo - - } - - public void testCreateTable() { - // todo - - } - - public void testInsert() { - // todo - - } - - public void testDelete() { - // todo - - } - - public void testUpdate() { - // todo - - } - - public void testRead() { - // todo - - } - - public void testSearch() { - // todo - - } - - -} diff --git a/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java b/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java new file mode 100644 index 0000000..9422326 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java @@ -0,0 +1,99 @@ +package net.kaaass.rumbase.transaction; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.transaction.mock.MockTransactionManager; + +/** + * 测试事务上下文 + * + * @author criki + */ +@Slf4j +public class TransactionContextTest extends TestCase { + + /** + * 测试创建事务 + */ + public void testCreateTransaction() { + var manager = new MockTransactionManager(); + + // 空事务 + var emptyTransaction = TransactionContext.empty(); + assertEquals(0, emptyTransaction.getXid()); + assertEquals(TransactionStatus.COMMITTED, emptyTransaction.getStatus()); + assertEquals(TransactionIsolation.READ_UNCOMMITTED, emptyTransaction.getIsolation()); + + // 普通事务 + var transaction = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); + assertEquals(1, transaction.getXid()); + assertEquals(TransactionStatus.PREPARING, transaction.getStatus()); + assertEquals(TransactionIsolation.READ_COMMITTED, transaction.getIsolation()); + + } + + /** + * 测试事务变化 + */ + public void testChangeStatus() { + var manager = new MockTransactionManager(); + var committedTransaction = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); + // 事务初始状态 + assertEquals("new transaction's default status should be PREPARING", TransactionStatus.PREPARING, committedTransaction.getStatus()); + + // 事务开始 + committedTransaction.start(); + assertEquals("starting transaction's default status should be ACTIVE", TransactionStatus.ACTIVE, committedTransaction.getStatus()); + + // 事务提交 + committedTransaction.commit(); + assertEquals("committed transaction's default status should be COMMITTED", TransactionStatus.COMMITTED, committedTransaction.getStatus()); + + // 事务中止 + var abortedTransaction = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); + abortedTransaction.start(); + abortedTransaction.rollback(); + assertEquals("aborted transaction's default status should be ABORTED", TransactionStatus.ABORTED, abortedTransaction.getStatus()); + } + + /** + * 测试事务持久化 + */ + public void testTransactionPersistence() { + var manager = new MockTransactionManager(); + // 事务创建,事务状态记录数改变 + var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); + assertEquals(1, MockTransactionManager.TransactionSize); + + var transaction2 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); + assertEquals(2, MockTransactionManager.TransactionSize); + + // 事务状态持久化 + assertEquals(TransactionStatus.PREPARING.getStatusId(), (byte) MockTransactionManager.XidLog.get(transaction1.getXid())); + assertEquals(TransactionStatus.PREPARING.getStatusId(), (byte) MockTransactionManager.XidLog.get(transaction2.getXid())); + + transaction1.start(); + assertEquals(TransactionStatus.ACTIVE.getStatusId(), (byte) MockTransactionManager.XidLog.get(transaction1.getXid())); + + transaction1.commit(); + assertEquals(TransactionStatus.COMMITTED.getStatusId(), (byte) MockTransactionManager.XidLog.get(transaction1.getXid())); + + transaction2.start(); + transaction2.rollback(); + assertEquals(TransactionStatus.ABORTED.getStatusId(), (byte) MockTransactionManager.XidLog.get(transaction2.getXid())); + } + + /** + * 测试事务上锁 + */ + public void testAddLock() { + var manager = new MockTransactionManager(); + var transaction = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); + String tableName = "test"; + // 加共享锁 + transaction.sharedLock(1, tableName); + + // 加排他锁 + transaction.exclusiveLock(2, tableName); + } +} From 634e36719a03242c5f1914e9a7497f222026be51 Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Tue, 12 Jan 2021 19:56:36 +0800 Subject: [PATCH 04/18] =?UTF-8?q?[M]=E4=BF=AE=E6=AD=A3dataitem=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=BC=82=E5=B8=B8=E5=91=BD=E5=90=8D=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20(#5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修改异常名称 * 修正编译失败 Co-authored-by: xiaoxineryi <529086017@qq.com> --- .../kaaass/rumbase/dataitem/ItemManager.java | 19 ++++++---------- .../dataitem/exception/ItemException.java | 22 +++++++++++++++++++ .../rumbase/dataitem/IItemStorageTest.java | 10 ++++++--- 3 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 src/main/java/net/kaaass/rumbase/dataitem/exception/ItemException.java diff --git a/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java b/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java index ff4064d..4f82621 100644 --- a/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java +++ b/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java @@ -1,7 +1,7 @@ package net.kaaass.rumbase.dataitem; -import net.kaaass.rumbase.dataitem.exception.FileException; import net.kaaass.rumbase.dataitem.mock.MockItemStorage; +import net.kaaass.rumbase.page.exception.FileException; import java.util.HashMap; import java.util.Map; @@ -22,10 +22,10 @@ public class ItemManager { * @param fileName 文件名 * @return 数据项管理器,用于管理数据项 */ - public static IItemStorage fromFile(String fileName) throws FileException { + public static IItemStorage fromFile(String fileName) throws FileException{ String errorFileName = "error.db"; if (errorFileName.equals(fileName)) { - throw new FileException(2); + throw new FileException(1); } if (maps.containsKey(fileName)) { @@ -47,14 +47,9 @@ public static IItemStorage fromFile(String fileName) throws FileException { */ public static IItemStorage createFile(String fileName, byte[] metadata) throws FileException { // 如果文件已经存在,那么就抛出文件已存在异常 - if (maps.containsKey(fileName)) { - throw new FileException(1); - } else { - // 若文件不存在,则创建文件。 - IItemStorage iItemStorage = MockItemStorage.ofNewFile(fileName, metadata); - maps.put(fileName, iItemStorage); - return iItemStorage; - } - + // 若文件不存在,则创建文件。 + IItemStorage iItemStorage = MockItemStorage.ofNewFile(fileName, metadata); + maps.put(fileName, iItemStorage); + return iItemStorage; } } diff --git a/src/main/java/net/kaaass/rumbase/dataitem/exception/ItemException.java b/src/main/java/net/kaaass/rumbase/dataitem/exception/ItemException.java new file mode 100644 index 0000000..b87b578 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/dataitem/exception/ItemException.java @@ -0,0 +1,22 @@ +package net.kaaass.rumbase.dataitem.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * 对数据库进行解析时出现的异常错误 + * + * @author kaito + */ +public class ItemException extends RumbaseException { + public static final Map REASONS = new HashMap<>() {{ + put(1, "要创建的文件已存在"); + put(2, "查找的文件不存在"); + }}; + + public ItemException(int subID) { + super(6001, subID, REASONS.get(subID)); + } +} diff --git a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java index a78b7fc..fbbdde6 100644 --- a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java @@ -2,8 +2,8 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; -import net.kaaass.rumbase.dataitem.exception.FileException; import net.kaaass.rumbase.dataitem.exception.UUIDException; +import net.kaaass.rumbase.page.exception.FileException; import java.util.ArrayList; import java.util.Comparator; @@ -22,9 +22,13 @@ public class IItemStorageTest extends TestCase { /** * 测试能否从已有文件中解析得到数据项管理器 */ - public void testGetFromFile() throws FileException { + public void testGetFromFile() { String fileName = "testGetFromFile.db"; - var itemStorage = ItemManager.fromFile(fileName); + try { + var itemStorage = ItemManager.fromFile(fileName); + } catch (FileException e) { + e.printStackTrace(); + } // 如果表中没有对应的文件,那么就抛出错误 String failFileName = "error.db"; From 143a463be3e3ac3062a683fc61de32967f2c5e22 Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Wed, 13 Jan 2021 19:34:10 +0800 Subject: [PATCH 05/18] =?UTF-8?q?[M]dataitem=E3=80=81index=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E8=B0=83=E6=95=B4=EF=BC=8C=E9=94=99=E8=AF=AF=E5=9F=BA?= =?UTF-8?q?=E7=B1=BB=E5=A2=9E=E5=8A=A0=E9=94=99=E8=AF=AF=E5=8E=9F=E5=9B=A0?= =?UTF-8?q?=20(#6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 表创建、表头解析以及页头解析 * 索引审计修改 * RumbaseException增加错误原因 Co-authored-by: xiaoxineryi <529086017@qq.com> Co-authored-by: DctorWei1314 <61504793+DctorWei1314@users.noreply.github.com> --- .../kaaass/rumbase/dataitem/IItemStorage.java | 10 +- .../kaaass/rumbase/dataitem/ItemManager.java | 25 +- .../kaaass/rumbase/dataitem/ItemStorage.java | 242 ++++++++++++++++++ .../dataitem/exception/FileException.java | 22 -- .../dataitem/mock/MockItemStorage.java | 11 +- .../rumbase/exception/RumbaseException.java | 14 + .../java/net/kaaass/rumbase/index/Pair.java | 3 +- .../exception/IndexAlreadyExistException.java | 3 +- .../exception/IndexNotFoundException.java | 3 +- .../rumbase/index/mock/MockBtreeIndex.java | 5 +- .../rumbase/index/mock/MockIterator.java | 3 + .../rumbase/dataitem/IItemStorageTest.java | 59 +++-- 12 files changed, 331 insertions(+), 69 deletions(-) create mode 100644 src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java delete mode 100644 src/main/java/net/kaaass/rumbase/dataitem/exception/FileException.java diff --git a/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java b/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java index 2f57144..52cb62c 100644 --- a/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java +++ b/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java @@ -1,7 +1,9 @@ package net.kaaass.rumbase.dataitem; import net.kaaass.rumbase.dataitem.exception.UUIDException; +import net.kaaass.rumbase.transaction.TransactionContext; +import java.io.IOException; import java.util.List; /** @@ -16,7 +18,7 @@ public interface IItemStorage { * @param item 数据项 * @return 返回数据项的UUID */ - long insertItem(byte[] item); + long insertItem(TransactionContext txContext, byte[] item) throws IOException; /** * 插入一个有UUID的数据项,唯一使用的地方是日志恢复时使用 @@ -28,7 +30,7 @@ public interface IItemStorage { * @param item 数据项 * @param uuid 编号 */ - void insertItemWithUuid(byte[] item, long uuid); + void insertItemWithUuid(TransactionContext txContext,byte[] item, long uuid); /** * 通过UUID查询数据项 @@ -55,7 +57,7 @@ public interface IItemStorage { * @param item 数据项 * @throws UUIDException 没有找到对应UUID的异常 */ - void updateItemByUuid(long uuid, byte[] item) throws UUIDException; + void updateItemByUuid(TransactionContext txContext,long uuid, byte[] item) throws UUIDException; /** * 获得数据项存储的元数据(可以用于头) @@ -69,7 +71,7 @@ public interface IItemStorage { * * @param metadata 头信息 */ - void setMetadata(byte[] metadata); + void setMetadata(TransactionContext txContext,byte[] metadata); /** * 清理多余的数据项,空间清理时使用。 diff --git a/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java b/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java index 4f82621..9e6b33c 100644 --- a/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java +++ b/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java @@ -1,8 +1,13 @@ package net.kaaass.rumbase.dataitem; + import net.kaaass.rumbase.dataitem.mock.MockItemStorage; import net.kaaass.rumbase.page.exception.FileException; +import net.kaaass.rumbase.page.exception.PageException; +import net.kaaass.rumbase.transaction.TransactionContext; +import net.kaaass.rumbase.transaction.mock.MockTransactionContext; +import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -22,10 +27,10 @@ public class ItemManager { * @param fileName 文件名 * @return 数据项管理器,用于管理数据项 */ - public static IItemStorage fromFile(String fileName) throws FileException{ + public static IItemStorage fromFile(String fileName) throws FileException, IOException, PageException { String errorFileName = "error.db"; if (errorFileName.equals(fileName)) { - throw new FileException(1); + throw new FileException(2); } if (maps.containsKey(fileName)) { @@ -42,14 +47,20 @@ public static IItemStorage fromFile(String fileName) throws FileException{ * * @param fileName 文件名 * @param metadata 上层提供的表头信息 + * @param txContext 对应的事务名 * @return 数据项管理器 * @throws FileException 想新建的文件已经存在的异常 */ - public static IItemStorage createFile(String fileName, byte[] metadata) throws FileException { + public static IItemStorage createFile(TransactionContext txContext ,String fileName, byte[] metadata) throws FileException, IOException, PageException { // 如果文件已经存在,那么就抛出文件已存在异常 - // 若文件不存在,则创建文件。 - IItemStorage iItemStorage = MockItemStorage.ofNewFile(fileName, metadata); - maps.put(fileName, iItemStorage); - return iItemStorage; + if (maps.containsKey(fileName)) { + throw new FileException(1); + } else { + // 若文件不存在,则创建文件。 + IItemStorage iItemStorage = MockItemStorage.ofNewFile(txContext,fileName, metadata); + maps.put(fileName, iItemStorage); + return iItemStorage; + } + } } diff --git a/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java b/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java new file mode 100644 index 0000000..d4c5160 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java @@ -0,0 +1,242 @@ +package net.kaaass.rumbase.dataitem; + +import com.igormaznitsa.jbbp.JBBPParser; +import com.igormaznitsa.jbbp.mapper.Bin; +import net.kaaass.rumbase.dataitem.exception.UUIDException; +import net.kaaass.rumbase.page.Page; +import net.kaaass.rumbase.page.PageManager; +import net.kaaass.rumbase.page.PageStorage; +import net.kaaass.rumbase.page.exception.FileException; +import net.kaaass.rumbase.page.exception.PageException; +import net.kaaass.rumbase.recovery.IRecoveryStorage; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.io.IOException; +import java.util.List; + +/** + * 数据项管理器的具体实现 + * + *

+ * 每个表的头信息都是一致的,为 + * |头信息标志位:1234(共4字节)|当前可用的第一个空闲页(2字节)|是否写入表头信息(1字节),写入为123|头信息所对应的UUID(8字节) + * + * 同时 + *

+ * @author kaito + */ +public class ItemStorage implements IItemStorage { + + private String fileName; + /** + * 当前第一个空闲的页,用于插入时作为起始页来进行操作。 + */ + private int tempFreePage; + /** + * 表信息头对应的UUID + */ + private long headerUuid; + /** + * 内部维护一个对应该文件的页管理器 + */ + private PageStorage pageStorage; + /** + * 维护一个日志管理器 + */ + private IRecoveryStorage recoveryStorage; + + + public ItemStorage(String fileName, int tempFreePage, long headerUuid,PageStorage pageStorage) { + this.fileName = fileName; + this.tempFreePage = tempFreePage; + this.headerUuid = headerUuid; + this.pageStorage = pageStorage; + } + + /** + * 判断有没有头部标志 + * + *

+ * 获取表头的前四个字节数据,若是1234则表示是表头,解析后面的数据,否则就认为该表没有被初始化 + *

+ * @param header 第一页的Page对象 + * @return 是否是表的第一页 + */ + private static boolean checkTableHeader(Page header){ + var data = header.getData(); + byte[] flag = new byte[4]; + try { + data.read(flag); + } catch (IOException e) { + e.printStackTrace(); + } + return flag[0] == 1 && flag[1] == 2 && flag[2] == 3 && flag[3] == 4; + } + + /** + * 打开相应的文件并读取头信息,如果没有对应的头信息就进行初始化 + * @param fileName 文件名 + * @return 解析或新建得到的数据项管理器对象 + */ + public static IItemStorage ofFile(String fileName) throws FileException, IOException, PageException { + var pageStorage = PageManager.fromFile(fileName); + var header = pageStorage.get(0); + if (checkTableHeader(header)){ + // 如果表头标志存在,就解析对应表头信息 + var h = JBBPParser.prepare("long headerFlag;int tempFreePage;byte hasHeaderInfo;long headerUuid;").parse(header.getData()).mapTo(new TableHeader()); + return new ItemStorage(fileName,h.tempFreePage,h.headerUuid,pageStorage); + }else { + // 若表头标志不存在,就初始化对应的表信息。 + // 只初始化headerFlag和tempFreePage,表头信息位置统一由setMetadata来实现 + byte[] bytes = new byte[]{1,2,3,4,0,1}; + header.patchData(0,bytes); + return new ItemStorage(fileName,1,0,pageStorage); + } + } + + /** + * 创建新的表,初始化相应数据,并且保存头信息。 + * @param fileName 文件名 + * @param metadata 表头信息 + * @return 数据项管理器 + */ + public static IItemStorage ofNewFile(TransactionContext txContext ,String fileName,byte[] metadata) throws IOException, FileException, PageException { + var pageStorage = ItemStorage.ofFile(fileName); + pageStorage.setMetadata(txContext,metadata); + return pageStorage; + } + + private PageHeader getPageHeader(Page page) throws IOException { + var data = page.getData(); + var pageHeader = JBBPParser.prepare("long pageFlag;int pageId;long lsn;int leftSpace;int recordNumber;" + + "Item[recordNumber]{int size;long uuid;int offset;}").parse(data).mapTo(new PageHeader()); + return pageHeader; + } + + + @Override + public long insertItem(TransactionContext txContext, byte[] item) throws IOException { + var page = pageStorage.get(this.tempFreePage); + var pageHeader = getPageHeader(page); + + return 0; + } + + @Override + public void insertItemWithUuid(TransactionContext txContext,byte[] item, long uuid) { + + } + + @Override + public byte[] queryItemByUuid(long uuid) throws UUIDException { + return new byte[0]; + } + + @Override + public List listItemByPageId(int pageId) { + return null; + } + + @Override + public void updateItemByUuid(TransactionContext txContext,long uuid, byte[] item) throws UUIDException { + + } + + @Override + public byte[] getMetadata() { + return new byte[0]; + } + + @Override + public void setMetadata(TransactionContext txContext,byte[] metadata) { + + } + + @Override + public void removeItems(List uuids) { + + } +} + +/** + * 表头 + */ +class TableHeader { + /** + * 是否是表头的标志 + */ + @Bin + long headerFlag; + /** + * 第一个可使用的空闲页编号 + */ + @Bin + int tempFreePage; + /** + * 是否有表头信息 + */ + @Bin + byte hasHeaderInfo; + /** + * 表头信息对应的UUID + */ + @Bin + long headerUuid; +} + +/** + * 每个数据项对应的相关信息 + */ +class Item{ + /** + * 数据项大小 + */ + @Bin + int size; + /** + * 数据项编号 + */ + @Bin + long uuid; + /** + * 页内偏移 + */ + @Bin + int offset; +} + +/** + * 页头 + */ +class PageHeader{ + /** + * 页头标志 + */ + @Bin + long pageFlag; + /** + * 当前页号 + */ + @Bin + int pageId; + /** + * 日志记录编号 + */ + @Bin + long lsn; + /** + * 剩余空间大小 + */ + @Bin + int leftSpace; + /** + * 页内记录总数 + */ + @Bin + int recordNumber; + /** + * 页内记录的相关信息 + */ + @Bin + Item[] item; +} \ No newline at end of file diff --git a/src/main/java/net/kaaass/rumbase/dataitem/exception/FileException.java b/src/main/java/net/kaaass/rumbase/dataitem/exception/FileException.java deleted file mode 100644 index 429a233..0000000 --- a/src/main/java/net/kaaass/rumbase/dataitem/exception/FileException.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.kaaass.rumbase.dataitem.exception; - -import net.kaaass.rumbase.exception.RumbaseException; - -import java.util.HashMap; -import java.util.Map; - -/** - * 在创建、打开数据库时 - * - * @author kaito - */ -public class FileException extends RumbaseException { - public static final Map REASONS = new HashMap<>() {{ - put(1, "要创建的文件已存在"); - put(2, "查找的文件不存在"); - }}; - - public FileException(int subID) { - super(6001, subID, REASONS.get(subID)); - } -} diff --git a/src/main/java/net/kaaass/rumbase/dataitem/mock/MockItemStorage.java b/src/main/java/net/kaaass/rumbase/dataitem/mock/MockItemStorage.java index a4f555a..1a6e86f 100644 --- a/src/main/java/net/kaaass/rumbase/dataitem/mock/MockItemStorage.java +++ b/src/main/java/net/kaaass/rumbase/dataitem/mock/MockItemStorage.java @@ -3,6 +3,7 @@ import lombok.Data; import net.kaaass.rumbase.dataitem.IItemStorage; import net.kaaass.rumbase.dataitem.exception.UUIDException; +import net.kaaass.rumbase.transaction.TransactionContext; import java.util.*; @@ -72,14 +73,14 @@ public static IItemStorage ofFile(String fileName) { * @param tableHeader 表头信息 * @return 返回数据项管理器 */ - public static IItemStorage ofNewFile(String fileName, byte[] tableHeader) { + public static IItemStorage ofNewFile(TransactionContext txContext ,String fileName, byte[] tableHeader) { // TODO: 因为是新建的文件,所以需要给文件头写入头信息数据。 return new MockItemStorage(fileName, 0, 0); } @Override - public long insertItem(byte[] item) { + public long insertItem(TransactionContext txContext,byte[] item) { Random ran = new Random(); long r = ran.nextLong(); maps.put(r, item); @@ -87,7 +88,7 @@ public long insertItem(byte[] item) { } @Override - public void insertItemWithUuid(byte[] item, long uuid) { + public void insertItemWithUuid(TransactionContext txContext,byte[] item, long uuid) { maps.put(uuid, item); } @@ -106,7 +107,7 @@ public List listItemByPageId(int pageId) { } @Override - public void updateItemByUuid(long uuid, byte[] item) throws UUIDException { + public void updateItemByUuid(TransactionContext txContext,long uuid, byte[] item) throws UUIDException { if (maps.containsKey(uuid)) { maps.put(uuid, item); } else { @@ -120,7 +121,7 @@ public byte[] getMetadata() { } @Override - public void setMetadata(byte[] metadata) { + public void setMetadata(TransactionContext txContext, byte[] metadata) { this.meta = metadata; } diff --git a/src/main/java/net/kaaass/rumbase/exception/RumbaseException.java b/src/main/java/net/kaaass/rumbase/exception/RumbaseException.java index deaeba8..4f6a659 100644 --- a/src/main/java/net/kaaass/rumbase/exception/RumbaseException.java +++ b/src/main/java/net/kaaass/rumbase/exception/RumbaseException.java @@ -46,4 +46,18 @@ public RumbaseException(int mainId, int subId, String reason) { this.mainId = mainId; this.subId = subId; } + + /** + * 构造Rumbase异常 + * + * @param mainId 主错误号 + * @param subId 子错误号 + * @param reason 错误原因 + * @param cause 源错误 + */ + public RumbaseException(int mainId, int subId, String reason, Throwable cause) { + super(String.format("E%d-%d: %s", mainId, subId, reason), cause); + this.mainId = mainId; + this.subId = subId; + } } diff --git a/src/main/java/net/kaaass/rumbase/index/Pair.java b/src/main/java/net/kaaass/rumbase/index/Pair.java index b426567..e361483 100644 --- a/src/main/java/net/kaaass/rumbase/index/Pair.java +++ b/src/main/java/net/kaaass/rumbase/index/Pair.java @@ -4,7 +4,8 @@ import lombok.Data; /** - * TODO 文档 + * 用于保存索引中的key-uuid对,上层模块可以根据key的值判断是否决定停止迭代,以获得想要的范围数据 + * @author 无索魏 */ @Data @AllArgsConstructor diff --git a/src/main/java/net/kaaass/rumbase/index/exception/IndexAlreadyExistException.java b/src/main/java/net/kaaass/rumbase/index/exception/IndexAlreadyExistException.java index f0fd104..1c9a4c3 100644 --- a/src/main/java/net/kaaass/rumbase/index/exception/IndexAlreadyExistException.java +++ b/src/main/java/net/kaaass/rumbase/index/exception/IndexAlreadyExistException.java @@ -6,7 +6,8 @@ import java.util.Map; /** - * TODO 文档 + * 当根据文件名创建一个新的空索引时,如果相应的文件名已经存在了索引,则不能创建新的空索引,并抛出此异常 + * @author 无索魏 */ public class IndexAlreadyExistException extends RumbaseException { diff --git a/src/main/java/net/kaaass/rumbase/index/exception/IndexNotFoundException.java b/src/main/java/net/kaaass/rumbase/index/exception/IndexNotFoundException.java index c485f0e..e7cdf60 100644 --- a/src/main/java/net/kaaass/rumbase/index/exception/IndexNotFoundException.java +++ b/src/main/java/net/kaaass/rumbase/index/exception/IndexNotFoundException.java @@ -6,7 +6,8 @@ import java.util.Map; /** - * TODO 文档 + * 当加载一个索引时,如果索引实现不存在相应的持久化信息,则无法加载,并抛出此异常 + * @author 无索魏 */ public class IndexNotFoundException extends RumbaseException { /** diff --git a/src/main/java/net/kaaass/rumbase/index/mock/MockBtreeIndex.java b/src/main/java/net/kaaass/rumbase/index/mock/MockBtreeIndex.java index 4c04748..a37d75f 100644 --- a/src/main/java/net/kaaass/rumbase/index/mock/MockBtreeIndex.java +++ b/src/main/java/net/kaaass/rumbase/index/mock/MockBtreeIndex.java @@ -7,12 +7,15 @@ import java.util.*; +/** + * @author 无索魏 + */ @RequiredArgsConstructor public class MockBtreeIndex implements Index { /** * 内存中创建的MockBtreeIndex */ - public static final Map MOCK_BTREE_INDEX_MAP = new HashMap<>(); + public static Map MOCK_BTREE_INDEX_MAP = new HashMap<>(); @Getter private HashMap> hashMap = new HashMap<>(); diff --git a/src/main/java/net/kaaass/rumbase/index/mock/MockIterator.java b/src/main/java/net/kaaass/rumbase/index/mock/MockIterator.java index 957604f..239114a 100644 --- a/src/main/java/net/kaaass/rumbase/index/mock/MockIterator.java +++ b/src/main/java/net/kaaass/rumbase/index/mock/MockIterator.java @@ -6,6 +6,9 @@ import java.util.List; import java.util.Map; +/** + * @author 无索魏 + */ public class MockIterator implements Iterator { private Iterator>> indexIterator; private Iterator tempIterator; diff --git a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java index fbbdde6..b1a3774 100644 --- a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java @@ -4,7 +4,11 @@ import lombok.extern.slf4j.Slf4j; import net.kaaass.rumbase.dataitem.exception.UUIDException; import net.kaaass.rumbase.page.exception.FileException; +import net.kaaass.rumbase.page.exception.PageException; +import net.kaaass.rumbase.transaction.TransactionContext; +import net.kaaass.rumbase.transaction.mock.MockTransactionContext; +import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -22,14 +26,9 @@ public class IItemStorageTest extends TestCase { /** * 测试能否从已有文件中解析得到数据项管理器 */ - public void testGetFromFile() { + public void testGetFromFile() throws FileException, IOException, PageException { String fileName = "testGetFromFile.db"; - try { - var itemStorage = ItemManager.fromFile(fileName); - } catch (FileException e) { - e.printStackTrace(); - } - + var itemStorage = ItemManager.fromFile(fileName); // 如果表中没有对应的文件,那么就抛出错误 String failFileName = "error.db"; try { @@ -43,18 +42,19 @@ public void testGetFromFile() { * 测试能否新建文件并得到数据项管理器 */ public void testCreateFile() { + TransactionContext txContext = new MockTransactionContext(); String fileName = "testCreateFile.db"; byte[] metadata = new byte[1024]; // 第一次执行的时候,表中没有数据,不会报错 try { - var iItemStorage = ItemManager.createFile(fileName, metadata); + var iItemStorage = ItemManager.createFile(txContext,fileName, metadata); } catch (Exception e) { e.printStackTrace(); } try { - IItemStorage iItemStorage = ItemManager.createFile(fileName, metadata); - } catch (FileException e) { + IItemStorage iItemStorage = ItemManager.createFile(txContext,fileName, metadata); + } catch (Exception e) { log.error("Exception Error :", e); } } @@ -62,11 +62,12 @@ public void testCreateFile() { /** * 进行插入的测试 */ - public void testInsert() throws FileException { + public void testInsert() throws FileException, IOException, PageException { String fileName = "testInsert.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; - long uuid = iItemStorage.insertItem(bytes); + TransactionContext txContext = new MockTransactionContext(); + long uuid = iItemStorage.insertItem(txContext,bytes); try { assertEquals(bytes, iItemStorage.queryItemByUuid(uuid)); } catch (UUIDException e) { @@ -77,13 +78,14 @@ public void testInsert() throws FileException { /** * 对插入一个已分配UUID的测试 */ - public void testInsertWithUUID() throws FileException { + public void testInsertWithUUID() throws FileException, IOException, PageException { String fileName = "testInsertWithUUID.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; long uuid = 50; + TransactionContext txContext = new MockTransactionContext(); // 第一次插入,表中没有该UUID,可以正常执行 - iItemStorage.insertItemWithUuid(bytes, uuid); + iItemStorage.insertItemWithUuid(txContext,bytes, uuid); try { assertEquals(bytes, iItemStorage.queryItemByUuid(uuid)); } catch (UUIDException e) { @@ -91,18 +93,19 @@ public void testInsertWithUUID() throws FileException { } // 第二次插入 - iItemStorage.insertItemWithUuid(bytes, uuid); + iItemStorage.insertItemWithUuid(txContext,bytes, uuid); } /** * 对查询进行测试 */ - public void testQuery() throws FileException { + public void testQuery() throws FileException, IOException, PageException { String fileName = "testQuery.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; - long uuid = iItemStorage.insertItem(bytes); + TransactionContext txContext = new MockTransactionContext(); + long uuid = iItemStorage.insertItem(txContext,bytes); // 查询可以正常执行 try { assertEquals(bytes, iItemStorage.queryItemByUuid(uuid)); @@ -124,14 +127,15 @@ public void testQuery() throws FileException { /** * 获取整个页的数据项进行测试 */ - public void testQueryByPageID() throws FileException { + public void testQueryByPageID() throws FileException, IOException, PageException { String fileName = "testQueryByPageID.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; - long uuid = iItemStorage.insertItem(bytes); + TransactionContext txContext = new MockTransactionContext(); + long uuid = iItemStorage.insertItem(txContext,bytes); byte[] bytes1 = new byte[]{2, 3, 4, 5}; - long uuid1 = iItemStorage.insertItem(bytes1); + long uuid1 = iItemStorage.insertItem(txContext,bytes1); Comparator comparator = new Comparator() { @Override @@ -166,15 +170,16 @@ public int compare(byte[] o1, byte[] o2) { /** * 对更新进行测试 */ - public void testUpdate() throws FileException { + public void testUpdate() throws FileException, IOException, PageException { String fileName = "testUpdate.db"; + TransactionContext txContext = new MockTransactionContext(); IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; - long uuid = iItemStorage.insertItem(bytes); + long uuid = iItemStorage.insertItem(txContext,bytes); // 正常情况下进行修改 byte[] result = new byte[]{2, 3, 4, 5}; try { - iItemStorage.updateItemByUuid(uuid, result); + iItemStorage.updateItemByUuid(txContext,uuid, result); byte[] bs = iItemStorage.queryItemByUuid(uuid); assertEquals(bs, result); } catch (UUIDException e) { @@ -186,7 +191,7 @@ public void testUpdate() throws FileException { s += 1; } try { - iItemStorage.updateItemByUuid(s, result); + iItemStorage.updateItemByUuid(txContext,s, result); } catch (UUIDException e) { e.printStackTrace(); } @@ -195,12 +200,12 @@ public void testUpdate() throws FileException { /** * 测试修改和获取表头信息 */ - public void testMeta() throws FileException { + public void testMeta() throws FileException, IOException, PageException { String fileName = "testMeta.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] result = new byte[]{1, 2, 3, 4}; - - iItemStorage.setMetadata(result); + TransactionContext txContext = new MockTransactionContext(); + iItemStorage.setMetadata(txContext,result); byte[] bs = iItemStorage.getMetadata(); assertEquals(result, bs); From fafb4c86ae83aa342cbfea98b9dab9000c2d2eec Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Wed, 13 Jan 2021 23:23:04 +0800 Subject: [PATCH 06/18] =?UTF-8?q?[M]=E6=94=B9=E8=BF=9Btransaction=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=20(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 完善事务模块接口,增加事务模块测试用例 * 修正事务接口 Co-authored-by: Criterionist <1229089076@qq.com> --- .../transaction/TransactionContext.java | 16 +++++++++++++++ .../transaction/TransactionManager.java | 8 ++++++++ .../transaction/TransactionStatus.java | 10 ++++++++++ .../mock/MockTransactionContext.java | 19 ++++++++++++++++++ .../mock/MockTransactionManager.java | 16 +++++++++++++++ .../transaction/TransactionContextTest.java | 20 +++++++++++++++++++ 6 files changed, 89 insertions(+) diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java index 0c1a78d..6d90817 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java @@ -2,6 +2,8 @@ import net.kaaass.rumbase.transaction.mock.MockTransactionContext; +import java.util.List; + /** * 事务上下文 *

@@ -21,6 +23,13 @@ static TransactionContext empty() { return new MockTransactionContext(); } + /** + * 获取当前事务快照信息 + * + * @return 事务快照 + */ + static List getSnapShot() { return MockTransactionContext.getSNAPSHOT(); } + /** * 获取事务隔离度 * @@ -35,6 +44,13 @@ static TransactionContext empty() { */ TransactionStatus getStatus(); + /** + * 获取事务管理器 + * + * @return 事务管理器 + */ + TransactionManager getManager(); + /** * 获取事务id * diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionManager.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionManager.java index 4d0e0fb..ced9f1d 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/TransactionManager.java +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionManager.java @@ -17,6 +17,14 @@ public interface TransactionManager { */ TransactionContext createTransactionContext(TransactionIsolation isolation); + /** + * 根据事务id获取事务上下文 + * + * @param xid 事务id + * @return 事务id为xid的事务上下文 + */ + TransactionContext getContext(int xid); + /** * 改变事务日志中的事务状态 * diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionStatus.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionStatus.java index 847cdf9..5ecae72 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/TransactionStatus.java +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionStatus.java @@ -39,4 +39,14 @@ public enum TransactionStatus { TransactionStatus(int statusId) { this.statusId = (byte) statusId; } + + public static TransactionStatus getStatusById(byte id) { + for (TransactionStatus value : values()) { + if (id == value.statusId) { + return value; + } + } + + return null; + } } diff --git a/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionContext.java b/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionContext.java index c8b51b9..573d8ab 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionContext.java +++ b/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionContext.java @@ -29,15 +29,19 @@ public class MockTransactionContext implements TransactionContext { */ @Getter private final int xid; + /** * 事务隔离度 */ @Getter private final TransactionIsolation isolation; + /** * 存储创建它的管理器 */ + @Getter private final TransactionManager manager; + /** * 事务状态 */ @@ -64,6 +68,21 @@ public MockTransactionContext(int xid, TransactionIsolation isolation, Transacti this.manager = manager; } + /** + * 用于恢复的事务上下文构造函数 + * + * @param xid 事务id + * @param isolation 事务隔离度 + * @param manager 创建事务的管理器 + * @param status 事务状态 + */ + public MockTransactionContext(int xid, TransactionIsolation isolation, TransactionManager manager, TransactionStatus status) { + this.xid = xid; + this.isolation = isolation; + this.manager = manager; + this.status = status; + } + @Override public void start() { this.status = TransactionStatus.ACTIVE; diff --git a/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionManager.java b/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionManager.java index 5a12779..a298efb 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionManager.java +++ b/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionManager.java @@ -5,6 +5,7 @@ import net.kaaass.rumbase.transaction.TransactionManager; import net.kaaass.rumbase.transaction.TransactionStatus; +import javax.transaction.xa.Xid; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -66,6 +67,21 @@ public TransactionContext createTransactionContext(TransactionIsolation isolatio return new MockTransactionContext(xid, isolation, this); } + /** + * 根据事务id获取事务上下文 + * + * @param xid 事务id + * @return 事务id为xid的事务上下文 + */ + @Override + public TransactionContext getContext(int xid) { + TransactionStatus status = TransactionStatus.getStatusById(XidLog.get(xid)); + + // 此处为了mock,事务隔离度均为TransactionIsolation.READ_UNCOMMITTED + + return new MockTransactionContext(xid, TransactionIsolation.READ_UNCOMMITTED, this, status); + } + /** * 改变日志中的事务状态 * diff --git a/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java b/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java index 9422326..daa9717 100644 --- a/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java +++ b/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java @@ -83,6 +83,26 @@ public void testTransactionPersistence() { assertEquals(TransactionStatus.ABORTED.getStatusId(), (byte) MockTransactionManager.XidLog.get(transaction2.getXid())); } + /** + * 测试事务状态复原 + */ + public void testTransactionRecovery() { + var manager = new MockTransactionManager(); + // 事务创建,事务状态记录数改变 + var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); + int xid = transaction1.getXid(); + + // 复原事务 + var transactionR = manager.getContext(xid); + assertEquals(TransactionStatus.PREPARING, transactionR.getStatus()); + assertEquals(TransactionIsolation.READ_UNCOMMITTED, transactionR.getIsolation()); + + // 改变事务状态 + transaction1.start(); + transactionR = manager.getContext(xid); + assertEquals(TransactionStatus.ACTIVE, transactionR.getStatus()); + } + /** * 测试事务上锁 */ From 03b9780050f40b54333793c45a1873182a19822c Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Thu, 14 Jan 2021 00:13:11 +0800 Subject: [PATCH 07/18] =?UTF-8?q?[M]=E4=BF=AE=E6=AD=A3=E5=BF=AB=E7=85=A7?= =?UTF-8?q?=E7=9A=84=E6=8E=A5=E5=8F=A3=E9=97=AE=E9=A2=98=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修正事务接口 Co-authored-by: Criterionist <1229089076@qq.com> --- .../rumbase/transaction/TransactionContext.java | 2 +- .../mock/MockTransactionContext.java | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java index 6d90817..f09a0dc 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java @@ -28,7 +28,7 @@ static TransactionContext empty() { * * @return 事务快照 */ - static List getSnapShot() { return MockTransactionContext.getSNAPSHOT(); } + List getSnapshot(); /** * 获取事务隔离度 diff --git a/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionContext.java b/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionContext.java index 573d8ab..8df3a6a 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionContext.java +++ b/src/main/java/net/kaaass/rumbase/transaction/mock/MockTransactionContext.java @@ -17,13 +17,6 @@ @Deprecated public class MockTransactionContext implements TransactionContext { - @Getter - private static final List SNAPSHOT; - - static { - SNAPSHOT = new ArrayList<>(); - } - /** * 事务Id */ @@ -41,18 +34,24 @@ public class MockTransactionContext implements TransactionContext { */ @Getter private final TransactionManager manager; - + /** + * 事务快照 + */ + @Getter + private final List snapshot; /** * 事务状态 */ @Getter private TransactionStatus status; + public MockTransactionContext() { this.xid = 0; this.status = TransactionStatus.COMMITTED; this.isolation = TransactionIsolation.READ_UNCOMMITTED; this.manager = null; + this.snapshot = new ArrayList<>(); } /** @@ -66,6 +65,7 @@ public MockTransactionContext(int xid, TransactionIsolation isolation, Transacti this.status = TransactionStatus.PREPARING; this.isolation = isolation; this.manager = manager; + this.snapshot = new ArrayList<>(); } /** @@ -81,6 +81,7 @@ public MockTransactionContext(int xid, TransactionIsolation isolation, Transacti this.isolation = isolation; this.manager = manager; this.status = status; + this.snapshot = new ArrayList<>(); } @Override From 42ddc0ca04b72ba3ef27d89a9cf7bda18fe38c03 Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Fri, 15 Jan 2021 12:14:56 +0800 Subject: [PATCH 08/18] =?UTF-8?q?[+]=E5=AE=8C=E6=88=90=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E9=A1=B9=E6=A8=A1=E5=9D=97=20(#9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 表创建、表头解析以及页头解析 * 文件创建和插入 * 修正编译问题 * 数据项的插入和查询完成 * 获取整页数据 * 完成头信息添加 以及更新操作 * 数据项增改查以及头部操作完成,添加并发插入测试 * 格式化代码,改进测试用例 * 增大测试用例数据量 Co-authored-by: xiaoxineryi <529086017@qq.com> --- .../kaaass/rumbase/dataitem/IItemStorage.java | 16 +- .../kaaass/rumbase/dataitem/ItemManager.java | 17 +- .../kaaass/rumbase/dataitem/ItemStorage.java | 541 +++++++++++++++--- .../dataitem/exception/ItemException.java | 4 +- .../dataitem/mock/MockItemStorage.java | 8 +- .../rumbase/dataitem/IItemStorageTest.java | 201 ++++--- 6 files changed, 582 insertions(+), 205 deletions(-) diff --git a/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java b/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java index 52cb62c..e5374ee 100644 --- a/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java +++ b/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java @@ -1,6 +1,8 @@ package net.kaaass.rumbase.dataitem; +import net.kaaass.rumbase.dataitem.exception.ItemException; import net.kaaass.rumbase.dataitem.exception.UUIDException; +import net.kaaass.rumbase.page.exception.PageException; import net.kaaass.rumbase.transaction.TransactionContext; import java.io.IOException; @@ -18,7 +20,7 @@ public interface IItemStorage { * @param item 数据项 * @return 返回数据项的UUID */ - long insertItem(TransactionContext txContext, byte[] item) throws IOException; + long insertItem(TransactionContext txContext, byte[] item) throws IOException, PageException; /** * 插入一个有UUID的数据项,唯一使用的地方是日志恢复时使用 @@ -30,7 +32,7 @@ public interface IItemStorage { * @param item 数据项 * @param uuid 编号 */ - void insertItemWithUuid(TransactionContext txContext,byte[] item, long uuid); + void insertItemWithUuid(TransactionContext txContext, byte[] item, long uuid) throws IOException, PageException; /** * 通过UUID查询数据项 @@ -39,7 +41,7 @@ public interface IItemStorage { * @return 数据项 * @throws UUIDException UUID找不到的异常 */ - byte[] queryItemByUuid(long uuid) throws UUIDException; + byte[] queryItemByUuid(long uuid) throws UUIDException, IOException, ItemException; /** @@ -48,7 +50,7 @@ public interface IItemStorage { * @param pageId 页号 * @return list的一堆数据项 */ - List listItemByPageId(int pageId); + List listItemByPageId(int pageId) throws IOException, ItemException; /** * 根据UUID更新数据项 @@ -57,21 +59,21 @@ public interface IItemStorage { * @param item 数据项 * @throws UUIDException 没有找到对应UUID的异常 */ - void updateItemByUuid(TransactionContext txContext,long uuid, byte[] item) throws UUIDException; + void updateItemByUuid(TransactionContext txContext, long uuid, byte[] item) throws UUIDException, IOException, PageException; /** * 获得数据项存储的元数据(可以用于头) * * @return 元数据 */ - byte[] getMetadata(); + byte[] getMetadata() throws IOException, ItemException, UUIDException; /** * 设置数据项存储的元数据(可以用于头) * * @param metadata 头信息 */ - void setMetadata(TransactionContext txContext,byte[] metadata); + void setMetadata(TransactionContext txContext, byte[] metadata) throws IOException, PageException; /** * 清理多余的数据项,空间清理时使用。 diff --git a/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java b/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java index 9e6b33c..609fff6 100644 --- a/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java +++ b/src/main/java/net/kaaass/rumbase/dataitem/ItemManager.java @@ -1,11 +1,9 @@ package net.kaaass.rumbase.dataitem; -import net.kaaass.rumbase.dataitem.mock.MockItemStorage; import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.page.exception.PageException; import net.kaaass.rumbase.transaction.TransactionContext; -import net.kaaass.rumbase.transaction.mock.MockTransactionContext; import java.io.IOException; import java.util.HashMap; @@ -28,15 +26,10 @@ public class ItemManager { * @return 数据项管理器,用于管理数据项 */ public static IItemStorage fromFile(String fileName) throws FileException, IOException, PageException { - String errorFileName = "error.db"; - if (errorFileName.equals(fileName)) { - throw new FileException(2); - } - if (maps.containsKey(fileName)) { return maps.get(fileName); } else { - IItemStorage iItemStorage = MockItemStorage.ofFile(fileName); + IItemStorage iItemStorage = ItemStorage.ofFile(fileName); maps.put(fileName, iItemStorage); return iItemStorage; } @@ -45,19 +38,19 @@ public static IItemStorage fromFile(String fileName) throws FileException, IOExc /** * 新建一个数据库,并且将上层提供的头信息写入。 * - * @param fileName 文件名 - * @param metadata 上层提供的表头信息 + * @param fileName 文件名 + * @param metadata 上层提供的表头信息 * @param txContext 对应的事务名 * @return 数据项管理器 * @throws FileException 想新建的文件已经存在的异常 */ - public static IItemStorage createFile(TransactionContext txContext ,String fileName, byte[] metadata) throws FileException, IOException, PageException { + public static IItemStorage createFile(TransactionContext txContext, String fileName, byte[] metadata) throws FileException, IOException, PageException { // 如果文件已经存在,那么就抛出文件已存在异常 if (maps.containsKey(fileName)) { throw new FileException(1); } else { // 若文件不存在,则创建文件。 - IItemStorage iItemStorage = MockItemStorage.ofNewFile(txContext,fileName, metadata); + IItemStorage iItemStorage = ItemStorage.ofNewFile(txContext, fileName, metadata); maps.put(fileName, iItemStorage); return iItemStorage; } diff --git a/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java b/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java index d4c5160..e6594c2 100644 --- a/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java +++ b/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java @@ -1,7 +1,9 @@ package net.kaaass.rumbase.dataitem; import com.igormaznitsa.jbbp.JBBPParser; +import com.igormaznitsa.jbbp.io.JBBPOut; import com.igormaznitsa.jbbp.mapper.Bin; +import net.kaaass.rumbase.dataitem.exception.ItemException; import net.kaaass.rumbase.dataitem.exception.UUIDException; import net.kaaass.rumbase.page.Page; import net.kaaass.rumbase.page.PageManager; @@ -12,18 +14,28 @@ import net.kaaass.rumbase.transaction.TransactionContext; import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.Random; /** * 数据项管理器的具体实现 * *

- * 每个表的头信息都是一致的,为 - * |头信息标志位:1234(共4字节)|当前可用的第一个空闲页(2字节)|是否写入表头信息(1字节),写入为123|头信息所对应的UUID(8字节) - * - * 同时 + * 每个表的头信息都是一致的,为 + * |头信息标志位:1 2 3 4(共4字节)|当前可用的第一个空闲页(4字节)|是否写入表头信息(1字节),写入为123|头信息所对应的UUID(8字节) + *

+ * 同时每个页都有相应的页头,页头格式为: + * |页头标志位 2 3 4 5(共4字节)|lsn来记录日志相关内容(8字节)|页剩余空间大小(4字节)|页内数据项个数n(4字节)|每个数据项标志(n*8字节)| + *

+ * 数据项标志为 |uuid后面的随机数(4字节)|在页内偏移offset(4字节)| + *

+ * 每个数据项的内容为|标志位,表示有没有拉链等特殊情况(1字节)|数据长度m(4字节)|数据内容(m字节)| + * TODO : |(若有拉链的话,则记录下一个uuid位置)8字节| *

- * @author kaito + * + * @author kaito */ public class ItemStorage implements IItemStorage { @@ -46,7 +58,7 @@ public class ItemStorage implements IItemStorage { private IRecoveryStorage recoveryStorage; - public ItemStorage(String fileName, int tempFreePage, long headerUuid,PageStorage pageStorage) { + public ItemStorage(String fileName, int tempFreePage, long headerUuid, PageStorage pageStorage) { this.fileName = fileName; this.tempFreePage = tempFreePage; this.headerUuid = headerUuid; @@ -57,12 +69,13 @@ public ItemStorage(String fileName, int tempFreePage, long headerUuid,PageStorag * 判断有没有头部标志 * *

- * 获取表头的前四个字节数据,若是1234则表示是表头,解析后面的数据,否则就认为该表没有被初始化 + * 获取表头的前四个字节数据,若是1234则表示是表头,解析后面的数据,否则就认为该表没有被初始化 *

+ * * @param header 第一页的Page对象 * @return 是否是表的第一页 */ - private static boolean checkTableHeader(Page header){ + private static boolean checkTableHeader(Page header) { var data = header.getData(); byte[] flag = new byte[4]; try { @@ -74,169 +87,511 @@ private static boolean checkTableHeader(Page header){ } /** - * 打开相应的文件并读取头信息,如果没有对应的头信息就进行初始化 + * 打开相应的文件并读取头信息,如果没有对应的头信息就进行初始化 + * * @param fileName 文件名 * @return 解析或新建得到的数据项管理器对象 */ public static IItemStorage ofFile(String fileName) throws FileException, IOException, PageException { var pageStorage = PageManager.fromFile(fileName); var header = pageStorage.get(0); - if (checkTableHeader(header)){ + header.pin(); + if (checkTableHeader(header)) { // 如果表头标志存在,就解析对应表头信息 - var h = JBBPParser.prepare("long headerFlag;int tempFreePage;byte hasHeaderInfo;long headerUuid;").parse(header.getData()).mapTo(new TableHeader()); - return new ItemStorage(fileName,h.tempFreePage,h.headerUuid,pageStorage); - }else { + var h = parseHeader(header); + header.unpin(); + return new ItemStorage(fileName, h.tempFreePage, h.headerUuid, pageStorage); + } else { // 若表头标志不存在,就初始化对应的表信息。 // 只初始化headerFlag和tempFreePage,表头信息位置统一由setMetadata来实现 - byte[] bytes = new byte[]{1,2,3,4,0,1}; - header.patchData(0,bytes); - return new ItemStorage(fileName,1,0,pageStorage); + var bytes = JBBPOut.BeginBin(). + Byte(1, 2, 3, 4). + Int(1). + End().toByteArray(); + header.patchData(0, bytes); + header.unpin(); + return new ItemStorage(fileName, 1, 0, pageStorage); } } /** * 创建新的表,初始化相应数据,并且保存头信息。 + * * @param fileName 文件名 * @param metadata 表头信息 * @return 数据项管理器 */ - public static IItemStorage ofNewFile(TransactionContext txContext ,String fileName,byte[] metadata) throws IOException, FileException, PageException { + public static IItemStorage ofNewFile(TransactionContext txContext, String fileName, byte[] metadata) throws IOException, FileException, PageException { var pageStorage = ItemStorage.ofFile(fileName); - pageStorage.setMetadata(txContext,metadata); + pageStorage.setMetadata(txContext, metadata); return pageStorage; } - private PageHeader getPageHeader(Page page) throws IOException { + /** + * 根据uuid获取后面的随机位置 + */ + private static int getRndByUuid(long uuid) { + return (int) (uuid & 0xFFFFFFFF); + } + + /** + * 根据uuid获取page + */ + private Page getPage(long uuid) { + var pageId = uuid >> 32; + Page page = pageStorage.get(pageId); + page.pin(); + return page; + } + + /** + * 根据pageId获取Page + */ + private Page getPage(int pageId) { + Page page = pageStorage.get(pageId); + page.pin(); + return page; + } + + /** + * 释放page的操作 + * + * @param page + */ + private void releasePage(Page page) { + page.unpin(); + } + + /** + * 解析表头数据 + * + * @return 解析得到的表头对象 + */ + private static TableHeader parseHeader(Page page) throws IOException { + return JBBPParser.prepare("int headerFlag;int tempFreePage;byte hasHeaderInfo;long headerUuid;"). + parse(page.getData()).mapTo(new TableHeader()); + } + + /** + * 通过偏移量解析得到数据 + * + * @param page 页 + * @param item 一个数据项记录 + * @return 解析得到的数据 + */ + private static byte[] parseData(Page page, Item item) throws IOException, ItemException { + int offset = item.offset; var data = page.getData(); - var pageHeader = JBBPParser.prepare("long pageFlag;int pageId;long lsn;int leftSpace;int recordNumber;" + - "Item[recordNumber]{int size;long uuid;int offset;}").parse(data).mapTo(new PageHeader()); - return pageHeader; + data.skip(offset); + var content = JBBPParser.prepare("byte type;int size;byte[size] data;").parse(data).mapTo(new ItemContent()); + if (content.type != NORMAL_DATA) { + throw new ItemException(2); + } + return content.data; } + /** + * 检查一个页头标志位是否存在,表示其是否已经被初始化。若初始化则标志位为2345 + * + * @param page 页 + * @return 该页是否已经被初始化 + * @throws IOException + */ + private boolean checkPageHeader(Page page) throws IOException { + byte[] pageFlag = new byte[4]; + var n = page.getData().read(pageFlag); + return pageFlag[0] == 2 && pageFlag[1] == 3 && pageFlag[2] == 4 && pageFlag[3] == 5; + } - @Override - public long insertItem(TransactionContext txContext, byte[] item) throws IOException { - var page = pageStorage.get(this.tempFreePage); - var pageHeader = getPageHeader(page); + private Optional getPageHeader(Page page) throws IOException { + if (checkPageHeader(page)) { + // 如果页头信息正确,就解析得到页头信息的对象。 + var data = page.getData(); + var pageHeader = JBBPParser.prepare("int pageFlag;long lsn;int leftSpace;int recordNumber;" + + "item [recordNumber]{int uuid;int offset;}").parse(data); + var p = pageHeader.mapTo(new PageHeader()); + return Optional.of(p); + } else { + // 否则返回空,交由上层处理。 + return Optional.empty(); + } + } - return 0; + private PageHeader initPage(Page page) throws IOException, PageException { + final byte[] bytes = JBBPOut.BeginBin(). + Byte(2, 3, 4, 5). // 页头标志位 + Long(0). // 日志记录位置,以后若有日志记录点则使用 + Int(4072). // 剩余空间大小 + Int(0). // 记录数目 + End().toByteArray(); + page.patchData(0, bytes); + return new PageHeader(0, 0, 4072, 0); + } + + /** + * 修改当前第一个可用页 + */ + private void addTempFreePage() throws IOException, PageException { + this.tempFreePage += 1; + var page = pageStorage.get(0); + page.pin(); + var tempFreePage = JBBPOut.BeginBin(). + Int(this.tempFreePage) + .End().toByteArray(); + page.patchData(4, tempFreePage); + page.unpin(); } @Override - public void insertItemWithUuid(TransactionContext txContext,byte[] item, long uuid) { + public synchronized long insertItem(TransactionContext txContext, byte[] item) throws IOException, PageException { + var page = getPage(this.tempFreePage); + var pageHeaderOp = getPageHeader(page); + if (pageHeaderOp.isEmpty()) { + // 如果获取的页没有页头信息,则进行初始化。 + pageHeaderOp = Optional.of(initPage(page)); + } + var pageHeader = pageHeaderOp.get(); + if (pageHeader.leftSpace - Math.min(item.length, MAX_RECORD_SIZE) <= MIN_LEFT_SPACE) { + // 如果剩余空间过小的话,就切换到下一个页进行,同时修改表头信息.并且,若数据过大则使用拉链,所以取512和数据大小较小的 + addTempFreePage(); + releasePage(page); + return insertItem(txContext, item); + } else { + // 剩余空间足够,则插入 + int rnd = Math.abs(new Random().nextInt()); + long s = this.tempFreePage; + long uuid = ((s << 32) + (long) (rnd)); + // 保证uuid不重复 + while (checkUuidExist(uuid)) { + rnd = Math.abs(new Random().nextInt()); + uuid = ((s << 32) + (long) (rnd)); + } + insertToPage(page, pageHeader, txContext, item, rnd); + releasePage(page); + return uuid; + } + } + /** + * 将数据插入到页内对应位置,并修改页头信息 + */ + private void insertToPage(Page page, PageHeader pageHeader, TransactionContext txContext, byte[] item, int rnd) throws IOException, PageException { + if (item.length < MAX_RECORD_SIZE) { + int offset = 0; + if (pageHeader.recordNumber == 0) { + // 如果页没有元素的话 + offset = 4095 - item.length - DATA_EXTRA_SIZE; + } else { + // 如果页内有插入的数据,则读取其offset并推算自己的offset + offset = pageHeader.item[pageHeader.recordNumber - 1].offset - item.length - DATA_EXTRA_SIZE; + } + //修改数据项头信息 + var i = JBBPOut.BeginBin(). + Int(rnd). + Int(offset). + End().toByteArray(); + page.patchData(ITEM_OFFSET + pageHeader.recordNumber * ITEM_SIZE, i); + //修改数据信息 + var data = JBBPOut.BeginBin(). + Byte(NORMAL_DATA). + Int(item.length). + Byte(item).End().toByteArray(); + page.patchData(offset, data); + //修改页头信息 + var headerInfo = JBBPOut.BeginBin(). + Int(pageHeader.leftSpace - item.length - DATA_EXTRA_SIZE - ITEM_SIZE). + Int(pageHeader.recordNumber + 1). + End().toByteArray(); + page.patchData(LEFT_SPACE_OFFSET, headerInfo); + } } @Override - public byte[] queryItemByUuid(long uuid) throws UUIDException { - return new byte[0]; + public synchronized void insertItemWithUuid(TransactionContext txContext, byte[] item, long uuid) throws IOException, PageException { + if (!checkUuidExist(uuid)) { + // 若不存在,则要恢复 + var page = getPage(uuid); + var pageHeaderOp = getPageHeader(page); + if (pageHeaderOp.isEmpty()) { + // 如果获取的页没有页头信息,则进行初始化。 + pageHeaderOp = Optional.of(initPage(page)); + } + int rnd = getRndByUuid(uuid); + insertToPage(page, pageHeaderOp.get(), txContext, item, rnd); + releasePage(page); + } + // 若存在则不需要恢复,直接返回 } - @Override - public List listItemByPageId(int pageId) { - return null; + /** + * 检查uuid是否存在 + * + * @param uuid + * @return + */ + private boolean checkUuidExist(long uuid) throws IOException { + var page = getPage(uuid); + int id = getRndByUuid(uuid); + var pageHeader = getPageHeader(page); + if (pageHeader.isEmpty()) { + return false; + } else { + for (var item : pageHeader.get().item) { + if (id == item.uuid) { + return true; + } + } + } + return false; } @Override - public void updateItemByUuid(TransactionContext txContext,long uuid, byte[] item) throws UUIDException { + public byte[] queryItemByUuid(long uuid) throws UUIDException, IOException, ItemException { + if (checkUuidExist(uuid)) { + var page = getPage(uuid); + var header = getPageHeader(page); + if (header.isEmpty()) { + releasePage(page); + throw new UUIDException(2); + } else { + // 遍历所有的item读取数据 + var items = header.get().item; + int id = getRndByUuid(uuid); + for (var item : items) { + if (item.uuid == id) { + var s = parseData(page, item); + releasePage(page); + return s; + } + } + } + return new byte[0]; + } else { + throw new UUIDException(2); + } + } + + @Override + public List listItemByPageId(int pageId) throws IOException, ItemException { + var page = getPage(pageId); + List bytes = new ArrayList<>(); + var pageHeaderOp = getPageHeader(page); + if (pageHeaderOp.isPresent()) { + var pageHeader = pageHeaderOp.get(); + for (var item : pageHeader.item) { + var data = parseData(page, item); + bytes.add(data); + } + } + releasePage(page); + return bytes; } @Override - public byte[] getMetadata() { - return new byte[0]; + public void updateItemByUuid(TransactionContext txContext, long uuid, byte[] item) throws UUIDException, IOException, PageException { + var page = getPage(uuid); + if (checkUuidExist(uuid)) { + var pageHeader = getPageHeader(page); + var items = pageHeader.get().item; + for (var i : items) { + if (i.uuid == getRndByUuid(uuid)) { + var offset = i.offset; + var bytes = JBBPOut.BeginBin(). + Byte(NORMAL_DATA). + Int(item.length) + .Byte(item) + .End().toByteArray(); + page.patchData(offset, bytes); + releasePage(page); + return; + } + } + } else { + releasePage(page); + throw new UUIDException(2); + } } @Override - public void setMetadata(TransactionContext txContext,byte[] metadata) { + public byte[] getMetadata() throws IOException, ItemException, UUIDException { + var page = getPage(0); + var header = parseHeader(page); + if (checkTableHeader(page) && header.hasHeaderInfo == HAS_HEADER) { + // 若表头已经被初始化并且有标志位的话,就说明有表头信息,进行获取. + var h = queryItemByUuid(header.headerUuid); + releasePage(page); + return h; + } else { + throw new ItemException(1); + } + } + @Override + public void setMetadata(TransactionContext txContext, byte[] metadata) throws IOException, PageException { + var page = getPage(0); + var headerUuid = insertItem(txContext, metadata); + var bytes = JBBPOut.BeginBin(). + Byte(HAS_HEADER). + Long(headerUuid).End().toByteArray(); + page.patchData(HEADER_OFFSET, bytes); + releasePage(page); } @Override public void removeItems(List uuids) { } -} -/** - * 表头 - */ -class TableHeader { /** - * 是否是表头的标志 + * */ - @Bin - long headerFlag; + final static byte NORMAL_DATA = 121; /** - * 第一个可使用的空闲页编号 + * 头部标志的偏移 */ - @Bin - int tempFreePage; + final static int HEADER_OFFSET = 8; + /** - * 是否有表头信息 + * 表头判断是否有表头数据 */ - @Bin - byte hasHeaderInfo; + final static byte HAS_HEADER = 123; /** - * 表头信息对应的UUID + * 数据项记录的大小 */ - @Bin - long headerUuid; -} + final static int ITEM_SIZE = 8; -/** - * 每个数据项对应的相关信息 - */ -class Item{ /** - * 数据项大小 + * 数据项记录的起始偏移 */ - @Bin - int size; + final static int ITEM_OFFSET = 20; /** - * 数据项编号 + * 记录数据项标志和数据项大小额外占用的空间 */ - @Bin - long uuid; + final static int DATA_EXTRA_SIZE = 5; /** - * 页内偏移 + * 页内空闲空间的页内偏移 */ - @Bin - int offset; -} - -/** - * 页头 - */ -class PageHeader{ + final static int LEFT_SPACE_OFFSET = 12; /** - * 页头标志 + * 页的大小 */ - @Bin - long pageFlag; + final static int PAGE_SIZE = 4096; /** - * 当前页号 + * 单个数据项的最大值,超过的话使用拉链 */ - @Bin - int pageId; + final static int MAX_RECORD_SIZE = 512; /** - * 日志记录编号 + * 每个页保留的大小 */ - @Bin - long lsn; + final static int MIN_LEFT_SPACE = 409; + /** - * 剩余空间大小 + * 表头 */ - @Bin - int leftSpace; + public static class TableHeader { + /** + * 是否是表头的标志 + */ + @Bin + int headerFlag; + /** + * 第一个可使用的空闲页编号 + */ + @Bin + int tempFreePage; + /** + * 是否有表头信息 + */ + @Bin + byte hasHeaderInfo; + /** + * 表头信息对应的UUID + */ + @Bin + long headerUuid; + + public Object newInstance(Class klazz) { + return klazz == TableHeader.class ? new TableHeader() : null; + } + } + /** - * 页内记录总数 + * 每个数据项对应的相关信息 */ - @Bin - int recordNumber; + public static class Item { + /** + * 数据项编号 + */ + @Bin + public int uuid; + /** + * 页内偏移 + */ + @Bin + public int offset; + + public Object newInstance(Class klazz) { + return klazz == Item.class ? new Item() : null; + } + } + /** - * 页内记录的相关信息 + * 页头 */ - @Bin - Item[] item; -} \ No newline at end of file + public static class PageHeader { + /** + * 页头标志 + */ + @Bin + public int pageFlag; + + /** + * 日志记录编号 + */ + @Bin + long lsn; + /** + * 剩余空间大小 + */ + @Bin + int leftSpace; + /** + * 页内记录总数 + */ + @Bin + int recordNumber; + /** + * 页内记录的相关信息 + */ + @Bin + Item[] item; + + public PageHeader() { + } + + public PageHeader(int pageFlag, long lsn, int leftSpace, int recordNumber) { + this.pageFlag = pageFlag; + this.lsn = lsn; + this.leftSpace = leftSpace; + this.recordNumber = recordNumber; + } + + public Object newInstance(Class klazz) { + return klazz == PageHeader.class ? new PageHeader() : null; + } + } + + public static class ItemContent { + @Bin + byte type; + @Bin + int size; + @Bin + byte[] data; + + public Object newInstance(Class klazz) { + return klazz == ItemContent.class ? new ItemContent() : null; + } + } +} diff --git a/src/main/java/net/kaaass/rumbase/dataitem/exception/ItemException.java b/src/main/java/net/kaaass/rumbase/dataitem/exception/ItemException.java index b87b578..bd02893 100644 --- a/src/main/java/net/kaaass/rumbase/dataitem/exception/ItemException.java +++ b/src/main/java/net/kaaass/rumbase/dataitem/exception/ItemException.java @@ -12,8 +12,8 @@ */ public class ItemException extends RumbaseException { public static final Map REASONS = new HashMap<>() {{ - put(1, "要创建的文件已存在"); - put(2, "查找的文件不存在"); + put(1, "没有相应表头信息"); + put(2, "所查找的数据项信息解析错误"); }}; public ItemException(int subID) { diff --git a/src/main/java/net/kaaass/rumbase/dataitem/mock/MockItemStorage.java b/src/main/java/net/kaaass/rumbase/dataitem/mock/MockItemStorage.java index 1a6e86f..19e6fd5 100644 --- a/src/main/java/net/kaaass/rumbase/dataitem/mock/MockItemStorage.java +++ b/src/main/java/net/kaaass/rumbase/dataitem/mock/MockItemStorage.java @@ -73,14 +73,14 @@ public static IItemStorage ofFile(String fileName) { * @param tableHeader 表头信息 * @return 返回数据项管理器 */ - public static IItemStorage ofNewFile(TransactionContext txContext ,String fileName, byte[] tableHeader) { + public static IItemStorage ofNewFile(TransactionContext txContext, String fileName, byte[] tableHeader) { // TODO: 因为是新建的文件,所以需要给文件头写入头信息数据。 return new MockItemStorage(fileName, 0, 0); } @Override - public long insertItem(TransactionContext txContext,byte[] item) { + public long insertItem(TransactionContext txContext, byte[] item) { Random ran = new Random(); long r = ran.nextLong(); maps.put(r, item); @@ -88,7 +88,7 @@ public long insertItem(TransactionContext txContext,byte[] item) { } @Override - public void insertItemWithUuid(TransactionContext txContext,byte[] item, long uuid) { + public void insertItemWithUuid(TransactionContext txContext, byte[] item, long uuid) { maps.put(uuid, item); } @@ -107,7 +107,7 @@ public List listItemByPageId(int pageId) { } @Override - public void updateItemByUuid(TransactionContext txContext,long uuid, byte[] item) throws UUIDException { + public void updateItemByUuid(TransactionContext txContext, long uuid, byte[] item) throws UUIDException { if (maps.containsKey(uuid)) { maps.put(uuid, item); } else { diff --git a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java index b1a3774..0dcdaf0 100644 --- a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java @@ -2,16 +2,19 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.dataitem.exception.ItemException; import net.kaaass.rumbase.dataitem.exception.UUIDException; import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.page.exception.PageException; import net.kaaass.rumbase.transaction.TransactionContext; -import net.kaaass.rumbase.transaction.mock.MockTransactionContext; import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Random; + +import static org.junit.Assert.assertArrayEquals; /** * 对数据项管理部分进行测试 @@ -41,19 +44,16 @@ public void testGetFromFile() throws FileException, IOException, PageException { /** * 测试能否新建文件并得到数据项管理器 */ - public void testCreateFile() { - TransactionContext txContext = new MockTransactionContext(); + public void testCreateFile() throws IOException, FileException, PageException { + TransactionContext txContext = TransactionContext.empty(); String fileName = "testCreateFile.db"; byte[] metadata = new byte[1024]; // 第一次执行的时候,表中没有数据,不会报错 - try { - var iItemStorage = ItemManager.createFile(txContext,fileName, metadata); - } catch (Exception e) { - e.printStackTrace(); - } + var iItemStorage = ItemManager.createFile(txContext, fileName, metadata); try { - IItemStorage iItemStorage = ItemManager.createFile(txContext,fileName, metadata); + iItemStorage = ItemManager.createFile(txContext, fileName, metadata); + fail("should get exception"); } catch (Exception e) { log.error("Exception Error :", e); } @@ -62,17 +62,18 @@ public void testCreateFile() { /** * 进行插入的测试 */ - public void testInsert() throws FileException, IOException, PageException { + public void testInsert() throws FileException, IOException, PageException, UUIDException, ItemException { String fileName = "testInsert.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; - TransactionContext txContext = new MockTransactionContext(); - long uuid = iItemStorage.insertItem(txContext,bytes); - try { - assertEquals(bytes, iItemStorage.queryItemByUuid(uuid)); - } catch (UUIDException e) { - e.printStackTrace(); - } + TransactionContext txContext = TransactionContext.empty(); + long uuid = iItemStorage.insertItem(txContext, bytes); + + long uuid2 = iItemStorage.insertItem(txContext, bytes); + + long uuid3 = iItemStorage.insertItem(txContext, bytes); + + assertArrayEquals(bytes, iItemStorage.queryItemByUuid(uuid)); } /** @@ -82,116 +83,143 @@ public void testInsertWithUUID() throws FileException, IOException, PageExceptio String fileName = "testInsertWithUUID.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; - long uuid = 50; - TransactionContext txContext = new MockTransactionContext(); + long s = 1; + int rnd = Math.abs(new Random().nextInt()); + long uuid = (s << 32) + rnd; + TransactionContext txContext = TransactionContext.empty(); // 第一次插入,表中没有该UUID,可以正常执行 - iItemStorage.insertItemWithUuid(txContext,bytes, uuid); + iItemStorage.insertItemWithUuid(txContext, bytes, uuid); try { - assertEquals(bytes, iItemStorage.queryItemByUuid(uuid)); - } catch (UUIDException e) { + assertArrayEquals(bytes, iItemStorage.queryItemByUuid(uuid)); + } catch (UUIDException | ItemException e) { e.printStackTrace(); } // 第二次插入 - iItemStorage.insertItemWithUuid(txContext,bytes, uuid); + iItemStorage.insertItemWithUuid(txContext, bytes, uuid); } /** - * 对查询进行测试 + * 对插入大量数据进行测试 */ - public void testQuery() throws FileException, IOException, PageException { - String fileName = "testQuery.db"; + public void testManyInsert() throws FileException, IOException, PageException, UUIDException, ItemException { + String fileName = "testInsertMany.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; - TransactionContext txContext = new MockTransactionContext(); - long uuid = iItemStorage.insertItem(txContext,bytes); - // 查询可以正常执行 - try { - assertEquals(bytes, iItemStorage.queryItemByUuid(uuid)); - } catch (UUIDException e) { - e.printStackTrace(); - } - // 找一个跟UUID不同的,也就是说不在数据库中的UUID进行查询 - long s = 1; - if (s == uuid) { - s += 1; - } - try { - var b = iItemStorage.queryItemByUuid(s); - } catch (UUIDException e) { - log.error("Exception Error :", e); + TransactionContext txContext = TransactionContext.empty(); + for (int i = 0; i < 1000; i++) { + long uuid = iItemStorage.insertItem(txContext, bytes); + long uuid2 = iItemStorage.insertItem(txContext, bytes); + long uuid3 = iItemStorage.insertItem(txContext, bytes); + // 查询可以正常执行 + var item = iItemStorage.queryItemByUuid(uuid); + assertArrayEquals(bytes, item); + var item3 = iItemStorage.queryItemByUuid(uuid3); + assertArrayEquals(bytes, item3); } } /** * 获取整个页的数据项进行测试 */ - public void testQueryByPageID() throws FileException, IOException, PageException { + public void testQueryByPageID() throws FileException, IOException, PageException, ItemException { String fileName = "testQueryByPageID.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; - TransactionContext txContext = new MockTransactionContext(); - long uuid = iItemStorage.insertItem(txContext,bytes); + TransactionContext txContext = TransactionContext.empty(); + long uuid = iItemStorage.insertItem(txContext, bytes); byte[] bytes1 = new byte[]{2, 3, 4, 5}; - long uuid1 = iItemStorage.insertItem(txContext,bytes1); - - Comparator comparator = new Comparator() { - @Override - public int compare(byte[] o1, byte[] o2) { - int length = Math.min(o1.length, o2.length); - for (int i = 0; i < length; i++) { - if (o2[i] > o1[i]) { - return -1; - } else if (o2[i] < o1[i]) { - return 1; - } + long uuid1 = iItemStorage.insertItem(txContext, bytes1); + + Comparator comparator = (o1, o2) -> { + int length = Math.min(o1.length, o2.length); + for (int i = 0; i < length; i++) { + if (o2[i] > o1[i]) { + return -1; + } else if (o2[i] < o1[i]) { + return 1; } - - return Integer.compare(o1.length, o2.length); } + + return Integer.compare(o1.length, o2.length); }; - try { - List bs = new ArrayList<>(); - bs.add(bytes); - bs.add(bytes1); - bs.sort(comparator); - // 获取pageID对应的数据项,在这里Mock是获取所有list中的数据 - var result = iItemStorage.listItemByPageId(0); - result.sort(comparator); - assertEquals(bs, result); - } catch (Exception e) { - e.printStackTrace(); + List bs = new ArrayList<>(); + bs.add(bytes); + bs.add(bytes1); + bs.sort(comparator); + // 获取pageID对应的数据项,在这里Mock是获取所有list中的数据 + var result = iItemStorage.listItemByPageId(1); + result.sort(comparator); + for (int i = 0; i < bs.size(); i++) { + assertArrayEquals(bs.get(i), result.get(i)); + } + } + + + static class Insert implements Runnable { + IItemStorage iItemStorage; + TransactionContext txContext; + + public Insert(IItemStorage iItemStorage, TransactionContext txContext) { + this.iItemStorage = iItemStorage; + this.txContext = txContext; + } + + @Override + public void run() { + var bytes = new byte[]{1, 2, 3, 4}; + try { + for (int i = 0; i < 100; i++) { + long uuid = iItemStorage.insertItem(txContext, bytes); + assertArrayEquals(bytes, iItemStorage.queryItemByUuid(uuid)); + } + } catch (Exception e) { + e.printStackTrace(); + fail("Exception caught"); + } } } + /** + * 测试并发下插入是否有问题 + */ + public void testSynInsert() throws IOException, FileException, PageException { + String fileName = "testInsert.db"; + IItemStorage iItemStorage = ItemManager.fromFile(fileName); + byte[] bytes = new byte[]{1, 2, 3, 4}; + TransactionContext txContext = TransactionContext.empty(); + new Thread(new Insert(iItemStorage, txContext)).start(); + new Thread(new Insert(iItemStorage, txContext)).start(); + new Thread(new Insert(iItemStorage, txContext)).start(); + new Thread(new Insert(iItemStorage, txContext)).start(); + } + + /** * 对更新进行测试 */ - public void testUpdate() throws FileException, IOException, PageException { + public void testUpdate() throws FileException, IOException, PageException, UUIDException, ItemException { String fileName = "testUpdate.db"; - TransactionContext txContext = new MockTransactionContext(); + TransactionContext txContext = TransactionContext.empty(); IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; - long uuid = iItemStorage.insertItem(txContext,bytes); + long uuid = iItemStorage.insertItem(txContext, bytes); // 正常情况下进行修改 byte[] result = new byte[]{2, 3, 4, 5}; - try { - iItemStorage.updateItemByUuid(txContext,uuid, result); - byte[] bs = iItemStorage.queryItemByUuid(uuid); - assertEquals(bs, result); - } catch (UUIDException e) { - e.printStackTrace(); - } + iItemStorage.updateItemByUuid(txContext, uuid, result); + byte[] bs = iItemStorage.queryItemByUuid(uuid); + assertArrayEquals(bs, result); // 修改一个不存在的UUID long s = 1; if (s == uuid) { s += 1; } try { - iItemStorage.updateItemByUuid(txContext,s, result); + iItemStorage.updateItemByUuid(txContext, s, result); + fail("Should get exception"); } catch (UUIDException e) { e.printStackTrace(); } @@ -200,15 +228,14 @@ public void testUpdate() throws FileException, IOException, PageException { /** * 测试修改和获取表头信息 */ - public void testMeta() throws FileException, IOException, PageException { + public void testMeta() throws FileException, IOException, PageException, UUIDException, ItemException { String fileName = "testMeta.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] result = new byte[]{1, 2, 3, 4}; - TransactionContext txContext = new MockTransactionContext(); - iItemStorage.setMetadata(txContext,result); + TransactionContext txContext = TransactionContext.empty(); + iItemStorage.setMetadata(txContext, result); byte[] bs = iItemStorage.getMetadata(); - assertEquals(result, bs); - + assertArrayEquals(result, bs); } } From 0c1b94eb8b14420b04a340a24d6d6812fac3032d Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Fri, 15 Jan 2021 15:11:02 +0800 Subject: [PATCH 09/18] =?UTF-8?q?[+]=E5=AE=8C=E6=88=90=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 记录mvcc初步 * 修正测试用例问题 * 完成可见性判断 * 增加0、0xffffffff为超级事务,视为已提交 * 增加读已提交测试、util测试 * 增加可重复读测试 * 完善回滚错误、改善测试代码覆盖率 * 改进dataitem模块的错误处理 * 修改异常处理和checkUuidExist * 改进uuid存在性判断逻辑 Co-authored-by: xiaoxineryi <529086017@qq.com> --- .../kaaass/rumbase/dataitem/IItemStorage.java | 18 +- .../kaaass/rumbase/dataitem/ItemStorage.java | 405 +++++++++++------- .../dataitem/exception/ItemException.java | 22 - .../exception/PageCorruptedException.java | 27 ++ .../exception/RumbaseRuntimeException.java | 41 ++ .../kaaass/rumbase/record/IRecordStorage.java | 7 +- .../rumbase/record/MvccRecordStorage.java | 257 +++++++++++ .../net/kaaass/rumbase/record/MvccUtil.java | 81 ++++ .../kaaass/rumbase/record/RecordManager.java | 8 +- .../exception/NeedRollbackException.java | 38 ++ .../exception/RecordNotFoundException.java | 11 + .../exception/StorageCorruptedException.java | 33 ++ .../rumbase/dataitem/IItemStorageTest.java | 14 +- .../kaaass/rumbase/record/FakeTxContext.java | 52 +++ .../kaaass/rumbase/record/FakeTxManager.java | 75 ++++ .../rumbase/record/IRecordStorageTest.java | 11 +- .../rumbase/record/MvccReadCommitTest.java | 80 ++++ .../record/MvccReadRepeatableTest.java | 104 +++++ .../kaaass/rumbase/record/MvccUtilTest.java | 28 ++ 19 files changed, 1104 insertions(+), 208 deletions(-) delete mode 100644 src/main/java/net/kaaass/rumbase/dataitem/exception/ItemException.java create mode 100644 src/main/java/net/kaaass/rumbase/dataitem/exception/PageCorruptedException.java create mode 100644 src/main/java/net/kaaass/rumbase/exception/RumbaseRuntimeException.java create mode 100644 src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java create mode 100644 src/main/java/net/kaaass/rumbase/record/MvccUtil.java create mode 100644 src/main/java/net/kaaass/rumbase/record/exception/NeedRollbackException.java create mode 100644 src/main/java/net/kaaass/rumbase/record/exception/StorageCorruptedException.java create mode 100644 src/test/java/net/kaaass/rumbase/record/FakeTxContext.java create mode 100644 src/test/java/net/kaaass/rumbase/record/FakeTxManager.java create mode 100644 src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java create mode 100644 src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java create mode 100644 src/test/java/net/kaaass/rumbase/record/MvccUtilTest.java diff --git a/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java b/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java index e5374ee..70c6b17 100644 --- a/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java +++ b/src/main/java/net/kaaass/rumbase/dataitem/IItemStorage.java @@ -1,11 +1,9 @@ package net.kaaass.rumbase.dataitem; -import net.kaaass.rumbase.dataitem.exception.ItemException; +import net.kaaass.rumbase.dataitem.exception.PageCorruptedException; import net.kaaass.rumbase.dataitem.exception.UUIDException; -import net.kaaass.rumbase.page.exception.PageException; import net.kaaass.rumbase.transaction.TransactionContext; -import java.io.IOException; import java.util.List; /** @@ -20,7 +18,7 @@ public interface IItemStorage { * @param item 数据项 * @return 返回数据项的UUID */ - long insertItem(TransactionContext txContext, byte[] item) throws IOException, PageException; + long insertItem(TransactionContext txContext, byte[] item); /** * 插入一个有UUID的数据项,唯一使用的地方是日志恢复时使用 @@ -32,7 +30,7 @@ public interface IItemStorage { * @param item 数据项 * @param uuid 编号 */ - void insertItemWithUuid(TransactionContext txContext, byte[] item, long uuid) throws IOException, PageException; + void insertItemWithUuid(TransactionContext txContext, byte[] item, long uuid); /** * 通过UUID查询数据项 @@ -41,7 +39,7 @@ public interface IItemStorage { * @return 数据项 * @throws UUIDException UUID找不到的异常 */ - byte[] queryItemByUuid(long uuid) throws UUIDException, IOException, ItemException; + byte[] queryItemByUuid(long uuid) throws UUIDException; /** @@ -50,7 +48,7 @@ public interface IItemStorage { * @param pageId 页号 * @return list的一堆数据项 */ - List listItemByPageId(int pageId) throws IOException, ItemException; + List listItemByPageId(int pageId); /** * 根据UUID更新数据项 @@ -59,21 +57,21 @@ public interface IItemStorage { * @param item 数据项 * @throws UUIDException 没有找到对应UUID的异常 */ - void updateItemByUuid(TransactionContext txContext, long uuid, byte[] item) throws UUIDException, IOException, PageException; + void updateItemByUuid(TransactionContext txContext, long uuid, byte[] item) throws UUIDException, PageCorruptedException; /** * 获得数据项存储的元数据(可以用于头) * * @return 元数据 */ - byte[] getMetadata() throws IOException, ItemException, UUIDException; + byte[] getMetadata(); /** * 设置数据项存储的元数据(可以用于头) * * @param metadata 头信息 */ - void setMetadata(TransactionContext txContext, byte[] metadata) throws IOException, PageException; + void setMetadata(TransactionContext txContext, byte[] metadata) throws PageCorruptedException; /** * 清理多余的数据项,空间清理时使用。 diff --git a/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java b/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java index e6594c2..7cb3217 100644 --- a/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java +++ b/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java @@ -3,7 +3,8 @@ import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPOut; import com.igormaznitsa.jbbp.mapper.Bin; -import net.kaaass.rumbase.dataitem.exception.ItemException; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.dataitem.exception.PageCorruptedException; import net.kaaass.rumbase.dataitem.exception.UUIDException; import net.kaaass.rumbase.page.Page; import net.kaaass.rumbase.page.PageManager; @@ -37,6 +38,7 @@ * * @author kaito */ +@Slf4j public class ItemStorage implements IItemStorage { private String fileName; @@ -81,7 +83,7 @@ private static boolean checkTableHeader(Page header) { try { data.read(flag); } catch (IOException e) { - e.printStackTrace(); + throw new PageCorruptedException(1, e); } return flag[0] == 1 && flag[1] == 2 && flag[2] == 3 && flag[3] == 4; } @@ -92,7 +94,7 @@ private static boolean checkTableHeader(Page header) { * @param fileName 文件名 * @return 解析或新建得到的数据项管理器对象 */ - public static IItemStorage ofFile(String fileName) throws FileException, IOException, PageException { + public static IItemStorage ofFile(String fileName) throws FileException, PageException { var pageStorage = PageManager.fromFile(fileName); var header = pageStorage.get(0); header.pin(); @@ -104,10 +106,16 @@ public static IItemStorage ofFile(String fileName) throws FileException, IOExcep } else { // 若表头标志不存在,就初始化对应的表信息。 // 只初始化headerFlag和tempFreePage,表头信息位置统一由setMetadata来实现 - var bytes = JBBPOut.BeginBin(). - Byte(1, 2, 3, 4). - Int(1). - End().toByteArray(); + byte[] bytes; + try { + bytes = JBBPOut.BeginBin(). + Byte(1, 2, 3, 4). + Int(1). + End().toByteArray(); + } catch (IOException e) { + header.unpin(); + throw new PageCorruptedException(1, e); + } header.patchData(0, bytes); header.unpin(); return new ItemStorage(fileName, 1, 0, pageStorage); @@ -167,9 +175,13 @@ private void releasePage(Page page) { * * @return 解析得到的表头对象 */ - private static TableHeader parseHeader(Page page) throws IOException { - return JBBPParser.prepare("int headerFlag;int tempFreePage;byte hasHeaderInfo;long headerUuid;"). - parse(page.getData()).mapTo(new TableHeader()); + private static TableHeader parseHeader(Page page) throws PageCorruptedException { + try { + return JBBPParser.prepare("int headerFlag;int tempFreePage;byte hasHeaderInfo;long headerUuid;"). + parse(page.getData()).mapTo(new TableHeader()); + } catch (IOException e) { + throw new PageCorruptedException(1, e); + } } /** @@ -179,15 +191,19 @@ private static TableHeader parseHeader(Page page) throws IOException { * @param item 一个数据项记录 * @return 解析得到的数据 */ - private static byte[] parseData(Page page, Item item) throws IOException, ItemException { + private static byte[] parseData(Page page, Item item) throws PageCorruptedException { int offset = item.offset; var data = page.getData(); - data.skip(offset); - var content = JBBPParser.prepare("byte type;int size;byte[size] data;").parse(data).mapTo(new ItemContent()); - if (content.type != NORMAL_DATA) { - throw new ItemException(2); + try { + data.skip(offset); + var content = JBBPParser.prepare("byte type;int size;byte[size] data;").parse(data).mapTo(new ItemContent()); + if (content.type != NORMAL_DATA) { + throw new PageCorruptedException(2); + } + return content.data; + } catch (IOException e) { + throw new PageCorruptedException(2, e); } - return content.data; } /** @@ -195,87 +211,110 @@ private static byte[] parseData(Page page, Item item) throws IOException, ItemEx * * @param page 页 * @return 该页是否已经被初始化 - * @throws IOException */ - private boolean checkPageHeader(Page page) throws IOException { + private boolean checkPageHeader(Page page) { byte[] pageFlag = new byte[4]; - var n = page.getData().read(pageFlag); + try { + var n = page.getData().read(pageFlag); + } catch (IOException e) { + throw new PageCorruptedException(1, e); + } return pageFlag[0] == 2 && pageFlag[1] == 3 && pageFlag[2] == 4 && pageFlag[3] == 5; } - private Optional getPageHeader(Page page) throws IOException { - if (checkPageHeader(page)) { - // 如果页头信息正确,就解析得到页头信息的对象。 - var data = page.getData(); - var pageHeader = JBBPParser.prepare("int pageFlag;long lsn;int leftSpace;int recordNumber;" + - "item [recordNumber]{int uuid;int offset;}").parse(data); - var p = pageHeader.mapTo(new PageHeader()); - return Optional.of(p); - } else { - // 否则返回空,交由上层处理。 - return Optional.empty(); + private Optional getPageHeader(Page page) { + try { + if (checkPageHeader(page)) { + // 如果页头信息正确,就解析得到页头信息的对象。 + var data = page.getData(); + var pageHeader = JBBPParser.prepare("int pageFlag;long lsn;int leftSpace;int recordNumber;" + + "item [recordNumber]{int uuid;int offset;}").parse(data); + var p = pageHeader.mapTo(new PageHeader()); + return Optional.of(p); + } + } catch (IOException e) { + throw new PageCorruptedException(1, e); } + // 否则返回空,交由上层处理。 + return Optional.empty(); } - private PageHeader initPage(Page page) throws IOException, PageException { - final byte[] bytes = JBBPOut.BeginBin(). - Byte(2, 3, 4, 5). // 页头标志位 - Long(0). // 日志记录位置,以后若有日志记录点则使用 - Int(4072). // 剩余空间大小 - Int(0). // 记录数目 - End().toByteArray(); - page.patchData(0, bytes); + private PageHeader initPage(Page page) { + final byte[] bytes; + try { + bytes = JBBPOut.BeginBin(). + Byte(2, 3, 4, 5). // 页头标志位 + Long(0). // 日志记录位置,以后若有日志记录点则使用 + Int(4072). // 剩余空间大小 + Int(0). // 记录数目 + End().toByteArray(); + page.patchData(0, bytes); + } catch (IOException | PageException e) { + throw new PageCorruptedException(1, e); + } return new PageHeader(0, 0, 4072, 0); } /** * 修改当前第一个可用页 */ - private void addTempFreePage() throws IOException, PageException { + private void addTempFreePage() { this.tempFreePage += 1; var page = pageStorage.get(0); page.pin(); - var tempFreePage = JBBPOut.BeginBin(). - Int(this.tempFreePage) - .End().toByteArray(); - page.patchData(4, tempFreePage); - page.unpin(); + byte[] tempFreePage; + try { + tempFreePage = JBBPOut.BeginBin(). + Int(this.tempFreePage) + .End().toByteArray(); + page.patchData(4, tempFreePage); + } catch (Exception e) { + throw new PageCorruptedException(1, e); + } finally { + page.unpin(); + } } @Override - public synchronized long insertItem(TransactionContext txContext, byte[] item) throws IOException, PageException { + public synchronized long insertItem(TransactionContext txContext, byte[] item) { var page = getPage(this.tempFreePage); - var pageHeaderOp = getPageHeader(page); - if (pageHeaderOp.isEmpty()) { - // 如果获取的页没有页头信息,则进行初始化。 - pageHeaderOp = Optional.of(initPage(page)); - } - var pageHeader = pageHeaderOp.get(); - if (pageHeader.leftSpace - Math.min(item.length, MAX_RECORD_SIZE) <= MIN_LEFT_SPACE) { - // 如果剩余空间过小的话,就切换到下一个页进行,同时修改表头信息.并且,若数据过大则使用拉链,所以取512和数据大小较小的 - addTempFreePage(); - releasePage(page); - return insertItem(txContext, item); - } else { - // 剩余空间足够,则插入 - int rnd = Math.abs(new Random().nextInt()); - long s = this.tempFreePage; - long uuid = ((s << 32) + (long) (rnd)); - // 保证uuid不重复 - while (checkUuidExist(uuid)) { - rnd = Math.abs(new Random().nextInt()); - uuid = ((s << 32) + (long) (rnd)); + try { + var pageHeaderOp = getPageHeader(page); + if (pageHeaderOp.isEmpty()) { + // 如果获取的页没有页头信息,则进行初始化。 + pageHeaderOp = Optional.of(initPage(page)); } - insertToPage(page, pageHeader, txContext, item, rnd); + var pageHeader = pageHeaderOp.get(); + if (pageHeader.leftSpace - Math.min(item.length, MAX_RECORD_SIZE) <= MIN_LEFT_SPACE) { + // 如果剩余空间过小的话,就切换到下一个页进行,同时修改表头信息.并且,若数据过大则使用拉链,所以取512和数据大小较小的 + addTempFreePage(); + return insertItem(txContext, item); + } else { + // 剩余空间足够,则插入 + int rnd = Math.abs(new Random().nextInt()); + long s = this.tempFreePage; + long uuid = ((s << 32) + (long) (rnd)); + // 保证uuid不重复 + while (checkUuidExist(uuid)) { + rnd = Math.abs(new Random().nextInt()); + uuid = ((s << 32) + (long) (rnd)); + } + insertToPage(page, pageHeader, txContext, item, rnd); + return uuid; + } + } catch (PageCorruptedException e) { + throw e; + } catch (Exception e) { + throw new PageCorruptedException(3); + } finally { releasePage(page); - return uuid; } } /** * 将数据插入到页内对应位置,并修改页头信息 */ - private void insertToPage(Page page, PageHeader pageHeader, TransactionContext txContext, byte[] item, int rnd) throws IOException, PageException { + private void insertToPage(Page page, PageHeader pageHeader, TransactionContext txContext, byte[] item, int rnd) { if (item.length < MAX_RECORD_SIZE) { int offset = 0; if (pageHeader.recordNumber == 0) { @@ -285,87 +324,114 @@ private void insertToPage(Page page, PageHeader pageHeader, TransactionContext t // 如果页内有插入的数据,则读取其offset并推算自己的offset offset = pageHeader.item[pageHeader.recordNumber - 1].offset - item.length - DATA_EXTRA_SIZE; } - //修改数据项头信息 - var i = JBBPOut.BeginBin(). - Int(rnd). - Int(offset). - End().toByteArray(); - page.patchData(ITEM_OFFSET + pageHeader.recordNumber * ITEM_SIZE, i); - //修改数据信息 - var data = JBBPOut.BeginBin(). - Byte(NORMAL_DATA). - Int(item.length). - Byte(item).End().toByteArray(); - page.patchData(offset, data); - //修改页头信息 - var headerInfo = JBBPOut.BeginBin(). - Int(pageHeader.leftSpace - item.length - DATA_EXTRA_SIZE - ITEM_SIZE). - Int(pageHeader.recordNumber + 1). - End().toByteArray(); - page.patchData(LEFT_SPACE_OFFSET, headerInfo); + try { + //修改数据项头信息 + var i = JBBPOut.BeginBin(). + Int(rnd). + Int(offset). + End().toByteArray(); + page.patchData(ITEM_OFFSET + pageHeader.recordNumber * ITEM_SIZE, i); + //修改数据信息 + var data = JBBPOut.BeginBin(). + Byte(NORMAL_DATA). + Int(item.length). + Byte(item).End().toByteArray(); + page.patchData(offset, data); + } catch (IOException | PageException e) { + throw new PageCorruptedException(2, e); + } + try { + //修改页头信息 + var headerInfo = JBBPOut.BeginBin(). + Int(pageHeader.leftSpace - item.length - DATA_EXTRA_SIZE - ITEM_SIZE). + Int(pageHeader.recordNumber + 1). + End().toByteArray(); + page.patchData(LEFT_SPACE_OFFSET, headerInfo); + } catch (IOException | PageException e) { + throw new PageCorruptedException(1, e); + } } } @Override - public synchronized void insertItemWithUuid(TransactionContext txContext, byte[] item, long uuid) throws IOException, PageException { + public synchronized void insertItemWithUuid(TransactionContext txContext, byte[] item, long uuid) { if (!checkUuidExist(uuid)) { // 若不存在,则要恢复 var page = getPage(uuid); - var pageHeaderOp = getPageHeader(page); - if (pageHeaderOp.isEmpty()) { - // 如果获取的页没有页头信息,则进行初始化。 - pageHeaderOp = Optional.of(initPage(page)); + try { + var pageHeaderOp = getPageHeader(page); + if (pageHeaderOp.isEmpty()) { + // 如果获取的页没有页头信息,则进行初始化。 + pageHeaderOp = Optional.of(initPage(page)); + } + int rnd = getRndByUuid(uuid); + insertToPage(page, pageHeaderOp.get(), txContext, item, rnd); + } catch (Exception e) { + throw new PageCorruptedException(3); + } finally { + releasePage(page); } - int rnd = getRndByUuid(uuid); - insertToPage(page, pageHeaderOp.get(), txContext, item, rnd); - releasePage(page); } // 若存在则不需要恢复,直接返回 } /** - * 检查uuid是否存在 + * 检查uuid是否存在,若Uuid的页号超过当前可用页,则直接返回False * * @param uuid * @return */ - private boolean checkUuidExist(long uuid) throws IOException { + private boolean checkUuidExist(long uuid) { + var pageId = uuid >> 32; + if (pageId > this.tempFreePage || pageId < 0) { + return false; + } var page = getPage(uuid); int id = getRndByUuid(uuid); - var pageHeader = getPageHeader(page); - if (pageHeader.isEmpty()) { - return false; - } else { - for (var item : pageHeader.get().item) { - if (id == item.uuid) { - return true; + try { + var pageHeader = getPageHeader(page); + if (pageHeader.isEmpty()) { + return false; + } else { + for (var item : pageHeader.get().item) { + if (id == item.uuid) { + return true; + } } } + return false; + } catch (Exception e) { + throw new PageCorruptedException(2); + } finally { + releasePage(page); } - return false; } @Override - public byte[] queryItemByUuid(long uuid) throws UUIDException, IOException, ItemException { + public byte[] queryItemByUuid(long uuid) throws UUIDException { if (checkUuidExist(uuid)) { var page = getPage(uuid); - var header = getPageHeader(page); - if (header.isEmpty()) { - releasePage(page); - throw new UUIDException(2); - } else { - // 遍历所有的item读取数据 - var items = header.get().item; - int id = getRndByUuid(uuid); - for (var item : items) { - if (item.uuid == id) { - var s = parseData(page, item); - releasePage(page); - return s; + try { + var header = getPageHeader(page); + if (header.isEmpty()) { + throw new UUIDException(2); + } else { + // 遍历所有的item读取数据 + var items = header.get().item; + int id = getRndByUuid(uuid); + for (var item : items) { + if (item.uuid == id) { + var s = parseData(page, item); + return s; + } } } + return new byte[0]; + } catch (Exception e) { + throw new UUIDException(2); + } finally { + releasePage(page); } - return new byte[0]; } else { throw new UUIDException(2); } @@ -373,69 +439,94 @@ public byte[] queryItemByUuid(long uuid) throws UUIDException, IOException, Item @Override - public List listItemByPageId(int pageId) throws IOException, ItemException { + public List listItemByPageId(int pageId) { var page = getPage(pageId); - List bytes = new ArrayList<>(); - var pageHeaderOp = getPageHeader(page); - if (pageHeaderOp.isPresent()) { - var pageHeader = pageHeaderOp.get(); - for (var item : pageHeader.item) { - var data = parseData(page, item); - bytes.add(data); + try { + List bytes = new ArrayList<>(); + var pageHeaderOp = getPageHeader(page); + if (pageHeaderOp.isPresent()) { + var pageHeader = pageHeaderOp.get(); + for (var item : pageHeader.item) { + var data = parseData(page, item); + bytes.add(data); + } } + return bytes; + } catch (Exception e) { + throw new PageCorruptedException(2); + } finally { + releasePage(page); } - releasePage(page); - return bytes; } @Override - public void updateItemByUuid(TransactionContext txContext, long uuid, byte[] item) throws UUIDException, IOException, PageException { + public void updateItemByUuid(TransactionContext txContext, long uuid, byte[] item) throws UUIDException, PageCorruptedException { var page = getPage(uuid); - if (checkUuidExist(uuid)) { - var pageHeader = getPageHeader(page); - var items = pageHeader.get().item; - for (var i : items) { - if (i.uuid == getRndByUuid(uuid)) { - var offset = i.offset; - var bytes = JBBPOut.BeginBin(). - Byte(NORMAL_DATA). - Int(item.length) - .Byte(item) - .End().toByteArray(); - page.patchData(offset, bytes); - releasePage(page); - return; + try { + if (checkUuidExist(uuid)) { + var pageHeader = getPageHeader(page); + var items = pageHeader.get().item; + try { + for (var i : items) { + if (i.uuid == getRndByUuid(uuid)) { + var offset = i.offset; + var bytes = JBBPOut.BeginBin(). + Byte(NORMAL_DATA). + Int(item.length) + .Byte(item) + .End().toByteArray(); + page.patchData(offset, bytes); + return; + } + } + } catch (PageException | IOException e) { + throw new PageCorruptedException(2, e); } + } else { + throw new UUIDException(2); } - } else { + } finally { releasePage(page); - throw new UUIDException(2); } } @Override - public byte[] getMetadata() throws IOException, ItemException, UUIDException { + public byte[] getMetadata() { var page = getPage(0); var header = parseHeader(page); if (checkTableHeader(page) && header.hasHeaderInfo == HAS_HEADER) { // 若表头已经被初始化并且有标志位的话,就说明有表头信息,进行获取. - var h = queryItemByUuid(header.headerUuid); - releasePage(page); + byte[] h; + try { + h = queryItemByUuid(header.headerUuid); + } catch (UUIDException e) { + // 若UUID不存在,肯定是页头的问题,因为控制过程都是ItemStorage操作的 + throw new PageCorruptedException(1, e); + } finally { + releasePage(page); + } return h; } else { - throw new ItemException(1); + // 默认Metadata为空 + releasePage(page); + return new byte[0]; } } @Override - public void setMetadata(TransactionContext txContext, byte[] metadata) throws IOException, PageException { + public void setMetadata(TransactionContext txContext, byte[] metadata) throws PageCorruptedException { var page = getPage(0); - var headerUuid = insertItem(txContext, metadata); - var bytes = JBBPOut.BeginBin(). - Byte(HAS_HEADER). - Long(headerUuid).End().toByteArray(); - page.patchData(HEADER_OFFSET, bytes); - releasePage(page); + try { + var headerUuid = insertItem(txContext, metadata); + var bytes = JBBPOut.BeginBin(). + Byte(HAS_HEADER). + Long(headerUuid).End().toByteArray(); + page.patchData(HEADER_OFFSET, bytes); + } catch (Exception e) { + throw new PageCorruptedException(1, e); + } finally { + releasePage(page); + } } @Override diff --git a/src/main/java/net/kaaass/rumbase/dataitem/exception/ItemException.java b/src/main/java/net/kaaass/rumbase/dataitem/exception/ItemException.java deleted file mode 100644 index bd02893..0000000 --- a/src/main/java/net/kaaass/rumbase/dataitem/exception/ItemException.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.kaaass.rumbase.dataitem.exception; - -import net.kaaass.rumbase.exception.RumbaseException; - -import java.util.HashMap; -import java.util.Map; - -/** - * 对数据库进行解析时出现的异常错误 - * - * @author kaito - */ -public class ItemException extends RumbaseException { - public static final Map REASONS = new HashMap<>() {{ - put(1, "没有相应表头信息"); - put(2, "所查找的数据项信息解析错误"); - }}; - - public ItemException(int subID) { - super(6001, subID, REASONS.get(subID)); - } -} diff --git a/src/main/java/net/kaaass/rumbase/dataitem/exception/PageCorruptedException.java b/src/main/java/net/kaaass/rumbase/dataitem/exception/PageCorruptedException.java new file mode 100644 index 0000000..11255fd --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/dataitem/exception/PageCorruptedException.java @@ -0,0 +1,27 @@ +package net.kaaass.rumbase.dataitem.exception; + +import net.kaaass.rumbase.exception.RumbaseRuntimeException; + +import java.util.HashMap; +import java.util.Map; + +/** + * 对数据库进行解析时出现的异常错误 + * + * @author kaito + */ +public class PageCorruptedException extends RumbaseRuntimeException { + public static final Map REASONS = new HashMap<>() {{ + put(1, "没有相应表头信息或表头信息损坏"); + put(2, "数据项信息损坏"); + put(3, "数据插入异常"); + }}; + + public PageCorruptedException(int subID) { + super(7002, subID, REASONS.get(subID)); + } + + public PageCorruptedException(int subID, Throwable e) { + super(7002, subID, REASONS.get(subID), e); + } +} diff --git a/src/main/java/net/kaaass/rumbase/exception/RumbaseRuntimeException.java b/src/main/java/net/kaaass/rumbase/exception/RumbaseRuntimeException.java new file mode 100644 index 0000000..c719220 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/exception/RumbaseRuntimeException.java @@ -0,0 +1,41 @@ +package net.kaaass.rumbase.exception; +/** + * 运行时错误基类 + *

+ * 详细的错误规范参考 {@link net.kaaass.rumbase.exception.RumbaseException} + * + * @author kaaass + */ +public class RumbaseRuntimeException extends RuntimeException { + + private int mainId; + + private int subId; + + /** + * 构造Rumbase运行时异常 + * + * @param mainId 主错误号 + * @param subId 子错误号 + * @param reason 错误原因 + */ + public RumbaseRuntimeException(int mainId, int subId, String reason) { + super(String.format("E%d-%d: %s", mainId, subId, reason)); + this.mainId = mainId; + this.subId = subId; + } + + /** + * 构造Rumbase运行时异常 + * + * @param mainId 主错误号 + * @param subId 子错误号 + * @param reason 错误原因 + * @param cause 源错误 + */ + public RumbaseRuntimeException(int mainId, int subId, String reason, Throwable cause) { + super(String.format("E%d-%d: %s", mainId, subId, reason), cause); + this.mainId = mainId; + this.subId = subId; + } +} diff --git a/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java index b7137d8..fb751a0 100644 --- a/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java +++ b/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java @@ -29,7 +29,10 @@ public interface IRecordStorage { * @return 记录数据字节 * @throws RecordNotFoundException 若记录不存在、不可见,抛出错误 */ - byte[] query(TransactionContext txContext, long recordId) throws RecordNotFoundException; + default byte[] query(TransactionContext txContext, long recordId) throws RecordNotFoundException { + var result = queryOptional(txContext, recordId); + return result.orElseThrow(() -> new RecordNotFoundException(2)); + } /** * 由记录ID查询记录数据,并忽略由事务造成的记录不可见。若物理记录不存在,将抛出运行时错误 @@ -38,7 +41,7 @@ public interface IRecordStorage { * @param recordId 记录ID * @return 记录数据字节,若记录不可见则返回Optional.empty() */ - Optional queryOptional(TransactionContext txContext, long recordId); + Optional queryOptional(TransactionContext txContext, long recordId) throws RecordNotFoundException; /** * 由记录ID删除记录数据 diff --git a/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java new file mode 100644 index 0000000..a3b1a28 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java @@ -0,0 +1,257 @@ +package net.kaaass.rumbase.record; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.dataitem.IItemStorage; +import net.kaaass.rumbase.dataitem.exception.UUIDException; +import net.kaaass.rumbase.record.exception.NeedRollbackException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.record.exception.StorageCorruptedException; +import net.kaaass.rumbase.transaction.TransactionContext; +import net.kaaass.rumbase.transaction.TransactionIsolation; +import net.kaaass.rumbase.transaction.TransactionStatus; + +import java.util.Optional; + +/** + * 实现MVCC并且通过transaction模块实现2PL的记录存储 + * + * @author kaaass + */ +@Slf4j +@RequiredArgsConstructor +public class MvccRecordStorage implements IRecordStorage { + + @NonNull + private final IItemStorage storage; + + @NonNull + private final String identifiedName; + + /** + * 元数据缓存。仅在第一条metadata可见时有效 + */ + private byte[] metadataCache = null; + + @SneakyThrows + @Override + public long insert(TransactionContext txContext, byte[] rawData) { + var data = new byte[rawData.length + 8]; + // 拼接data + writeXmin(data, txContext.getXid()); + writeXmax(data, 0); + writePayload(data, rawData); + // 不用检查版本跳跃的原因是,插入本身不用;更新操作必定先删除,而删除检查 + // 插入记录 + return storage.insertItem(txContext, data); + } + + @Override + public Optional queryOptional(TransactionContext txContext, long recordId) throws RecordNotFoundException { + // 串行化事务则申请读锁 + if (txContext.getIsolation() == TransactionIsolation.SERIALIZABLE) { + txContext.sharedLock(recordId, this.identifiedName); + } + // 读取数据 + byte[] data; + try { + data = storage.queryItemByUuid(recordId); + } catch (UUIDException e) { + throw new RecordNotFoundException(1, e); + } + // 检查可见性 + if (isVisible(txContext, data)) { + return Optional.of(readPayload(data)); + } else { + return Optional.empty(); + } + } + + @SneakyThrows + @Override + public void delete(TransactionContext txContext, long recordId) { + // 读取数据 + var data = storage.queryItemByUuid(recordId); + // 判断可见性,若不可见直接返回 + if (!isVisible(txContext, data)) { + return; + } + // 申请互斥锁 + txContext.exclusiveLock(recordId, this.identifiedName); + var xid = txContext.getXid(); + if (xid == 0) { + xid = -1; + } + // 是否已经被删除 + var xmax = readXmax(data); + if (xmax == xid) { + return; + } + // 版本跳跃检查 + if (isVersionSkip(txContext, data)) { + log.info("事务 {} 操作记录 {} 发生版本跳跃", txContext.getXid(), recordId); + throw new NeedRollbackException(1); + } + // 更新记录 + writeXmax(data, xid); + storage.updateItemByUuid(txContext, recordId, data); + } + + /** + * 判断可见性 + */ + private boolean isVisible(TransactionContext txContext, byte[] data) { + var isolation = txContext.getIsolation(); + var xid = txContext.getXid(); + var xmin = readXmin(data); + var xmax = readXmax(data); + if (isolation == TransactionIsolation.READ_UNCOMMITTED) { + // 读未提交:没被删除 + return xmax == 0; + } else if (isolation == TransactionIsolation.READ_COMMITTED) { + // 读已提交 + // 事务自身创建,且未被删除 + if (xmin == xid && xmax == 0) { + return true; + } + // 记录已提交 + if (statusOf(xmin, txContext) == TransactionStatus.COMMITTED) { + // 未被删除 + if (xmax == 0) { + return true; + } + // 被自身删除则不可见 + if (xmax == xid) { + return false; + } + // 被删除但是删除事务未提交 + return statusOf(xmax, txContext) != TransactionStatus.COMMITTED; + } + return false; + } else if (isolation == TransactionIsolation.REPEATABLE_READ) { + // 可重复读 + // 事务自身创建,且未被删除 + if (xmin == xid && xmax == 0) { + return true; + } + // 记录被可见事务提交 + if (xmin < xid && !txContext.getSnapshot().contains(xmin) + && statusOf(xmin, txContext) == TransactionStatus.COMMITTED) { + // 未被删除 + if (xmax == 0) { + return true; + } + // 被自身删除则不可见 + if (xmax == xid) { + return false; + } + // 被删除但是删除事务未提交 + return xmax > xid || txContext.getSnapshot().contains(xmax) + || statusOf(xmax, txContext) != TransactionStatus.COMMITTED; + } + } else if (isolation == TransactionIsolation.SERIALIZABLE) { + // 可串行化:没被删除,依靠2PL保证事务性 + return xmax == 0; + } + return false; + } + + /** + * 判断版本跳跃 + */ + private boolean isVersionSkip(TransactionContext txContext, byte[] data) { + var isolation = txContext.getIsolation(); + if (isolation != TransactionIsolation.REPEATABLE_READ) { + // 只有可重复读需要检测版本跳跃 + // 序列化隔离度因为严格2PL,根本不出现版本跳跃 + return false; + } + var xmax = readXmax(data); + // 事务已经提交且对当前事务不可见 + return statusOf(xmax, txContext) == TransactionStatus.COMMITTED + && (xmax > txContext.getXid() || txContext.getSnapshot().contains(xmax)); + } + + @SneakyThrows + @Override + public byte[] getMetadata(TransactionContext txContext) { + // TODO 申请全表锁,用this锁代替 + synchronized (this) { + if (this.metadataCache != null) { + return this.metadataCache; + } + // 读入记录数据 + var uuids = storage.getMetadata(); + for (int st = 0; st < uuids.length; st += 8) { + var uuid = MvccUtil.readLong(uuids, st); + Optional result; + try { + result = queryOptional(txContext, uuid); + } catch (RecordNotFoundException e) { + throw new StorageCorruptedException(1, e); + } + if (result.isPresent()) { + // 如果是第一个,更新cache + if (st == 0) { + this.metadataCache = result.get(); + } + return result.get(); + } + } + } + return new byte[0]; + } + + @Override + public void setMetadata(TransactionContext txContext, byte[] metadata) { + // TODO 申请全表锁,用this锁代替 + // 主要逻辑:为了保证事务性,使用UUID数组,结合可见性选择对应的记录。倒序记录便于存储。 + synchronized (this) { + // 创建一条记录 + var newId = insert(txContext, metadata); + // 保存记录至下层 metadata + var oldMetadata = storage.getMetadata(); + var newMetadata = new byte[oldMetadata.length + 8]; + System.arraycopy(oldMetadata, 0, newMetadata, 8, oldMetadata.length); + MvccUtil.writeLong(newMetadata, 0, newId); + storage.setMetadata(txContext, newMetadata); + // 缓存取消 + this.metadataCache = null; + } + } + + private static TransactionStatus statusOf(int xid, TransactionContext txContext) { + if (xid == 0 || xid == -1) { + return TransactionStatus.COMMITTED; + } + return txContext.getManager().getContext(xid).getStatus(); + } + + private static void writeXmin(byte[] data, int xmin) { + MvccUtil.writeInt(data, 0, xmin); + } + + private static void writeXmax(byte[] data, int xmax) { + MvccUtil.writeInt(data, 4, xmax); + } + + private static void writePayload(byte[] data, byte[] payload) { + System.arraycopy(payload, 0, data, 8, payload.length); + } + + private static int readXmin(byte[] data) { + return MvccUtil.readInt(data, 0); + } + + private static int readXmax(byte[] data) { + return MvccUtil.readInt(data, 4); + } + + private static byte[] readPayload(byte[] data) { + var result = new byte[data.length - 8]; + System.arraycopy(data, 8, result, 0, result.length); + return result; + } +} diff --git a/src/main/java/net/kaaass/rumbase/record/MvccUtil.java b/src/main/java/net/kaaass/rumbase/record/MvccUtil.java new file mode 100644 index 0000000..e31b454 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/record/MvccUtil.java @@ -0,0 +1,81 @@ +package net.kaaass.rumbase.record; + +/** + * MVCC常用操作函数库 + * + * @author kaaass + */ +public class MvccUtil { + + /** + * 将整数以大端序写入字节数组 + * + * @param data 目的字节数组 + * @param offset 偏移 + * @param value 写入值 + */ + public static void writeInt(byte[] data, int offset, int value) { + assert data.length >= 4; + data[offset] = (byte) (value >> (8 * 3)); + data[offset + 1] = (byte) ((value >> (8 * 2)) & 0xff); + data[offset + 2] = (byte) ((value >> 8) & 0xff); + data[offset + 3] = (byte) (value & 0xff); + } + + /** + * 从字节数组读入大端序整数 + * + * @param data 源字节数组 + * @param offset 偏移 + * @return 读入的整数 + */ + public static int readInt(byte[] data, int offset) { + assert data.length >= 4; + int result = 0; + result |= (data[offset] & 0xff) << (8 * 3); + result |= (data[offset + 1] & 0xff) << (8 * 2); + result |= (data[offset + 2] & 0xff) << 8; + result |= data[offset + 3] & 0xff; + return result; + } + + /** + * 将长整数以大端序写入字节数组 + * + * @param data 目的字节数组 + * @param offset 偏移 + * @param value 写入值 + */ + public static void writeLong(byte[] data, int offset, long value) { + assert data.length >= 8; + data[offset] = (byte) (value >> (8 * 7)); + data[offset + 1] = (byte) ((value >> (8 * 6)) & 0xff); + data[offset + 2] = (byte) ((value >> (8 * 5)) & 0xff); + data[offset + 3] = (byte) ((value >> (8 * 4)) & 0xff); + data[offset + 4] = (byte) ((value >> (8 * 3)) & 0xff); + data[offset + 5] = (byte) ((value >> (8 * 2)) & 0xff); + data[offset + 6] = (byte) ((value >> 8) & 0xff); + data[offset + 7] = (byte) (value & 0xff); + } + + /** + * 从字节数组读入大端序长整数 + * + * @param data 源字节数组 + * @param offset 偏移 + * @return 读入的整数 + */ + public static long readLong(byte[] data, int offset) { + assert data.length >= 8; + long result = 0; + result |= (long) (data[offset] & 0xff) << (8 * 7); + result |= (long) (data[offset + 1] & 0xff) << (8 * 6); + result |= (long) (data[offset + 2] & 0xff) << (8 * 5); + result |= (long) (data[offset + 3] & 0xff) << (8 * 4); + result |= (long) (data[offset + 4] & 0xff) << (8 * 3); + result |= (long) (data[offset + 5] & 0xff) << (8 * 2); + result |= (long) (data[offset + 6] & 0xff) << 8; + result |= (long) data[offset + 7] & 0xff; + return result; + } +} diff --git a/src/main/java/net/kaaass/rumbase/record/RecordManager.java b/src/main/java/net/kaaass/rumbase/record/RecordManager.java index 865e233..b6ff7b4 100644 --- a/src/main/java/net/kaaass/rumbase/record/RecordManager.java +++ b/src/main/java/net/kaaass/rumbase/record/RecordManager.java @@ -1,6 +1,7 @@ package net.kaaass.rumbase.record; -import net.kaaass.rumbase.record.mock.MockRecordStorage; +import lombok.SneakyThrows; +import net.kaaass.rumbase.dataitem.ItemManager; /** * 记录管理类 @@ -16,7 +17,10 @@ public class RecordManager { * @param filepath 记录文件 * @return 记录存储对象 */ + @SneakyThrows public static IRecordStorage fromFile(String filepath) { - return MockRecordStorage.ofFile(filepath); + var itemStorage = ItemManager.fromFile(filepath); + var identifier = "TBL_" + filepath; + return new MvccRecordStorage(itemStorage, identifier); } } diff --git a/src/main/java/net/kaaass/rumbase/record/exception/NeedRollbackException.java b/src/main/java/net/kaaass/rumbase/record/exception/NeedRollbackException.java new file mode 100644 index 0000000..85eb930 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/record/exception/NeedRollbackException.java @@ -0,0 +1,38 @@ +package net.kaaass.rumbase.record.exception; + +import net.kaaass.rumbase.exception.RumbaseRuntimeException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E5003 需要自动回滚异常 + *

+ * E5003-1 发生版本跳跃,需要自动回滚事务 + * + * @author kaaass + */ +public class NeedRollbackException extends RumbaseRuntimeException { + + public static final Map REASONS = new HashMap<>() {{ + put(1, "发生版本跳跃,需要自动回滚事务"); + }}; + + /** + * 需要自动回滚异常 + * + * @param subId 子错误号 + */ + public NeedRollbackException(int subId) { + super(5003, subId, REASONS.get(subId)); + } + + /** + * 需要自动回滚异常 + * + * @param subId 子错误号 + */ + public NeedRollbackException(int subId, Throwable cause) { + super(5003, subId, REASONS.get(subId), cause); + } +} diff --git a/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java b/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java index 51f8ccb..8c27daa 100644 --- a/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java +++ b/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java @@ -9,6 +9,7 @@ * E5001 记录不存在异常 *

* E5001-1 物理记录不存在 + * E5001-2 由于事务性,记录不可见 * * @author kaaass */ @@ -16,6 +17,7 @@ public class RecordNotFoundException extends RumbaseException { public static final Map REASONS = new HashMap<>() {{ put(1, "物理记录不存在"); + put(2, "由于事务性,记录不可见"); }}; /** @@ -26,4 +28,13 @@ public class RecordNotFoundException extends RumbaseException { public RecordNotFoundException(int subId) { super(5001, subId, REASONS.get(subId)); } + + /** + * 记录不存在异常 + * + * @param subId 子错误号 + */ + public RecordNotFoundException(int subId, Throwable cause) { + super(5001, subId, REASONS.get(subId), cause); + } } diff --git a/src/main/java/net/kaaass/rumbase/record/exception/StorageCorruptedException.java b/src/main/java/net/kaaass/rumbase/record/exception/StorageCorruptedException.java new file mode 100644 index 0000000..5b1aeff --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/record/exception/StorageCorruptedException.java @@ -0,0 +1,33 @@ +package net.kaaass.rumbase.record.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E5002 存储数据错误异常 + *

+ * E5002-1 存储数据元信息不存在 + * + * @author kaaass + */ +public class StorageCorruptedException extends RumbaseException { + + public static final Map REASONS = new HashMap<>() {{ + put(1, "存储数据元信息不存在"); + }}; + + /** + * 记录不存在异常 + * + * @param subId 子错误号 + */ + public StorageCorruptedException(int subId) { + super(5002, subId, REASONS.get(subId)); + } + + public StorageCorruptedException(int subId, Throwable cause) { + super(5002, subId, REASONS.get(subId), cause); + } +} diff --git a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java index 0dcdaf0..588a8e7 100644 --- a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java @@ -2,7 +2,7 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; -import net.kaaass.rumbase.dataitem.exception.ItemException; +import net.kaaass.rumbase.dataitem.exception.PageCorruptedException; import net.kaaass.rumbase.dataitem.exception.UUIDException; import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.page.exception.PageException; @@ -62,7 +62,7 @@ public void testCreateFile() throws IOException, FileException, PageException { /** * 进行插入的测试 */ - public void testInsert() throws FileException, IOException, PageException, UUIDException, ItemException { + public void testInsert() throws FileException, IOException, PageException, UUIDException, PageCorruptedException { String fileName = "testInsert.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; @@ -91,7 +91,7 @@ public void testInsertWithUUID() throws FileException, IOException, PageExceptio iItemStorage.insertItemWithUuid(txContext, bytes, uuid); try { assertArrayEquals(bytes, iItemStorage.queryItemByUuid(uuid)); - } catch (UUIDException | ItemException e) { + } catch (UUIDException | PageCorruptedException e) { e.printStackTrace(); } @@ -103,7 +103,7 @@ public void testInsertWithUUID() throws FileException, IOException, PageExceptio /** * 对插入大量数据进行测试 */ - public void testManyInsert() throws FileException, IOException, PageException, UUIDException, ItemException { + public void testManyInsert() throws FileException, IOException, PageException, UUIDException, PageCorruptedException { String fileName = "testInsertMany.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; @@ -123,7 +123,7 @@ public void testManyInsert() throws FileException, IOException, PageException, U /** * 获取整个页的数据项进行测试 */ - public void testQueryByPageID() throws FileException, IOException, PageException, ItemException { + public void testQueryByPageID() throws FileException, IOException, PageException, PageCorruptedException { String fileName = "testQueryByPageID.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; @@ -201,7 +201,7 @@ public void testSynInsert() throws IOException, FileException, PageException { /** * 对更新进行测试 */ - public void testUpdate() throws FileException, IOException, PageException, UUIDException, ItemException { + public void testUpdate() throws FileException, IOException, PageException, UUIDException, PageCorruptedException { String fileName = "testUpdate.db"; TransactionContext txContext = TransactionContext.empty(); IItemStorage iItemStorage = ItemManager.fromFile(fileName); @@ -228,7 +228,7 @@ public void testUpdate() throws FileException, IOException, PageException, UUIDE /** * 测试修改和获取表头信息 */ - public void testMeta() throws FileException, IOException, PageException, UUIDException, ItemException { + public void testMeta() throws FileException, IOException, PageException, UUIDException, PageCorruptedException { String fileName = "testMeta.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] result = new byte[]{1, 2, 3, 4}; diff --git a/src/test/java/net/kaaass/rumbase/record/FakeTxContext.java b/src/test/java/net/kaaass/rumbase/record/FakeTxContext.java new file mode 100644 index 0000000..f830edb --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/record/FakeTxContext.java @@ -0,0 +1,52 @@ +package net.kaaass.rumbase.record; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.transaction.TransactionContext; +import net.kaaass.rumbase.transaction.TransactionIsolation; +import net.kaaass.rumbase.transaction.TransactionManager; +import net.kaaass.rumbase.transaction.TransactionStatus; + +import java.util.List; + +/** + * 用于测试可见性等的假事务上下文 + */ +@Slf4j +@Data +public class FakeTxContext implements TransactionContext { + + private List snapshot; + + private TransactionIsolation isolation; + + private TransactionStatus status; + + private TransactionManager manager; + + private int xid; + + @Override + public void start() { + } + + @Override + public void commit() { + this.status = TransactionStatus.COMMITTED; + } + + @Override + public void rollback() { + this.status = TransactionStatus.ABORTED; + } + + @Override + public void sharedLock(long uuid, String tableName) { + log.info("申请共享锁 {}.{}", tableName, uuid); + } + + @Override + public void exclusiveLock(long uuid, String tableName) { + log.info("申请互斥锁 {}.{}", tableName, uuid); + } +} diff --git a/src/test/java/net/kaaass/rumbase/record/FakeTxManager.java b/src/test/java/net/kaaass/rumbase/record/FakeTxManager.java new file mode 100644 index 0000000..f52adf0 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/record/FakeTxManager.java @@ -0,0 +1,75 @@ +package net.kaaass.rumbase.record; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.transaction.TransactionContext; +import net.kaaass.rumbase.transaction.TransactionIsolation; +import net.kaaass.rumbase.transaction.TransactionManager; +import net.kaaass.rumbase.transaction.TransactionStatus; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 用于测试可见性等的假事务管理器 + */ +@RequiredArgsConstructor +public class FakeTxManager implements TransactionManager { + + private int txCount = 0; + + private final Map txContextMap = new HashMap<>(); + + @NonNull + private final TransactionIsolation isolation; + + @Override + public TransactionContext createTransactionContext(TransactionIsolation isolation) { + var newTx = new FakeTxContext(); + newTx.setXid(++txCount); + newTx.setIsolation(isolation); + newTx.setManager(this); + var snapshot = + txContextMap.entrySet() + .stream() + .filter(tx -> tx.getValue().getStatus() == TransactionStatus.ACTIVE) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + newTx.setSnapshot(snapshot); + newTx.setStatus(TransactionStatus.ACTIVE); + txContextMap.put(txCount, newTx); + return newTx; + } + + public TransactionContext begin() { + return createTransactionContext(this.isolation); + } + + public TransactionContext begin(int xid) { + var newTx = new FakeTxContext(); + newTx.setXid(xid); + newTx.setIsolation(this.isolation); + newTx.setManager(this); + var snapshot = + txContextMap.entrySet() + .stream() + .filter(tx -> tx.getValue().getStatus() == TransactionStatus.ACTIVE) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + newTx.setSnapshot(snapshot); + newTx.setStatus(TransactionStatus.ACTIVE); + txContextMap.put(xid, newTx); + return newTx; + } + + @Override + public TransactionContext getContext(int xid) { + return txContextMap.get(xid); + } + + @Override + public void changeTransactionStatus(int xid, TransactionStatus status) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java index 583da7c..f7d9ed5 100644 --- a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java @@ -5,7 +5,6 @@ import net.kaaass.rumbase.record.exception.RecordNotFoundException; import net.kaaass.rumbase.transaction.TransactionContext; -import java.util.Arrays; import java.util.UUID; import static org.junit.Assert.assertArrayEquals; @@ -23,19 +22,15 @@ public void testQuery() { var storage = RecordManager.fromFile("test_query"); var context = TransactionContext.empty(); - var result = storage.queryOptional(context, UUID.randomUUID().getLeastSignificantBits()); - - assertTrue("unknown uuid should get empty", result.isEmpty()); - try { storage.query(context, UUID.randomUUID().getLeastSignificantBits()); - fail("unknown uuid should get exception"); + fail("unknown physical record should get exception"); } catch (RecordNotFoundException e) { log.error("Exception expected: ", e); } } - public void testInsert() { + public void testInsert() throws RecordNotFoundException { var storage = RecordManager.fromFile("test_insert"); var context = TransactionContext.empty(); @@ -46,7 +41,7 @@ public void testInsert() { assertArrayEquals(new byte[]{0x1, 0x2, 0x1f}, result.get()); } - public void testDelete() { + public void testDelete() throws RecordNotFoundException { var storage = RecordManager.fromFile("test_delete"); var context = TransactionContext.empty(); diff --git a/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java b/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java new file mode 100644 index 0000000..835bfa6 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java @@ -0,0 +1,80 @@ +package net.kaaass.rumbase.record; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.transaction.TransactionIsolation; + +@Slf4j +public class MvccReadCommitTest extends TestCase { + + public void testReadSelf() throws RecordNotFoundException { + var storage = RecordManager.fromFile("MvccReadCommitTest::testReadSelf"); + var manager = new FakeTxManager(TransactionIsolation.READ_COMMITTED); + // 创建事务1 + var tx1 = manager.begin(); + // 事务1的记录自身可见 + var a1 = storage.insert(tx1, new byte[]{0x23, 0x63}); + assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + // 事务2不可见a1 + var tx2 = manager.begin(); + assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + // 事务1新纪录也可见 + for (int i = 0; i < 100; i++) { + var uuid = storage.insert(tx1, new byte[]{0x23, 0x63, 0x44}); + assertTrue("uuid see a1", storage.queryOptional(tx1, uuid).isPresent()); + } + } + + public void testReadOther() throws RecordNotFoundException { + var storage = RecordManager.fromFile("MvccReadCommitTest::testReadOther"); + var manager = new FakeTxManager(TransactionIsolation.READ_COMMITTED); + // 创建事务12 + var tx1 = manager.begin(); + var tx2 = manager.begin(); + // 事务2创建的b1,事务1不可见 + var b1 = storage.insert(tx2, new byte[]{0x1, 0x2, 0x3}); + assertTrue("tx2 see b1", storage.queryOptional(tx2, b1).isPresent()); + assertTrue("tx1 blind b1", storage.queryOptional(tx1, b1).isEmpty()); + // 事务1创建的a1,事务2不可见 + var a1 = storage.insert(tx1, new byte[]{0x6, 0x5, 0x4, 0x32}); + assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + // 事务2提交,则事务1可见b1 + tx2.commit(); + assertTrue("tx1 see b1 after commit", storage.queryOptional(tx1, b1).isPresent()); + // 新建事务3 + var tx3 = manager.begin(); + // 事务3可以看到b1,但是不能看到a1 + assertTrue("tx3 see b1 after commit", storage.queryOptional(tx3, b1).isPresent()); + assertTrue("tx3 blind a1 before commit", storage.queryOptional(tx3, a1).isEmpty()); + // 提交事务1,a1可见 + tx1.commit(); + assertTrue("tx3 see a1 after commit", storage.queryOptional(tx3, a1).isPresent()); + } + + public void testDelete() throws RecordNotFoundException { + var storage = RecordManager.fromFile("MvccReadCommitTest::testDelete"); + var manager = new FakeTxManager(TransactionIsolation.READ_COMMITTED); + // 创建事务1、记录a1a2 + var tx1 = manager.begin(); + var a1 = storage.insert(tx1, new byte[]{0x1, 0x2, 0x3}); + var a2 = storage.insert(tx1, new byte[]{0x5, 0x6, 0x7}); + assertTrue(storage.queryOptional(tx1, a1).isPresent()); + assertTrue(storage.queryOptional(tx1, a2).isPresent()); + // 自身删除 + storage.delete(tx1, a1); + assertTrue("a1 should be deleted", storage.queryOptional(tx1, a1).isEmpty()); + // 提交事务1 + tx1.commit(); + // 事务2删除,事务3仍然可见a2 + var tx2 = manager.begin(); + var tx3 = manager.begin(); + storage.delete(tx2, a2); + assertTrue("tx2 blind a2 after delete", storage.queryOptional(tx2, a2).isEmpty()); + assertTrue("tx3 see a2 before commit", storage.queryOptional(tx3, a2).isPresent()); + // 事务2提交,事务3不可见a2 + tx2.commit(); + assertTrue("tx3 blind a2 after commit", storage.queryOptional(tx3, a2).isEmpty()); + } +} diff --git a/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java b/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java new file mode 100644 index 0000000..755d954 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java @@ -0,0 +1,104 @@ +package net.kaaass.rumbase.record; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.record.exception.NeedRollbackException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.transaction.TransactionContext; +import net.kaaass.rumbase.transaction.TransactionIsolation; +import net.kaaass.rumbase.transaction.TransactionManager; + +@Slf4j +public class MvccReadRepeatableTest extends TestCase { + + public void testReadSelf() throws RecordNotFoundException { + var storage = RecordManager.fromFile("MvccReadRepeatableTest::testReadSelf"); + var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); + // 创建事务1 + var tx1 = manager.begin(); + // 事务1的记录自身可见 + var a1 = storage.insert(tx1, new byte[]{0x23, 0x63}); + assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + // 事务2不可见a1 + var tx2 = manager.begin(); + assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + // 事务1新纪录也可见 + for (int i = 0; i < 100; i++) { + var uuid = storage.insert(tx1, new byte[]{0x23, 0x63, 0x44}); + assertTrue("uuid see a1", storage.queryOptional(tx1, uuid).isPresent()); + } + } + + public void testReadOther() throws RecordNotFoundException { + var storage = RecordManager.fromFile("MvccReadRepeatableTest::testReadOther"); + var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); + // 创建事务12 + var tx1 = manager.begin(); + var tx2 = manager.begin(); + // 事务2创建的b1,事务1不可见 + var b1 = storage.insert(tx2, new byte[]{0x1, 0x2, 0x3}); + assertTrue("tx2 see b1", storage.queryOptional(tx2, b1).isPresent()); + assertTrue("tx1 blind b1", storage.queryOptional(tx1, b1).isEmpty()); + // 事务1创建的a1,事务2不可见 + var a1 = storage.insert(tx1, new byte[]{0x6, 0x5, 0x4, 0x32}); + assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + // 事务2提交,则事务1依旧不可见b1,因为事务1不可见事务2 + tx2.commit(); + assertTrue("tx1 blind b1 after commit", storage.queryOptional(tx1, b1).isEmpty()); + // 新建事务3 + var tx3 = manager.begin(); + // 事务3可以看到b1,但是不能看到a1 + assertTrue("tx3 see b1 after commit", storage.queryOptional(tx3, b1).isPresent()); + assertTrue("tx3 blind a1 before commit", storage.queryOptional(tx3, a1).isEmpty()); + // 提交事务1,a1还是不可见,因为事务3创建时事务1还在运行 + tx1.commit(); + assertTrue("tx3 blind a1 after commit", storage.queryOptional(tx3, a1).isEmpty()); + } + + public void testDelete() throws RecordNotFoundException { + var storage = RecordManager.fromFile("MvccReadRepeatableTest::testDelete"); + var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); + // 创建事务1、记录a1a2 + var tx1 = manager.begin(); + var a1 = storage.insert(tx1, new byte[]{0x1, 0x2, 0x3}); + var a2 = storage.insert(tx1, new byte[]{0x5, 0x6, 0x7}); + assertTrue(storage.queryOptional(tx1, a1).isPresent()); + assertTrue(storage.queryOptional(tx1, a2).isPresent()); + // 自身删除 + storage.delete(tx1, a1); + assertTrue("a1 should be deleted", storage.queryOptional(tx1, a1).isEmpty()); + // 提交事务1 + tx1.commit(); + // 事务2删除,事务3仍然可见a2 + var tx2 = manager.begin(); + var tx3 = manager.begin(); + storage.delete(tx2, a2); + assertTrue("tx2 blind a2 after delete", storage.queryOptional(tx2, a2).isEmpty()); + assertTrue("tx3 see a2 before commit", storage.queryOptional(tx3, a2).isPresent()); + // 事务2提交,事务3依旧可见a2,因为事务3开始时事务2没有结束 + tx2.commit(); + assertTrue("tx3 see a2 after commit", storage.queryOptional(tx3, a2).isPresent()); + } + + public void testVersionSkip() throws RecordNotFoundException, NeedRollbackException { + var storage = RecordManager.fromFile("MvccReadRepeatableTest::testDelete"); + var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); + // 创建公共版本 + var r = storage.insert(TransactionContext.empty(), new byte[]{0x1, 0x2, 0x3, 0x4}); + // 进行版本跳跃操作 + var tx1 = manager.begin(); + var tx2 = manager.begin(); + assertTrue(storage.queryOptional(tx1, r).isPresent()); + assertTrue(storage.queryOptional(tx2, r).isPresent()); + storage.delete(tx1, r); + tx1.commit(); + try { + storage.delete(tx2, r); + fail("Should rollback tx2 at here"); + tx2.commit(); + } catch (NeedRollbackException e) { + log.info("Expected runtime exception: ", e); + } + } +} diff --git a/src/test/java/net/kaaass/rumbase/record/MvccUtilTest.java b/src/test/java/net/kaaass/rumbase/record/MvccUtilTest.java new file mode 100644 index 0000000..f239fac --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/record/MvccUtilTest.java @@ -0,0 +1,28 @@ +package net.kaaass.rumbase.record; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; + +import java.util.Random; + +@Slf4j +public class MvccUtilTest extends TestCase { + + public void testReadWriteInt() { + byte[] arr = new byte[5]; + for (int i = 0; i < Integer.MAX_VALUE - 100; i += 50) { + MvccUtil.writeInt(arr, 1, i); + assertEquals(i, MvccUtil.readInt(arr, 1)); + } + } + + public void testReadWriteLong() { + byte[] arr = new byte[13]; + var rand = new Random(); + for (int i = 0; i < 10000; i++) { + long num = rand.nextLong(); + MvccUtil.writeLong(arr, 4, num); + assertEquals(num, MvccUtil.readLong(arr, 4)); + } + } +} \ No newline at end of file From 86305e3d6101f84941b2b4728e36a9357ca97b34 Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Fri, 15 Jan 2021 18:23:49 +0800 Subject: [PATCH 10/18] =?UTF-8?q?[+]=E5=AE=8C=E6=88=90=E4=BA=8B=E5=8A=A1?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 实现事务管理器和事务上下文,完善测试类 * 完善事务实现 * 完善锁模块 * 改进测试用例 * Record测试用例加入事务相关,修正小bug Co-authored-by: Criterionist <1229089076@qq.com> --- .../kaaass/rumbase/record/IRecordStorage.java | 2 +- .../rumbase/record/MvccRecordStorage.java | 35 ++- .../exception/NeedRollbackException.java | 2 + .../exception/RecordNotFoundException.java | 2 +- .../exception/StorageCorruptedException.java | 5 +- .../transaction/TransactionContext.java | 10 +- .../transaction/TransactionContextImpl.java | 191 ++++++++++++ .../transaction/TransactionIsolation.java | 10 + .../transaction/TransactionManagerImpl.java | 224 ++++++++++++++ .../rumbase/transaction/lock/DataItemId.java | 59 ++++ .../rumbase/transaction/lock/Graph.java | 97 ++++++ .../rumbase/transaction/lock/LockMode.java | 19 ++ .../transaction/{ => lock}/LockTable.java | 10 +- .../transaction/lock/LockTableImpl.java | 280 ++++++++++++++++++ .../{ => lock}/LockTableManager.java | 2 +- .../rumbase/transaction/lock/TxItem.java | 96 ++++++ .../rumbase/transaction/lock/TxList.java | 134 +++++++++ .../transaction/mock/MockLockTable.java | 2 +- .../rumbase/record/IRecordStorageTest.java | 12 +- .../rumbase/record/MvccReadCommitTest.java | 96 +++++- .../record/MvccReadRepeatableTest.java | 123 +++++++- .../transaction/TransactionContextTest.java | 124 ++++++-- 22 files changed, 1477 insertions(+), 58 deletions(-) create mode 100644 src/main/java/net/kaaass/rumbase/transaction/TransactionContextImpl.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/TransactionManagerImpl.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/lock/DataItemId.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/lock/Graph.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/lock/LockMode.java rename src/main/java/net/kaaass/rumbase/transaction/{ => lock}/LockTable.java (55%) create mode 100644 src/main/java/net/kaaass/rumbase/transaction/lock/LockTableImpl.java rename src/main/java/net/kaaass/rumbase/transaction/{ => lock}/LockTableManager.java (94%) create mode 100644 src/main/java/net/kaaass/rumbase/transaction/lock/TxItem.java create mode 100644 src/main/java/net/kaaass/rumbase/transaction/lock/TxList.java diff --git a/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java index fb751a0..66c562d 100644 --- a/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java +++ b/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java @@ -49,7 +49,7 @@ default byte[] query(TransactionContext txContext, long recordId) throws RecordN * @param txContext 事务上下文 * @param recordId 记录ID */ - void delete(TransactionContext txContext, long recordId); + void delete(TransactionContext txContext, long recordId) throws RecordNotFoundException; /** * 获得记录存储的元信息(与单个记录无关) diff --git a/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java index a3b1a28..52807a5 100644 --- a/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java +++ b/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java @@ -12,6 +12,7 @@ import net.kaaass.rumbase.transaction.TransactionContext; import net.kaaass.rumbase.transaction.TransactionIsolation; import net.kaaass.rumbase.transaction.TransactionStatus; +import net.kaaass.rumbase.transaction.exception.DeadlockException; import java.util.Optional; @@ -35,7 +36,6 @@ public class MvccRecordStorage implements IRecordStorage { */ private byte[] metadataCache = null; - @SneakyThrows @Override public long insert(TransactionContext txContext, byte[] rawData) { var data = new byte[rawData.length + 8]; @@ -52,7 +52,11 @@ public long insert(TransactionContext txContext, byte[] rawData) { public Optional queryOptional(TransactionContext txContext, long recordId) throws RecordNotFoundException { // 串行化事务则申请读锁 if (txContext.getIsolation() == TransactionIsolation.SERIALIZABLE) { - txContext.sharedLock(recordId, this.identifiedName); + try { + txContext.sharedLock(recordId, this.identifiedName); + } catch (DeadlockException e) { + throw new NeedRollbackException(2, e); + } } // 读取数据 byte[] data; @@ -69,17 +73,25 @@ public Optional queryOptional(TransactionContext txContext, long recordI } } - @SneakyThrows @Override - public void delete(TransactionContext txContext, long recordId) { + public void delete(TransactionContext txContext, long recordId) throws RecordNotFoundException { // 读取数据 - var data = storage.queryItemByUuid(recordId); + byte[] data; + try { + data = storage.queryItemByUuid(recordId); + } catch (UUIDException e) { + throw new RecordNotFoundException(1, e); + } // 判断可见性,若不可见直接返回 if (!isVisible(txContext, data)) { - return; + throw new RecordNotFoundException(2); } // 申请互斥锁 - txContext.exclusiveLock(recordId, this.identifiedName); + try { + txContext.exclusiveLock(recordId, this.identifiedName); + } catch (DeadlockException e) { + throw new NeedRollbackException(2, e); + } var xid = txContext.getXid(); if (xid == 0) { xid = -1; @@ -87,7 +99,7 @@ public void delete(TransactionContext txContext, long recordId) { // 是否已经被删除 var xmax = readXmax(data); if (xmax == xid) { - return; + throw new RecordNotFoundException(2); } // 版本跳跃检查 if (isVersionSkip(txContext, data)) { @@ -96,7 +108,11 @@ public void delete(TransactionContext txContext, long recordId) { } // 更新记录 writeXmax(data, xid); - storage.updateItemByUuid(txContext, recordId, data); + try { + storage.updateItemByUuid(txContext, recordId, data); + } catch (UUIDException e) { + throw new RecordNotFoundException(1, e); + } } /** @@ -174,7 +190,6 @@ private boolean isVersionSkip(TransactionContext txContext, byte[] data) { && (xmax > txContext.getXid() || txContext.getSnapshot().contains(xmax)); } - @SneakyThrows @Override public byte[] getMetadata(TransactionContext txContext) { // TODO 申请全表锁,用this锁代替 diff --git a/src/main/java/net/kaaass/rumbase/record/exception/NeedRollbackException.java b/src/main/java/net/kaaass/rumbase/record/exception/NeedRollbackException.java index 85eb930..d13bc60 100644 --- a/src/main/java/net/kaaass/rumbase/record/exception/NeedRollbackException.java +++ b/src/main/java/net/kaaass/rumbase/record/exception/NeedRollbackException.java @@ -9,6 +9,7 @@ * E5003 需要自动回滚异常 *

* E5003-1 发生版本跳跃,需要自动回滚事务 + * E5003-2 发生死锁,需要回滚当前事务 * * @author kaaass */ @@ -16,6 +17,7 @@ public class NeedRollbackException extends RumbaseRuntimeException { public static final Map REASONS = new HashMap<>() {{ put(1, "发生版本跳跃,需要自动回滚事务"); + put(2, "发生死锁,需要回滚当前事务"); }}; /** diff --git a/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java b/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java index 8c27daa..fd5857b 100644 --- a/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java +++ b/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java @@ -17,7 +17,7 @@ public class RecordNotFoundException extends RumbaseException { public static final Map REASONS = new HashMap<>() {{ put(1, "物理记录不存在"); - put(2, "由于事务性,记录不可见"); + put(2, "由于事务隔离或已经被删除,记录不可见"); }}; /** diff --git a/src/main/java/net/kaaass/rumbase/record/exception/StorageCorruptedException.java b/src/main/java/net/kaaass/rumbase/record/exception/StorageCorruptedException.java index 5b1aeff..b7305f1 100644 --- a/src/main/java/net/kaaass/rumbase/record/exception/StorageCorruptedException.java +++ b/src/main/java/net/kaaass/rumbase/record/exception/StorageCorruptedException.java @@ -1,6 +1,7 @@ package net.kaaass.rumbase.record.exception; import net.kaaass.rumbase.exception.RumbaseException; +import net.kaaass.rumbase.exception.RumbaseRuntimeException; import java.util.HashMap; import java.util.Map; @@ -8,11 +9,11 @@ /** * E5002 存储数据错误异常 *

- * E5002-1 存储数据元信息不存在 + * E5002-1 存储数据元信息不存在,存储数据可能损坏 * * @author kaaass */ -public class StorageCorruptedException extends RumbaseException { +public class StorageCorruptedException extends RumbaseRuntimeException { public static final Map REASONS = new HashMap<>() {{ put(1, "存储数据元信息不存在"); diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java index f09a0dc..f0f1692 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java @@ -1,6 +1,6 @@ package net.kaaass.rumbase.transaction; -import net.kaaass.rumbase.transaction.mock.MockTransactionContext; +import net.kaaass.rumbase.transaction.exception.DeadlockException; import java.util.List; @@ -20,7 +20,7 @@ public interface TransactionContext { * @return 空事务上下文 */ static TransactionContext empty() { - return new MockTransactionContext(); + return new TransactionContextImpl(); } /** @@ -78,14 +78,16 @@ static TransactionContext empty() { * * @param uuid 记录id * @param tableName 表字段 + * @throws DeadlockException 发生死锁异常 */ - void sharedLock(long uuid, String tableName); + void sharedLock(long uuid, String tableName) throws DeadlockException; /** * 对记录加排他锁 * * @param uuid 记录id * @param tableName 表字段 + * @throws DeadlockException 发生死锁异常 */ - void exclusiveLock(long uuid, String tableName); + void exclusiveLock(long uuid, String tableName) throws DeadlockException; } diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionContextImpl.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionContextImpl.java new file mode 100644 index 0000000..bb803bd --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionContextImpl.java @@ -0,0 +1,191 @@ +package net.kaaass.rumbase.transaction; + +import lombok.Getter; +import lombok.Setter; +import net.kaaass.rumbase.transaction.exception.DeadlockException; +import net.kaaass.rumbase.transaction.lock.LockTable; +import net.kaaass.rumbase.transaction.lock.LockTableImpl; + +import java.util.ArrayList; +import java.util.List; + +/** + * 事务上下文的实现 + * + * @author criki + */ +public class TransactionContextImpl implements TransactionContext { + + /** + * 事务Id + */ + @Getter + private final int xid; + /** + * 事务隔离度 + */ + @Getter + private final TransactionIsolation isolation; + /** + * 存储创建它的管理器 + */ + @Getter + private final TransactionManager manager; + /** + * 事务快照 + */ + @Getter + private final List snapshot; + /** + * 事务状态 + */ + @Getter + @Setter + private TransactionStatus status; + + /** + * 事务上下文 + */ + public TransactionContextImpl() { + this.xid = 0; + this.status = TransactionStatus.COMMITTED; + this.isolation = TransactionIsolation.READ_UNCOMMITTED; + this.manager = null; + this.snapshot = new ArrayList<>(); + } + + /** + * 事务上下文 + * + * @param xid 事务id + * @param isolation 事务隔离度 + * @param manager 创建事务的管理器 + */ + public TransactionContextImpl(int xid, TransactionIsolation isolation, TransactionManager manager) { + this.xid = xid; + this.status = TransactionStatus.PREPARING; + this.isolation = isolation; + this.manager = manager; + this.snapshot = new ArrayList<>(); + } + + /** + * 事务上下文 + * + * @param xid 事务id + * @param isolation 事务隔离度 + * @param manager 创建事务的管理器 + * @param status 事务状态 + */ + public TransactionContextImpl(int xid, TransactionIsolation isolation, TransactionManager manager, TransactionStatus status) { + this.xid = xid; + this.isolation = isolation; + this.manager = manager; + this.status = status; + this.snapshot = new ArrayList<>(); + } + + /** + * 事务上下文 + * + * @param xid 事务id + * @param isolation 事务隔离度 + * @param manager 创建事务的管理器 + * @param snapshot 事务状态 + */ + public TransactionContextImpl(int xid, TransactionIsolation isolation, TransactionManager manager, List snapshot) { + this.xid = xid; + this.isolation = isolation; + this.manager = manager; + this.status = TransactionStatus.PREPARING; + this.snapshot = snapshot; + } + + /** + * 事务上下文 + * + * @param xid 事务id + * @param isolation 事务隔离度 + * @param manager 创建事务的管理器 + * @param snapshot 事务快照 + * @param status 事务状态 + */ + public TransactionContextImpl(int xid, TransactionIsolation isolation, TransactionManager manager, List snapshot, TransactionStatus status) { + this.xid = xid; + this.isolation = isolation; + this.manager = manager; + this.snapshot = snapshot; + this.status = status; + } + + /** + * 开始事务 + */ + @Override + public void start() { + this.status = TransactionStatus.ACTIVE; + if (manager != null) { + manager.changeTransactionStatus(xid, TransactionStatus.ACTIVE); + } + + } + + /** + * 提交事务 + */ + @Override + public void commit() { + // 修改状态 + this.status = TransactionStatus.COMMITTED; + if (manager != null) { + manager.changeTransactionStatus(xid, TransactionStatus.COMMITTED); + } + + // 释放锁 + LockTable lockTable = LockTableImpl.getInstance(); + lockTable.release(xid); + } + + /** + * 中止事务 + */ + @Override + public void rollback() { + // 修改状态 + this.status = TransactionStatus.ABORTED; + if (manager != null) { + manager.changeTransactionStatus(xid, TransactionStatus.ABORTED); + } + + // 释放锁 + LockTable lockTable = LockTableImpl.getInstance(); + lockTable.release(xid); + } + + /** + * 加共享锁 + * + * @param uuid 记录id + * @param tableName 表字段 + */ + @Override + public void sharedLock(long uuid, String tableName) throws DeadlockException { + //TODO 加锁 + LockTable lockTable = LockTableImpl.getInstance(); + lockTable.addSharedLock(xid, uuid, tableName); + } + + /** + * 加排他锁 + * + * @param uuid 记录id + * @param tableName 表字段 + */ + @Override + public void exclusiveLock(long uuid, String tableName) throws DeadlockException { + //TODO 加锁 + LockTable lockTable = LockTableImpl.getInstance(); + lockTable.addExclusiveLock(xid, uuid, tableName); + } + +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionIsolation.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionIsolation.java index 1990495..d4d077f 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/TransactionIsolation.java +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionIsolation.java @@ -39,4 +39,14 @@ public enum TransactionIsolation { TransactionIsolation(int isolationId) { this.isolationId = isolationId; } + + public static TransactionIsolation getStatusById(byte id) { + for (TransactionIsolation value : values()) { + if (id == value.isolationId) { + return value; + } + } + + return null; + } } diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionManagerImpl.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionManagerImpl.java new file mode 100644 index 0000000..819e141 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionManagerImpl.java @@ -0,0 +1,224 @@ +package net.kaaass.rumbase.transaction; + +import com.igormaznitsa.jbbp.io.JBBPBitInputStream; +import com.igormaznitsa.jbbp.io.JBBPByteOrder; +import com.igormaznitsa.jbbp.io.JBBPOut; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.page.Page; +import net.kaaass.rumbase.page.PageManager; +import net.kaaass.rumbase.page.PageStorage; +import net.kaaass.rumbase.page.exception.FileException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 事务管理器的实现 + * + * @author criki + */ +@Slf4j +public class TransactionManagerImpl implements TransactionManager { + + /** + * 事务状态持久化文件名 + */ + private static final String LOG_FILE_NAME = "xid.log"; + /** + * 每页最大事务状态数 + */ + private static final int TX_NUM_PER_PAGE = 2048; + /** + * 事务ID计数器 + */ + @Getter + private final AtomicInteger SIZE; + /** + * 事务数量日志写入锁 + */ + private final Lock sizeWriteLock = new ReentrantLock(); + /** + * 事务状态日志 + */ + private final PageStorage storage; + + /** + * 事务缓存 + *

+ * TODO 实现事务缓存调度 + */ + private final Map txCache = new HashMap<>(); + + /** + * Mock事务管理器 + */ + public TransactionManagerImpl() throws FileException, IOException { + if (new File(LOG_FILE_NAME).exists()) { + // 初始化storage + this.storage = PageManager.fromFile(LOG_FILE_NAME); + // 从日志文件里获取事务数量 + JBBPBitInputStream stream = new JBBPBitInputStream(storage.get(0).getData()); + int size = stream.readInt(JBBPByteOrder.BIG_ENDIAN); + log.info("Initial size : {}", size); + // 初始化SIZE + this.SIZE = new AtomicInteger(size); + } else { + // 初始化storage + this.storage = PageManager.fromFile(LOG_FILE_NAME); + // 初始化SIZE + this.SIZE = new AtomicInteger(0); + } + + } + + /** + * 创建新事务 + * + * @param isolation 事务隔离度 + * @return 事务对象 + */ + @Override + public TransactionContext createTransactionContext(TransactionIsolation isolation) { + // 获取最新事务id + int xid = SIZE.incrementAndGet(); + + // 生成快照 + List snapshot = new ArrayList<>(); + + synchronized (txCache) { + for (TransactionContext context : txCache.values()) { + if (context.getStatus() == TransactionStatus.ACTIVE) { + snapshot.add(context.getXid()); + } + } + } + + // 创建对象 + TransactionContext context = new TransactionContextImpl(xid, isolation, this, snapshot); + // 加入缓存 + txCache.put(xid, context); + + // 进行持久化 + Page page = storage.get(0); + page.pin(); + sizeWriteLock.lock(); + try { + // 转换数据 + byte[] bytes = JBBPOut.BeginBin() + .Int(SIZE.get()) + .End().toByteArray(); + // 写入数据 + page.writeData(bytes); + // 刷新数据 + page.flush(); + + // 写入日志中的事务隔离度 + writeTransactionIsolation(xid, isolation); + // 更新日志中的事务状态 + changeTransactionStatus(xid, TransactionStatus.PREPARING); + } catch (Exception e) { + e.printStackTrace(); + } finally { + sizeWriteLock.unlock(); + page.unpin(); + } + + + return context; + } + + /** + * 改变日志中的事务隔离度 + * + * @param xid 事务id + * @param isolation 事务隔离度 + */ + private void writeTransactionIsolation(int xid, TransactionIsolation isolation) { + int pageId = xid / TX_NUM_PER_PAGE + 1; + int offset = xid % TX_NUM_PER_PAGE * 2 + 1; + + log.info("Xid : {}", xid); + log.info("Page id : {}", pageId); + log.info("offset : {}", offset); + Page page = storage.get(pageId); + page.pin(); + try { + byte[] data = new byte[1]; + data[0] = (byte) isolation.getIsolationId(); + page.patchData(offset, data); + page.flush(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + page.unpin(); + } + } + + /** + * 改变日志中的事务状态 + * + * @param xid 事务id + * @param status 新事务状态 + */ + @Override + public void changeTransactionStatus(int xid, TransactionStatus status) { + int pageId = xid / TX_NUM_PER_PAGE + 1; + int offset = xid % TX_NUM_PER_PAGE * 2; + + Page page = storage.get(pageId); + page.pin(); + try { + byte[] data = new byte[1]; + data[0] = status.getStatusId(); + page.patchData(offset, data); + page.flush(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + page.unpin(); + } + + var context = txCache.get(xid); + if (context instanceof TransactionContextImpl) { + ((TransactionContextImpl) context).setStatus(status); + } + } + + /** + * 根据事务id获取事务上下文 + * + * @param xid 事务id + * @return 事务id为xid的事务上下文 + */ + @Override + public TransactionContext getContext(int xid) { + if (txCache.containsKey(xid)) { + return txCache.get(xid); + } + + int pageId = xid / TX_NUM_PER_PAGE + 1; + int statusOffset = xid % TX_NUM_PER_PAGE * 2; + int isolationOffset = xid % TX_NUM_PER_PAGE * 2 + 1; + + Page page = storage.get(pageId); + page.pin(); + byte[] bytes = page.getDataBytes(); + byte statusId = bytes[statusOffset]; + byte isolationId = bytes[isolationOffset]; + + TransactionStatus status = TransactionStatus.getStatusById(statusId); + TransactionIsolation isolation = TransactionIsolation.getStatusById(isolationId); + + page.unpin(); + + return new TransactionContextImpl(xid, isolation, this, status); + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/lock/DataItemId.java b/src/main/java/net/kaaass/rumbase/transaction/lock/DataItemId.java new file mode 100644 index 0000000..966590f --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/DataItemId.java @@ -0,0 +1,59 @@ +package net.kaaass.rumbase.transaction.lock; + +import lombok.Getter; + +/** + * 数据项标识符 + *

+ * 用于唯一指定一个数据项 + * + * @author criki + */ +public class DataItemId { + /** + * 数据项所在表 + */ + @Getter + private final String tableName; + + /** + * 数据项表内id + */ + @Getter + private final long uuid; + + public DataItemId(String tableName, long uuid) { + this.tableName = tableName; + this.uuid = uuid; + } + + @Override + public String toString() { + return "(" + tableName + ", " + uuid + ")"; + } + + @Override + public int hashCode() { + return tableName.hashCode() * 37 + Long.valueOf(uuid).hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + + if (this == obj) { + return true; + } + + if (!(obj instanceof DataItemId)) { + return false; + } + + DataItemId id = (DataItemId) obj; + + return this.tableName.equals(id.tableName) + && this.uuid == id.uuid; + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/lock/Graph.java b/src/main/java/net/kaaass/rumbase/transaction/lock/Graph.java new file mode 100644 index 0000000..1301612 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/Graph.java @@ -0,0 +1,97 @@ +package net.kaaass.rumbase.transaction.lock; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 有向图数据结构 + *

+ * 用于死锁检测 + * + * @author criki + */ +public class Graph { + + /** + * 深搜访问数组 + */ + private final Map visited = new HashMap<>(); + /** + * 等待图邻接表 + */ + public Map> waitGraph = new HashMap<>(); + + /** + * 添加边 + * + * @param from 源点 + * @param to 终点 + */ + public void addEdge(int from, int to) { + if (waitGraph.containsKey(from)) { + waitGraph.get(from).add(to); + } else { + List edges = new ArrayList<>(); + edges.add(to); + waitGraph.put(from, edges); + } + + if (!waitGraph.containsKey(to)) { + List edges = new ArrayList<>(); + waitGraph.put(to, edges); + } + } + + /** + * 判断是否存在环路 + * + * @return + * + * + *
true存在环路
false不存在环路
+ */ + public boolean hasLoop() { + // 初始化访问数组 + visited.clear(); + for (Integer vertex : waitGraph.keySet()) { + visited.put(vertex, 0); + } + + for (Map.Entry v : visited.entrySet()) { + if (v.getValue() == 0) { + if (dfs(v.getKey())) { + // 有环 + return true; + } + } + } + + return false; + } + + /** + * 从u点开始深度优先搜索 + * + * @param u 源点 + * @return 是否存在环路 + */ + private boolean dfs(int u) { + // 标识正在访问 + visited.put(u, -1); + + List edges = waitGraph.get(u); + for (Integer v : edges) { + int visitedV = visited.get(v); + if (visitedV < 0) { + return true; + } else if (visitedV == 0 && dfs(v)) { + return true; + } + } + + visited.put(u, 1); + return false; + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/lock/LockMode.java b/src/main/java/net/kaaass/rumbase/transaction/lock/LockMode.java new file mode 100644 index 0000000..6cb9609 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/LockMode.java @@ -0,0 +1,19 @@ +package net.kaaass.rumbase.transaction.lock; + +/** + * 锁模式 + *

+ * 用于锁表中标识锁类型 + * + * @author criki + */ +public enum LockMode { + /** + * 共享锁 + */ + SHARED, + /** + * 排他锁 + */ + EXCLUSIVE +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/LockTable.java b/src/main/java/net/kaaass/rumbase/transaction/lock/LockTable.java similarity index 55% rename from src/main/java/net/kaaass/rumbase/transaction/LockTable.java rename to src/main/java/net/kaaass/rumbase/transaction/lock/LockTable.java index 5527887..23b46ea 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/LockTable.java +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/LockTable.java @@ -1,4 +1,6 @@ -package net.kaaass.rumbase.transaction; +package net.kaaass.rumbase.transaction.lock; + +import net.kaaass.rumbase.transaction.exception.DeadlockException; /** * 锁表 @@ -14,8 +16,9 @@ public interface LockTable { * @param xid 事务id * @param uuid 记录id * @param tableName 表名 + * @throws DeadlockException 发生死锁异常 */ - void addSharedLock(int xid, long uuid, String tableName); + void addSharedLock(int xid, long uuid, String tableName) throws DeadlockException; /** * 添加排他锁 @@ -23,8 +26,9 @@ public interface LockTable { * @param xid 事务id * @param uuid 记录id * @param tableName 表名 + * @throws DeadlockException 发生死锁异常 */ - void addExclusiveLock(int xid, long uuid, String tableName); + void addExclusiveLock(int xid, long uuid, String tableName) throws DeadlockException; /** * 释放事务的锁 diff --git a/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableImpl.java b/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableImpl.java new file mode 100644 index 0000000..7b617cf --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableImpl.java @@ -0,0 +1,280 @@ +package net.kaaass.rumbase.transaction.lock; + +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.transaction.exception.DeadlockException; + +import java.util.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +/** + * 锁表的实现 + * + * @author criki + */ +@Slf4j +public class LockTableImpl implements LockTable { + + /** + * 单例 + */ + private static LockTable instance; + /** + * 锁表 + */ + private final Map lockTable = new HashMap<>(); + /** + * 锁表互斥锁 + */ + private final Lock lock = new ReentrantLock(); + + private LockTableImpl() { + + } + + /** + * 获取实例方法 + * + * @return 单例 + */ + public static LockTable getInstance() { + if (instance == null) { + instance = new LockTableImpl(); + } + return instance; + } + + /** + * 添加锁的模板函数 + * + * @param xid 事务id + * @param id 数据项id + * @param mode 锁类型 + */ + private void addLockTemplate(int xid, DataItemId id, LockMode mode) throws DeadlockException { + TxList list; + + // 获取id的等待队列 + lock.lock(); + try { + if (lockTable.containsKey(id)) { + list = lockTable.get(id); + } else { + list = new TxList(); + lockTable.put(id, list); + } + } finally { + lock.unlock(); + } + + // 判断是否需要手动释放互斥锁 + boolean canUnlock = true; + list.mutexLock.lock(); + try { + // 判断是否能加锁 + boolean canGrant = list.canGrant(mode); + log.info("{} can grant {} lock : {}", xid, mode, canGrant); + // 虚加锁 + list.weakInsert(xid, id, mode, canGrant); + // 检测死锁 + if (deadlockCheck()) { + log.info("deadlock"); + list.pop(); + throw new DeadlockException(1); + } + + // 可以加锁 + // 对于互斥锁,如果发生等待,在等待处即已释放,此处无需释放 + canUnlock = canGrant; + + // 移除虚锁 + list.pop(); + // 正式加锁 + list.insert(xid, id, mode, canGrant); + } finally { + if (canUnlock) { + list.mutexLock.unlock(); + } + } + + } + + /** + * 添加共享锁 + * + * @param xid 事务id + * @param uuid 记录id + * @param tableName 表名 + */ + @Override + public void addSharedLock(int xid, long uuid, String tableName) throws DeadlockException { + log.info("{} add shared lock on ({}, {})", xid, tableName, uuid); + addLockTemplate(xid, new DataItemId(tableName, uuid), LockMode.SHARED); + } + + /** + * 添加排他锁 + * + * @param xid 事务id + * @param uuid 记录id + * @param tableName 表名 + */ + @Override + public void addExclusiveLock(int xid, long uuid, String tableName) throws DeadlockException { + log.info("{} add exclusive lock on ({}, {})", xid, tableName, uuid); + addLockTemplate(xid, new DataItemId(tableName, uuid), LockMode.EXCLUSIVE); + } + + /** + * 释放事务的锁 + * + * @param xid 事务id + */ + @Override + public void release(int xid) { + Set dataItemSet = new HashSet<>(); + List sharedLocks = TxList.sharedLocks.get(xid); + List exclusiveLocks = TxList.exclusiveLocks.get(xid); + log.info("{}'s sharedLocks: {}", xid, sharedLocks); + log.info("{}'s exclusiveLocks: {}", xid, exclusiveLocks); + if (sharedLocks != null) { + dataItemSet.addAll(sharedLocks); + } + if (exclusiveLocks != null) { + + dataItemSet.addAll(exclusiveLocks); + } + + for (DataItemId id : dataItemSet) { + release(xid, id); + } + } + + /** + * 释放事务xid持有的数据项id的锁 + * + * @param xid 事务id + * @param id 数据项id + */ + private void release(int xid, DataItemId id) { + TxList waitList; + + // 获取id的等待队列 + lock.lock(); + try { + waitList = lockTable.get(id); + } finally { + lock.unlock(); + } + + // 从等待队列中移除xid + waitList.mutexLock.lock(); + try { + // 查找等待队列中事务id为xid的项 + List lockList = waitList.locks.stream().filter(lock -> lock.xid == xid).collect(Collectors.toList()); + + List txSharedLocks = TxList.sharedLocks.get(xid); + List txExclusiveLocks = TxList.exclusiveLocks.get(xid); + for (TxItem lock : lockList) { + // 从事务持锁列表中移除该数据项 + if (lock.mode.equals(LockMode.SHARED)) { + txSharedLocks.remove(id); + } else { + txExclusiveLocks.remove(id); + } + + // 中止锁申请 + lock.abort(); + + // 从数据项等待队列中移除该事务锁 + waitList.locks.remove(lock); + } + + // 数据项等待队列空,从锁表中移除 + if (waitList.locks.isEmpty()) { + lockTable.remove(id); + return; + } + + } finally { + waitList.mutexLock.unlock(); + } + + boolean firstTx = true; + // 等待队列非空,从等待队列中唤醒下一个事务 + for (TxItem tx : waitList.locks) { + if (tx.granted) { + // 有事务持有该数据项的锁,暂不分配新锁 + break; + } + + // 给第一个事务加锁 + if (firstTx) { + tx.grant(); + firstTx = false; + } + // 给非第一个事务且申请共享锁的事务加锁 + else if (tx.mode.equals(LockMode.SHARED)) { + tx.grant(); + continue; + } + + // 如果该事务是排他锁,则不给之后的事务加锁 + if (tx.mode.equals(LockMode.EXCLUSIVE)) { + break; + } + } + } + + /** + * 检查是否存在死锁 + * + * @return + * + * + *
true 存在死锁
false 不存在死锁
+ */ + private boolean deadlockCheck() { + Graph graph = new Graph(); + + // 建图 + + // 遍历每一个等待队列 + for (TxList list : lockTable.values()) { + List waitingTxs = new ArrayList<>(list.locks); + // 对等待队列中建立等待关系 + for (int i = 0; i < waitingTxs.size() - 1; i++) { + TxItem frontItem = waitingTxs.get(i); + // 共享锁 + if (frontItem.mode.equals(LockMode.SHARED)) { + // 找到后面第一个排他锁 + for (int j = i + 1; j < waitingTxs.size(); j++) { + TxItem backItem = waitingTxs.get(j); + // 邻近的共享锁不构成等待关系 + if (backItem.mode.equals(LockMode.SHARED)) { + continue; + } + + log.info("[CREATING GRAPH] add edge : {} -> {}", backItem.xid, frontItem.xid); + + // backItem等待frontItem + graph.addEdge(backItem.xid, frontItem.xid); + } + } + // 排他锁 + else { + // 邻近的后面的锁等待该锁 + TxItem backItem = waitingTxs.get(i + 1); + // backItem等待frontItem + log.info("[CREATING GRAPH] add edge : {} -> {}", backItem.xid, frontItem.xid); + graph.addEdge(backItem.xid, frontItem.xid); + } + } + } + + log.info("create graph successful!"); + // 图成环,则有死锁 + return graph.hasLoop(); + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/LockTableManager.java b/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableManager.java similarity index 94% rename from src/main/java/net/kaaass/rumbase/transaction/LockTableManager.java rename to src/main/java/net/kaaass/rumbase/transaction/lock/LockTableManager.java index a69daad..77bd2b2 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/LockTableManager.java +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableManager.java @@ -1,4 +1,4 @@ -package net.kaaass.rumbase.transaction; +package net.kaaass.rumbase.transaction.lock; import net.kaaass.rumbase.transaction.mock.MockLockTable; diff --git a/src/main/java/net/kaaass/rumbase/transaction/lock/TxItem.java b/src/main/java/net/kaaass/rumbase/transaction/lock/TxItem.java new file mode 100644 index 0000000..9bafce6 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/TxItem.java @@ -0,0 +1,96 @@ +package net.kaaass.rumbase.transaction.lock; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 事务项 + *

+ * 锁表中记录事务加锁信息的单元 + * + * @author criki + */ +public class TxItem { + /** + * 判断是否被分配锁 + */ + public boolean granted; + + /** + * 事务id + */ + public int xid; + + /** + * 加锁类型 + */ + public LockMode mode; + + /** + * 事务项的互斥锁 + */ + public Lock lock = new ReentrantLock(); + + /** + * 分配锁条件量 + */ + public Condition grantLock = lock.newCondition(); + + /** + * 事务项 + * + * @param granted 是否已分配锁 + * @param xid 事务id + * @param mode 锁类型 + */ + public TxItem(boolean granted, int xid, LockMode mode) { + this.granted = granted; + this.xid = xid; + this.mode = mode; + } + + /** + * 请求分配锁 + */ + public void waitForGrant() { + lock.lock(); + try { + // 等待分配锁 + while (!this.granted) { + grantLock.await(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + } + + /** + * 分配锁 + */ + public void grant() { + lock.lock(); + try { + // 分配到锁 + granted = true; + // 唤醒线程 + grantLock.signal(); + } finally { + lock.unlock(); + } + } + + /** + * 中止请求 + */ + public void abort() { + lock.lock(); + try { + grantLock.signal(); + } finally { + lock.unlock(); + } + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/lock/TxList.java b/src/main/java/net/kaaass/rumbase/transaction/lock/TxList.java new file mode 100644 index 0000000..2ad3386 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/TxList.java @@ -0,0 +1,134 @@ +package net.kaaass.rumbase.transaction.lock; + +import lombok.extern.slf4j.Slf4j; + +import java.util.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 事务项列表 + *

+ * 锁表中一个数据项的事务等待列表 + * + * @author criki + */ +@Slf4j +public class TxList { + + /** + * 记录事务持有的共享锁 + */ + public static Map> sharedLocks = new HashMap<>(); + + /** + * 记录事务持有的排他锁 + */ + public static Map> exclusiveLocks = new HashMap<>(); + + /** + * 一个数据项上的事务请求列表 + */ + public Deque locks = new ArrayDeque<>(); + + /** + * 事务列表的互斥锁 + */ + public Lock mutexLock = new ReentrantLock(); + + /** + * 判断当前等待列表是否支持分配mode类型的锁 + * + * @param mode 待分配锁类型 + * @return 是否可以分配 + */ + public boolean canGrant(LockMode mode) { + // 等待队列为空,可以分配任何锁 + if (locks.isEmpty()) { + return true; + } + + // 非空队列中,不可申请排他锁 + if (mode.equals(LockMode.EXCLUSIVE)) { + return false; + } + + // 当前锁类型为共享锁,且等待队列非空 + + // 获取队尾事务项 + TxItem item = locks.getLast(); + + // 当且仅当队尾事务项锁类型是共享锁 + // 且已得到锁时,才可申请共享锁 + return item.mode.equals(LockMode.SHARED) && item.granted; + } + + /** + * 向等待列表插入一个事务项 + * + * @param xid 事务id + * @param id 数据项id + * @param mode 申请锁类型 + * @param granted 是否已分配锁 + */ + public void insert(int xid, DataItemId id, LockMode mode, boolean granted) { + + log.info("{} getting {} lock on {}, it's {}", xid, mode, id, granted); + + // 创建事务项 + TxItem item = new TxItem(granted, xid, mode); + // 加入等待队列 + locks.add(item); + + // 未分配锁时,令其等待 + if (!granted) { + // 先将互斥锁解开,放行后面对等待队列的操作 + mutexLock.unlock(); + // 等待 + item.waitForGrant(); + } + + + // 记录事务获得到的数据项锁 + List lockList; + if (mode.equals(LockMode.SHARED)) { + if (sharedLocks.containsKey(xid)) { + lockList = sharedLocks.get(xid); + } else { + lockList = new ArrayList<>(); + sharedLocks.put(xid, lockList); + } + } else { + if (exclusiveLocks.containsKey(xid)) { + lockList = exclusiveLocks.get(xid); + } else { + lockList = new ArrayList<>(); + exclusiveLocks.put(xid, lockList); + } + } + + lockList.add(id); + } + + /** + * 弹出最后一个事务项 + */ + public void pop() { + locks.pollLast(); + } + + /** + * 向等待列表插入一个事务项,用于判断死锁 + * + * @param xid 事务id + * @param id 数据项id + * @param mode 申请锁类型 + * @param granted 是否已分配锁 + */ + public void weakInsert(int xid, DataItemId id, LockMode mode, boolean granted) { + // 创建事务项 + TxItem item = new TxItem(granted, xid, mode); + // 加入等待队列 + locks.add(item); + } +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/mock/MockLockTable.java b/src/main/java/net/kaaass/rumbase/transaction/mock/MockLockTable.java index 84ee697..af01bd6 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/mock/MockLockTable.java +++ b/src/main/java/net/kaaass/rumbase/transaction/mock/MockLockTable.java @@ -1,6 +1,6 @@ package net.kaaass.rumbase.transaction.mock; -import net.kaaass.rumbase.transaction.LockTable; +import net.kaaass.rumbase.transaction.lock.LockTable; /** * Mock锁表 diff --git a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java index f7d9ed5..297a118 100644 --- a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java @@ -12,14 +12,16 @@ /** * 测试记录存储接口 * - * @see net.kaaass.rumbase.record.IRecordStorage * @author kaaass + * @see net.kaaass.rumbase.record.IRecordStorage */ @Slf4j public class IRecordStorageTest extends TestCase { + public final static String PATH = "build/"; + public void testQuery() { - var storage = RecordManager.fromFile("test_query"); + var storage = RecordManager.fromFile(PATH + "test_query"); var context = TransactionContext.empty(); try { @@ -31,7 +33,7 @@ public void testQuery() { } public void testInsert() throws RecordNotFoundException { - var storage = RecordManager.fromFile("test_insert"); + var storage = RecordManager.fromFile(PATH + "test_insert"); var context = TransactionContext.empty(); var id = storage.insert(context, new byte[]{0x1, 0x2, 0x1f}); @@ -42,7 +44,7 @@ public void testInsert() throws RecordNotFoundException { } public void testDelete() throws RecordNotFoundException { - var storage = RecordManager.fromFile("test_delete"); + var storage = RecordManager.fromFile(PATH + "test_delete"); var context = TransactionContext.empty(); storage.insert(context, new byte[]{0x1, 0x2}); @@ -58,7 +60,7 @@ public void testDelete() throws RecordNotFoundException { } public void testMetadata() { - var storage = RecordManager.fromFile("test_metadata"); + var storage = RecordManager.fromFile(PATH + "test_metadata"); var context = TransactionContext.empty(); var result = storage.getMetadata(context); diff --git a/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java b/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java index 835bfa6..fac8a73 100644 --- a/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java +++ b/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java @@ -2,14 +2,21 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.record.exception.RecordNotFoundException; import net.kaaass.rumbase.transaction.TransactionIsolation; +import net.kaaass.rumbase.transaction.TransactionManager; +import net.kaaass.rumbase.transaction.TransactionManagerImpl; + +import java.io.IOException; @Slf4j public class MvccReadCommitTest extends TestCase { + public final static String PATH = "build/"; + public void testReadSelf() throws RecordNotFoundException { - var storage = RecordManager.fromFile("MvccReadCommitTest::testReadSelf"); + var storage = RecordManager.fromFile(PATH + "testReadSelf"); var manager = new FakeTxManager(TransactionIsolation.READ_COMMITTED); // 创建事务1 var tx1 = manager.begin(); @@ -27,7 +34,7 @@ public void testReadSelf() throws RecordNotFoundException { } public void testReadOther() throws RecordNotFoundException { - var storage = RecordManager.fromFile("MvccReadCommitTest::testReadOther"); + var storage = RecordManager.fromFile(PATH + "testReadOther"); var manager = new FakeTxManager(TransactionIsolation.READ_COMMITTED); // 创建事务12 var tx1 = manager.begin(); @@ -54,7 +61,7 @@ public void testReadOther() throws RecordNotFoundException { } public void testDelete() throws RecordNotFoundException { - var storage = RecordManager.fromFile("MvccReadCommitTest::testDelete"); + var storage = RecordManager.fromFile(PATH + "testDelete"); var manager = new FakeTxManager(TransactionIsolation.READ_COMMITTED); // 创建事务1、记录a1a2 var tx1 = manager.begin(); @@ -77,4 +84,87 @@ public void testDelete() throws RecordNotFoundException { tx2.commit(); assertTrue("tx3 blind a2 after commit", storage.queryOptional(tx3, a2).isEmpty()); } + + public void testReadSelfReal() throws RecordNotFoundException, IOException, FileException { + var storage = RecordManager.fromFile(PATH + "testReadSelfReal"); + var manager = new TransactionManagerImpl(); + // 创建事务1 + var tx1 = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); + tx1.start(); + // 事务1的记录自身可见 + var a1 = storage.insert(tx1, new byte[]{0x23, 0x63}); + assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + // 事务2不可见a1 + var tx2 = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); + tx2.start(); + assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + // 事务1新纪录也可见 + for (int i = 0; i < 100; i++) { + var uuid = storage.insert(tx1, new byte[]{0x23, 0x63, 0x44}); + assertTrue("uuid see a1", storage.queryOptional(tx1, uuid).isPresent()); + } + // + tx1.commit(); + tx2.commit(); + } + + public void testReadOtherReal() throws RecordNotFoundException, IOException, FileException { + var storage = RecordManager.fromFile(PATH + "testReadOtherReal"); + var manager = new TransactionManagerImpl(); + // 创建事务12 + var tx1 = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); + tx1.start(); + var tx2 = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); + tx2.start(); + // 事务2创建的b1,事务1不可见 + var b1 = storage.insert(tx2, new byte[]{0x1, 0x2, 0x3}); + assertTrue("tx2 see b1", storage.queryOptional(tx2, b1).isPresent()); + assertTrue("tx1 blind b1", storage.queryOptional(tx1, b1).isEmpty()); + // 事务1创建的a1,事务2不可见 + var a1 = storage.insert(tx1, new byte[]{0x6, 0x5, 0x4, 0x32}); + assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + // 事务2提交,则事务1可见b1 + tx2.commit(); + assertTrue("tx1 see b1 after commit", storage.queryOptional(tx1, b1).isPresent()); + // 新建事务3 + var tx3 = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); + tx3.start(); + // 事务3可以看到b1,但是不能看到a1 + assertTrue("tx3 see b1 after commit", storage.queryOptional(tx3, b1).isPresent()); + assertTrue("tx3 blind a1 before commit", storage.queryOptional(tx3, a1).isEmpty()); + // 提交事务1,a1可见 + tx1.commit(); + assertTrue("tx3 see a1 after commit", storage.queryOptional(tx3, a1).isPresent()); + tx3.commit(); + } + + public void testDeleteReal() throws RecordNotFoundException, IOException, FileException { + var storage = RecordManager.fromFile(PATH + "testDeleteReal"); + var manager = new TransactionManagerImpl(); + // 创建事务1、记录a1a2 + var tx1 = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); + tx1.start(); + var a1 = storage.insert(tx1, new byte[]{0x1, 0x2, 0x3}); + var a2 = storage.insert(tx1, new byte[]{0x5, 0x6, 0x7}); + assertTrue(storage.queryOptional(tx1, a1).isPresent()); + assertTrue(storage.queryOptional(tx1, a2).isPresent()); + // 自身删除 + storage.delete(tx1, a1); + assertTrue("a1 should be deleted", storage.queryOptional(tx1, a1).isEmpty()); + // 提交事务1 + tx1.commit(); + // 事务2删除,事务3仍然可见a2 + var tx2 = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); + tx2.start(); + var tx3 = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); + tx3.start(); + storage.delete(tx2, a2); + assertTrue("tx2 blind a2 after delete", storage.queryOptional(tx2, a2).isEmpty()); + assertTrue("tx3 see a2 before commit", storage.queryOptional(tx3, a2).isPresent()); + // 事务2提交,事务3不可见a2 + tx2.commit(); + assertTrue("tx3 blind a2 after commit", storage.queryOptional(tx3, a2).isEmpty()); + tx3.commit(); + } } diff --git a/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java b/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java index 755d954..ac0fce2 100644 --- a/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java +++ b/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java @@ -2,17 +2,23 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.record.exception.NeedRollbackException; import net.kaaass.rumbase.record.exception.RecordNotFoundException; import net.kaaass.rumbase.transaction.TransactionContext; import net.kaaass.rumbase.transaction.TransactionIsolation; import net.kaaass.rumbase.transaction.TransactionManager; +import net.kaaass.rumbase.transaction.TransactionManagerImpl; + +import java.io.IOException; @Slf4j public class MvccReadRepeatableTest extends TestCase { + public final static String PATH = "build/"; + public void testReadSelf() throws RecordNotFoundException { - var storage = RecordManager.fromFile("MvccReadRepeatableTest::testReadSelf"); + var storage = RecordManager.fromFile(PATH + "testReadSelf"); var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); // 创建事务1 var tx1 = manager.begin(); @@ -30,7 +36,7 @@ public void testReadSelf() throws RecordNotFoundException { } public void testReadOther() throws RecordNotFoundException { - var storage = RecordManager.fromFile("MvccReadRepeatableTest::testReadOther"); + var storage = RecordManager.fromFile(PATH + "testReadOther"); var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); // 创建事务12 var tx1 = manager.begin(); @@ -57,7 +63,7 @@ public void testReadOther() throws RecordNotFoundException { } public void testDelete() throws RecordNotFoundException { - var storage = RecordManager.fromFile("MvccReadRepeatableTest::testDelete"); + var storage = RecordManager.fromFile(PATH + "testDelete"); var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); // 创建事务1、记录a1a2 var tx1 = manager.begin(); @@ -82,7 +88,7 @@ public void testDelete() throws RecordNotFoundException { } public void testVersionSkip() throws RecordNotFoundException, NeedRollbackException { - var storage = RecordManager.fromFile("MvccReadRepeatableTest::testDelete"); + var storage = RecordManager.fromFile(PATH + "testDelete"); var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); // 创建公共版本 var r = storage.insert(TransactionContext.empty(), new byte[]{0x1, 0x2, 0x3, 0x4}); @@ -101,4 +107,113 @@ public void testVersionSkip() throws RecordNotFoundException, NeedRollbackExcept log.info("Expected runtime exception: ", e); } } + + public void testReadSelfReal() throws RecordNotFoundException, IOException, FileException { + var storage = RecordManager.fromFile(PATH + "testReadSelfReal"); + var manager = new TransactionManagerImpl(); + // 创建事务1 + var tx1 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); + tx1.start(); + // 事务1的记录自身可见 + var a1 = storage.insert(tx1, new byte[]{0x23, 0x63}); + assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + // 事务2不可见a1 + var tx2 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); + tx2.start(); + assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + // 事务1新纪录也可见 + for (int i = 0; i < 100; i++) { + var uuid = storage.insert(tx1, new byte[]{0x23, 0x63, 0x44}); + assertTrue("uuid see a1", storage.queryOptional(tx1, uuid).isPresent()); + } + // + tx1.commit(); + tx2.commit(); + } + + public void testReadOtherReal() throws RecordNotFoundException, IOException, FileException { + var storage = RecordManager.fromFile(PATH + "testReadOtherReal"); + var manager = new TransactionManagerImpl(); + // 创建事务12 + var tx1 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); + tx1.start(); + var tx2 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); + tx2.start(); + // 事务2创建的b1,事务1不可见 + var b1 = storage.insert(tx2, new byte[]{0x1, 0x2, 0x3}); + assertTrue("tx2 see b1", storage.queryOptional(tx2, b1).isPresent()); + assertTrue("tx1 blind b1", storage.queryOptional(tx1, b1).isEmpty()); + // 事务1创建的a1,事务2不可见 + var a1 = storage.insert(tx1, new byte[]{0x6, 0x5, 0x4, 0x32}); + assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + // 事务2提交,则事务1依旧不可见b1,因为事务1不可见事务2 + tx2.commit(); + assertTrue("tx1 blind b1 after commit", storage.queryOptional(tx1, b1).isEmpty()); + // 新建事务3 + var tx3 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); + tx3.start(); + // 事务3可以看到b1,但是不能看到a1 + assertTrue("tx3 see b1 after commit", storage.queryOptional(tx3, b1).isPresent()); + assertTrue("tx3 blind a1 before commit", storage.queryOptional(tx3, a1).isEmpty()); + // 提交事务1,a1还是不可见,因为事务3创建时事务1还在运行 + tx1.commit(); + assertTrue("tx3 blind a1 after commit", storage.queryOptional(tx3, a1).isEmpty()); + // + tx3.commit(); + } + + public void testDeleteReal() throws RecordNotFoundException, IOException, FileException { + var storage = RecordManager.fromFile(PATH + "testDeleteReal"); + var manager = new TransactionManagerImpl(); + // 创建事务1、记录a1a2 + var tx1 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); + tx1.start(); + var a1 = storage.insert(tx1, new byte[]{0x1, 0x2, 0x3}); + var a2 = storage.insert(tx1, new byte[]{0x5, 0x6, 0x7}); + assertTrue(storage.queryOptional(tx1, a1).isPresent()); + assertTrue(storage.queryOptional(tx1, a2).isPresent()); + // 自身删除 + storage.delete(tx1, a1); + assertTrue("a1 should be deleted", storage.queryOptional(tx1, a1).isEmpty()); + // 提交事务1 + tx1.commit(); + // 事务2删除,事务3仍然可见a2 + var tx2 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); + tx2.start(); + var tx3 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); + tx3.start(); + storage.delete(tx2, a2); + assertTrue("tx2 blind a2 after delete", storage.queryOptional(tx2, a2).isEmpty()); + assertTrue("tx3 see a2 before commit", storage.queryOptional(tx3, a2).isPresent()); + // 事务2提交,事务3依旧可见a2,因为事务3开始时事务2没有结束 + tx2.commit(); + assertTrue("tx3 see a2 after commit", storage.queryOptional(tx3, a2).isPresent()); + // + tx3.commit(); + } + + public void testVersionSkipReal() throws RecordNotFoundException, NeedRollbackException, IOException, FileException { + var storage = RecordManager.fromFile(PATH + "testDeleteReal"); + var manager = new TransactionManagerImpl(); + // 创建公共版本 + var r = storage.insert(TransactionContext.empty(), new byte[]{0x1, 0x2, 0x3, 0x4}); + // 进行版本跳跃操作 + var tx1 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); + tx1.start(); + var tx2 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); + tx2.start(); + assertTrue(storage.queryOptional(tx1, r).isPresent()); + assertTrue(storage.queryOptional(tx2, r).isPresent()); + storage.delete(tx1, r); + tx1.commit(); + try { + storage.delete(tx2, r); + fail("Should rollback tx2 at here"); + tx2.commit(); + } catch (NeedRollbackException e) { + log.info("Expected runtime exception: ", e); + tx2.rollback(); + } + } } diff --git a/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java b/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java index daa9717..b19c727 100644 --- a/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java +++ b/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java @@ -2,7 +2,11 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; -import net.kaaass.rumbase.transaction.mock.MockTransactionManager; +import net.kaaass.rumbase.page.exception.FileException; +import net.kaaass.rumbase.transaction.exception.DeadlockException; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; /** * 测试事务上下文 @@ -15,8 +19,8 @@ public class TransactionContextTest extends TestCase { /** * 测试创建事务 */ - public void testCreateTransaction() { - var manager = new MockTransactionManager(); + public void testCreateTransaction() throws IOException, FileException { + var manager = new TransactionManagerImpl(); // 空事务 var emptyTransaction = TransactionContext.empty(); @@ -35,8 +39,9 @@ public void testCreateTransaction() { /** * 测试事务变化 */ - public void testChangeStatus() { - var manager = new MockTransactionManager(); + public void testChangeStatus() throws IOException, FileException { + // TODO 将Mock类改成实现类 + var manager = new TransactionManagerImpl(); var committedTransaction = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); // 事务初始状态 assertEquals("new transaction's default status should be PREPARING", TransactionStatus.PREPARING, committedTransaction.getStatus()); @@ -59,35 +64,41 @@ public void testChangeStatus() { /** * 测试事务持久化 */ - public void testTransactionPersistence() { - var manager = new MockTransactionManager(); + public void testTransactionPersistence() throws IOException, FileException { + // TODO 将Mock类改成实现类 + var manager = new TransactionManagerImpl(); // 事务创建,事务状态记录数改变 var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); - assertEquals(1, MockTransactionManager.TransactionSize); + assertEquals(1, manager.getSIZE().get()); var transaction2 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); - assertEquals(2, MockTransactionManager.TransactionSize); + assertEquals(2, manager.getSIZE().get()); // 事务状态持久化 - assertEquals(TransactionStatus.PREPARING.getStatusId(), (byte) MockTransactionManager.XidLog.get(transaction1.getXid())); - assertEquals(TransactionStatus.PREPARING.getStatusId(), (byte) MockTransactionManager.XidLog.get(transaction2.getXid())); + var txFromDisk1 = manager.getContext(1); + var txFromDisk2 = manager.getContext(2); + assertEquals(TransactionStatus.PREPARING, txFromDisk1.getStatus()); + assertEquals(TransactionStatus.PREPARING, txFromDisk2.getStatus()); transaction1.start(); - assertEquals(TransactionStatus.ACTIVE.getStatusId(), (byte) MockTransactionManager.XidLog.get(transaction1.getXid())); + txFromDisk1 = manager.getContext(1); + assertEquals(TransactionStatus.ACTIVE, txFromDisk1.getStatus()); transaction1.commit(); - assertEquals(TransactionStatus.COMMITTED.getStatusId(), (byte) MockTransactionManager.XidLog.get(transaction1.getXid())); + txFromDisk1 = manager.getContext(1); + assertEquals(TransactionStatus.COMMITTED, txFromDisk1.getStatus()); transaction2.start(); transaction2.rollback(); - assertEquals(TransactionStatus.ABORTED.getStatusId(), (byte) MockTransactionManager.XidLog.get(transaction2.getXid())); + txFromDisk2 = manager.getContext(2); + assertEquals(TransactionStatus.ABORTED, txFromDisk2.getStatus()); } /** * 测试事务状态复原 */ - public void testTransactionRecovery() { - var manager = new MockTransactionManager(); + public void testTransactionRecovery() throws IOException, FileException { + var manager = new TransactionManagerImpl(); // 事务创建,事务状态记录数改变 var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); int xid = transaction1.getXid(); @@ -106,14 +117,81 @@ public void testTransactionRecovery() { /** * 测试事务上锁 */ - public void testAddLock() { - var manager = new MockTransactionManager(); - var transaction = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); + public void testAddLock() throws IOException, FileException { + // TODO 将Mock类改成实现类 + var manager = new TransactionManagerImpl(); + var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); + var transaction2 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); + String tableName = "test"; + + + // 互斥锁 + new Thread(() -> { + try { + Thread.sleep(30); + } catch (InterruptedException e) { + e.printStackTrace(); + } + log.info("transaction2 commit"); + transaction2.commit(); + }).start(); + try { + transaction1.exclusiveLock(1, tableName); + transaction2.exclusiveLock(2, tableName); + transaction1.exclusiveLock(2, tableName); + log.info("transaction1 got exclusiveLock on 2"); + } catch (DeadlockException e) { + e.printStackTrace(); + } + transaction1.commit(); + + try { + transaction1.sharedLock(1, tableName); + transaction2.sharedLock(1, tableName); + transaction2.sharedLock(2, tableName); + transaction1.sharedLock(2, tableName); + + transaction1.commit(); + transaction2.rollback(); + } catch (DeadlockException e) { + e.printStackTrace(); + } + } + + /** + * 测试死锁 + */ + public void testDeadlock() throws IOException, FileException, InterruptedException { + var manager = new TransactionManagerImpl(); + var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); + var transaction2 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); String tableName = "test"; - // 加共享锁 - transaction.sharedLock(1, tableName); - // 加排他锁 - transaction.exclusiveLock(2, tableName); + AtomicBoolean deadlockDetect = new AtomicBoolean(false); + new Thread(() -> { + try { + Thread.sleep(3); + } catch (InterruptedException e) { + e.printStackTrace(); + } + try { + transaction2.exclusiveLock(1, tableName); + } catch (DeadlockException e) { + deadlockDetect.set(true); + transaction2.rollback(); + e.printStackTrace(); + } + }).start(); + try { + transaction1.exclusiveLock(1, tableName); + transaction2.exclusiveLock(2, tableName); + transaction1.exclusiveLock(2, tableName); + } catch (DeadlockException e) { + deadlockDetect.set(true); + transaction2.rollback(); + e.printStackTrace(); + } + Thread.sleep(10); + assertTrue("Deadlock should be detected", deadlockDetect.get()); } } From 3470fc97fc7bf5c17744a41da451a18a831568c5 Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Sat, 16 Jan 2021 00:56:09 +0800 Subject: [PATCH 11/18] =?UTF-8?q?[+]=E5=AE=8C=E6=88=90=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Completed a prototype * add notes * Initially completed * 增加“页不足时自动添加新的物理页”功能 * 修复内存不足时抛出异常无上层处理情况 * double write * undo double write * 增加一些测试用例 * Fix errors when reading and writing files * 修正测试用例的系列文件路径 * Fix errors when test all files * 完善测试用例 * 更新travis配置 * 更新travis配置展示test过程 * 延长addlock测试延迟时间 Co-authored-by: XuanLaoYee <1115810634@qq.com> Co-authored-by: Criterionist <1229089076@qq.com> --- .gitignore | 1 + .travis.yml | 1 + build.gradle | 7 + .../net/kaaass/rumbase/page/PageManager.java | 6 +- .../net/kaaass/rumbase/page/Replacer.java | 130 +++++++++++++++ .../net/kaaass/rumbase/page/RumBuffer.java | 132 ++++++++++++++++ .../java/net/kaaass/rumbase/page/RumPage.java | 127 +++++++++++++++ .../kaaass/rumbase/page/RumPageStorage.java | 101 ++++++++++++ ...erExeception.java => BufferException.java} | 8 +- .../kaaass/rumbase/page/mock/MockBuffer.java | 11 +- .../transaction/TransactionManagerImpl.java | 23 ++- .../rumbase/dataitem/IItemStorageTest.java | 32 ++-- .../kaaass/rumbase/page/PageStorageTest.java | 148 +++++++++++++++--- .../net/kaaass/rumbase/page/PageTest.java | 83 +++++----- .../transaction/TransactionContextTest.java | 99 ++++++++---- 15 files changed, 784 insertions(+), 125 deletions(-) create mode 100644 src/main/java/net/kaaass/rumbase/page/Replacer.java create mode 100644 src/main/java/net/kaaass/rumbase/page/RumBuffer.java create mode 100644 src/main/java/net/kaaass/rumbase/page/RumPage.java create mode 100644 src/main/java/net/kaaass/rumbase/page/RumPageStorage.java rename src/main/java/net/kaaass/rumbase/page/exception/{BufferExeception.java => BufferException.java} (63%) diff --git a/.gitignore b/.gitignore index 4540085..c66cfe5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ testFile +test_gen_files # IntelliJ IDEA .idea diff --git a/.travis.yml b/.travis.yml index 336a154..115da55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,5 @@ dist: trusty group: edge script: + - ./gradlew check - ./gradlew build --scan -s diff --git a/build.gradle b/build.gradle index 31aac2b..b0f85f1 100644 --- a/build.gradle +++ b/build.gradle @@ -35,3 +35,10 @@ if (hasProperty('buildScan')) { termsOfServiceAgree = 'yes' } } + +test { + testLogging { + events "passed", "skipped", "failed" + exceptionFormat "full" + } +} diff --git a/src/main/java/net/kaaass/rumbase/page/PageManager.java b/src/main/java/net/kaaass/rumbase/page/PageManager.java index 60cea7e..0483634 100644 --- a/src/main/java/net/kaaass/rumbase/page/PageManager.java +++ b/src/main/java/net/kaaass/rumbase/page/PageManager.java @@ -9,8 +9,8 @@ public class PageManager { public static int PAGE_SIZE = 1024 * 4; // 页面大小是4KB public static long FILE_HEAD_SIZE = 5; // 文件头留5页 - public static int PAGE_NUM = 50; - public static int BYTE_BUFFER_SIZE = 1024 * 4 * PAGE_NUM; + public static int BUFFER_SIZE = 1000; //缓冲大小,单位是页,页的大小不可以超过524287 + public static int BYTE_BUFFER_SIZE = 1024 * 4 * BUFFER_SIZE; /** * 取数据库文件生成文件管理的对象 @@ -20,6 +20,6 @@ public class PageManager { * @throws FileException 若文件不存在则创建,创建过程中出现错误会抛出错误 */ public static PageStorage fromFile(String filepath) throws FileException { - return new MockPageStorage(filepath); + return new RumPageStorage(filepath); } } diff --git a/src/main/java/net/kaaass/rumbase/page/Replacer.java b/src/main/java/net/kaaass/rumbase/page/Replacer.java new file mode 100644 index 0000000..283fc4c --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/page/Replacer.java @@ -0,0 +1,130 @@ +package net.kaaass.rumbase.page; + +import net.kaaass.rumbase.page.exception.BufferException; + +import java.util.HashMap; +import java.util.Map; + +public class Replacer { + private static Replacer instance = null; + private Replacer() { + this.head = null; + this.tail = null; + this.table = new HashMap<>(); + } + + public static Replacer getInstance() { + if (instance == null) { + synchronized (Replacer.class) { + if (instance == null) { + instance = new Replacer(); + } + } + } + return instance; + } + + public void insert(RumPage value) { + synchronized (this) { + if (!this.table.containsKey(value)) {//如果页没在内存中 + Node tmp = new Node(value, null); + if(head==null){ + this.head = tmp; + this.tail = tmp; + }else{ + this.tail.next = tmp; + this.tail = this.tail.next; + this.table.put(value, this.tail); + } + ++size; + } else { + Node hitNode = this.table.get(value); + hit(hitNode); + } + } + } + + /** + * 从链表中取出受害者页,若链表为空则返回null + * @return 返回受害者页面 + * @throws BufferException 若链表中所有的页均被钉住,则抛出异常 + */ + public RumPage victim() throws BufferException { + synchronized (this) { + if (size == 0) { + return null; + } + Node tmp = head; + while (tmp != null && tmp.pinned()){ + tmp = tmp.next; + } + if(tmp == null){ + throw new BufferException(2); + } + Node pre = head;//找tmp上一个节点 + if(pre!=tmp){ + while(pre.next != tmp){ + pre = pre.next; + } + } + if(tmp == head){ + head = head.next; + size--; + return tmp.getData(); + }else{ + pre.next = tmp.next; + size--; + return tmp.getData(); + } + } + } + + public int size() { + return size; + } + + /** + * 将命中的页移至链表尾 + * + * @param p 命中的节点 + */ + private void hit(Node p) { + Node tmp = head; + while (tmp.next != null) { + if (tmp.next == p) { + break; + } else { + tmp = tmp.next; + } + } + if (p.next != null) { + tmp.next = p.next; + this.tail.next = p; + this.tail = this.tail.next; + } + } + + private int size; + Node tail; + Node head; + Map table; + +} + +class Node { + private final RumPage data; + Node next = null; + + public Node(RumPage data, Node p) { + this.data = data; + this.next = p; + } + + public boolean pinned(){ + return data.pinned(); + } + + public RumPage getData(){ + return data; + } +} diff --git a/src/main/java/net/kaaass/rumbase/page/RumBuffer.java b/src/main/java/net/kaaass/rumbase/page/RumBuffer.java new file mode 100644 index 0000000..5f1b909 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/page/RumBuffer.java @@ -0,0 +1,132 @@ +package net.kaaass.rumbase.page; + +import net.kaaass.rumbase.page.exception.BufferException; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 内存管理 + *

+ * 使用单例模式,使用byte数组开辟连续空间,内存的大小不得超过BYTE_BUFFER_SIZE,RumBuffer在操作时直接将整块内存锁住 + *

+ * @author XuanLaoYee + */ +public class RumBuffer { + private static RumBuffer instance = null; + private int size = 0; + private ReentrantLock lock = null; + private final byte[] byteBuffer = new byte[PageManager.BYTE_BUFFER_SIZE]; + private List freePage = null; + + private RumBuffer(){ + this.lock = new ReentrantLock(); + this.size = PageManager.BUFFER_SIZE; + this.freePage = new ArrayList<>(); + for(int i=0;i + * 页持有的是整个缓冲区的指针和偏移。页在patchData时本身并不加锁,如果防止冲突需要在上层加锁。 + *

+ * @author XuanLaoYee + */ +public class RumPage implements Page { + public RumPage(byte[] data, long pageId, String filepath, int offset) { + this.data = data; + this.pageId = pageId; + this.dirty = false; + this.filepath = filepath; + this.offset = offset;//在内存中的偏移,指的是以页为单位的偏移 + } + + /** + * 得到的是数据的副本,而非缓冲区的指针。 + * @return clone后的数据 + */ + @Override + public byte[] getDataBytes() { + synchronized (this) { + byte[] tmp = new byte[PageManager.PAGE_SIZE]; + System.arraycopy(this.data, this.offset * PageManager.PAGE_SIZE, tmp, 0, tmp.length); + return tmp; + } + } + + /** + * + * @param offset 页内偏移值,以字节为单位 ,该过程不加锁 + * @param data 待写入数据 + * @throws PageException 回写数据偏移与大小之和超过规定,则抛出异常 + */ + @Override + public void patchData(int offset, byte[] data) throws PageException { + if (offset + data.length > PageManager.PAGE_SIZE) { + throw new PageException(1); + } + synchronized (this) { + this.dirty = true; + } + //直接往缓冲内写入 + System.arraycopy(data, 0, this.data, this.offset * PageManager.PAGE_SIZE + offset, data.length); + } + + /** + * 还未实现double write + * @throws FileException + */ + @Override + public void flush() throws FileException { + File file = new File(this.filepath); + synchronized (this) { + try { + RandomAccessFile out = new RandomAccessFile(file, "rw"); + try { + out.seek((PageManager.FILE_HEAD_SIZE + this.pageId) * (long)PageManager.PAGE_SIZE); + } catch (Exception e) { + throw new FileException(4); + } + try { + byte[] data = new byte[PageManager.PAGE_SIZE]; + System.arraycopy(this.data,this.offset*PageManager.PAGE_SIZE,data,0,data.length); + out.write(data); + } catch (Exception e) { + throw new FileException(2); + } + out.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Override + public void pin() { + synchronized (this) { + pinned++; + } + } + + @Override + public void unpin() { + synchronized (this) { + pinned--; + } + } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + public boolean pinned() { + return pinned > 0; + } + + public boolean dirty() { + return dirty; + } + + public Long pageId(){ + return this.pageId; + } + + private final byte[] data; + private final long pageId; + boolean dirty; + int pinned = 0; + String filepath; + int offset; +} diff --git a/src/main/java/net/kaaass/rumbase/page/RumPageStorage.java b/src/main/java/net/kaaass/rumbase/page/RumPageStorage.java new file mode 100644 index 0000000..d94dc23 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/page/RumPageStorage.java @@ -0,0 +1,101 @@ +package net.kaaass.rumbase.page; + +import net.kaaass.rumbase.page.exception.BufferException; +import net.kaaass.rumbase.page.exception.FileException; + +import java.io.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author 11158 + */ +public class RumPageStorage implements PageStorage { + public RumPageStorage(String filepath) throws FileException { + this.filepath = filepath; + pageMap = new HashMap<>(); + } + + @Override + public Page get(long pageId) { + File file = new File(filepath); + //文件不存在时创建新文件 + if (!file.exists()) { + try { + file.createNewFile(); + FileOutputStream out = new FileOutputStream(file); + out.write(new byte[PageManager.PAGE_SIZE * 10]); + } catch (IOException e) { + e.printStackTrace(); + } + } + //文件会预留5页作为文件头 + try { + FileInputStream in = new FileInputStream(file); + byte[] data = new byte[PageManager.PAGE_SIZE]; + try { + //当文件存储容量不够时追加 + while (in.available() < (pageId + 1 + PageManager.FILE_HEAD_SIZE) * PageManager.PAGE_SIZE) { + FileWriter fw = new FileWriter(file, true); + char[] blank = new char[PageManager.PAGE_SIZE * (in.available() / PageManager.PAGE_SIZE)]; + Arrays.fill(blank, (char)0); + fw.write(blank); + fw.close(); + } + in.skip((pageId + PageManager.FILE_HEAD_SIZE) * PageManager.PAGE_SIZE); + int readNumber = in.read(data); + if (readNumber < PageManager.PAGE_SIZE) { + throw new FileException(4); + } + } catch (Exception e) { + throw new FileException(4); + } + + Long tmpId = pageId; + if (pageMap.containsKey(tmpId)) { + return pageMap.get(tmpId); + } + int offset = -1; + while (offset < 0) { + synchronized (RumBuffer.getInstance()) {//并非区间锁,而是将整个内存全部锁住 + try { + offset = RumBuffer.getInstance().getFreeOffset(); + RumBuffer.getInstance().put(offset, data); + } catch (BufferException e) { + //下面的这个换出算法没有考虑到在此过程中其他进程再次pin()的情况 + RumPage p = Replacer.getInstance().victim(); + if (p.dirty()) { + p.flush(); + } + RumBuffer.getInstance().free(p.offset); + this.pageMap.remove(p.pageId()); + } + } + } + RumPage page = new RumPage(RumBuffer.getInstance().buffer(), pageId, this.filepath, offset); + Replacer.getInstance().insert(page); + pageMap.put(tmpId, page); + return page; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @Override + public void flush() { + Set> entrySet = this.pageMap.entrySet(); + for (Map.Entry entry : entrySet) { + try { + entry.getValue().flush(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private final Map pageMap; + private final String filepath; +} diff --git a/src/main/java/net/kaaass/rumbase/page/exception/BufferExeception.java b/src/main/java/net/kaaass/rumbase/page/exception/BufferException.java similarity index 63% rename from src/main/java/net/kaaass/rumbase/page/exception/BufferExeception.java rename to src/main/java/net/kaaass/rumbase/page/exception/BufferException.java index 4025d82..8d14de2 100644 --- a/src/main/java/net/kaaass/rumbase/page/exception/BufferExeception.java +++ b/src/main/java/net/kaaass/rumbase/page/exception/BufferException.java @@ -9,12 +9,16 @@ * E9003 缓冲异常 *

* E9003-1 内存不足无法换入 + * E9003-2 内存中所有页均被钉住无法换出 + * E9003-3 占用非空内存位置 * * @author XuanLaoYee */ -public class BufferExeception extends RumbaseException { +public class BufferException extends RumbaseException { public static final Map REASONS = new HashMap() {{ put(1, "内存不足无法换入"); + put(2, "内存中所有页均被钉住无法换出"); + put(3, "占用非空内存位置"); }}; /** @@ -22,7 +26,7 @@ public class BufferExeception extends RumbaseException { * * @param subId 子错误号 */ - public BufferExeception(int subId) { + public BufferException(int subId) { super(9003, subId, REASONS.get(subId)); } } diff --git a/src/main/java/net/kaaass/rumbase/page/mock/MockBuffer.java b/src/main/java/net/kaaass/rumbase/page/mock/MockBuffer.java index 71da150..a536bc3 100644 --- a/src/main/java/net/kaaass/rumbase/page/mock/MockBuffer.java +++ b/src/main/java/net/kaaass/rumbase/page/mock/MockBuffer.java @@ -1,17 +1,20 @@ package net.kaaass.rumbase.page.mock; import net.kaaass.rumbase.page.PageManager; -import net.kaaass.rumbase.page.exception.BufferExeception; +import net.kaaass.rumbase.page.exception.BufferException; import java.util.concurrent.locks.ReentrantLock; +/** + * + */ public class MockBuffer { private static MockBuffer instance = null; private int size = 0; private MockBuffer() { this.lock = new ReentrantLock(); - size = PageManager.PAGE_NUM; + size = PageManager.BUFFER_SIZE; } public static MockBuffer getInstance() { @@ -27,9 +30,9 @@ public static MockBuffer getInstance() { private final byte[] byteBuffer = new byte[PageManager.BYTE_BUFFER_SIZE]; - public void put(int offset, byte[] bytes) throws BufferExeception { + public void put(int offset, byte[] bytes) throws BufferException { if (this.size <= 0) { - throw new BufferExeception(1); + throw new BufferException(1); } lock.lock(); try { diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionManagerImpl.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionManagerImpl.java index 819e141..2b6f1b5 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/TransactionManagerImpl.java +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionManagerImpl.java @@ -28,14 +28,14 @@ @Slf4j public class TransactionManagerImpl implements TransactionManager { - /** - * 事务状态持久化文件名 - */ - private static final String LOG_FILE_NAME = "xid.log"; /** * 每页最大事务状态数 */ private static final int TX_NUM_PER_PAGE = 2048; + /** + * 事务状态持久化文件名 + */ + private final String LOG_FILE_NAME; /** * 事务ID计数器 */ @@ -58,9 +58,20 @@ public class TransactionManagerImpl implements TransactionManager { private final Map txCache = new HashMap<>(); /** - * Mock事务管理器 + * 事务管理器 */ - public TransactionManagerImpl() throws FileException, IOException { + public TransactionManagerImpl() throws IOException, FileException { + this("xid.log"); + } + + /** + * 事务管理器 + * + * @param logFile 事务日志文件 + */ + public TransactionManagerImpl(String logFile) throws FileException, IOException { + this.LOG_FILE_NAME = logFile; + if (new File(LOG_FILE_NAME).exists()) { // 初始化storage this.storage = PageManager.fromFile(LOG_FILE_NAME); diff --git a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java index 588a8e7..3cdbb00 100644 --- a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java @@ -26,19 +26,21 @@ @Slf4j public class IItemStorageTest extends TestCase { + private static final String PATH = "build/"; + /** * 测试能否从已有文件中解析得到数据项管理器 */ public void testGetFromFile() throws FileException, IOException, PageException { - String fileName = "testGetFromFile.db"; + String fileName = PATH + "testGetFromFile.db"; var itemStorage = ItemManager.fromFile(fileName); // 如果表中没有对应的文件,那么就抛出错误 - String failFileName = "error.db"; - try { - IItemStorage iItemStorage1 = ItemManager.fromFile(failFileName); - } catch (FileException f) { - log.error("Exception Error :", f); - } +// String failFileName = "error.db"; +// try { +// IItemStorage iItemStorage1 = ItemManager.fromFile(failFileName); +// } catch (FileException f) { +// log.error("Exception Error :", f); +// } } /** @@ -46,7 +48,7 @@ public void testGetFromFile() throws FileException, IOException, PageException { */ public void testCreateFile() throws IOException, FileException, PageException { TransactionContext txContext = TransactionContext.empty(); - String fileName = "testCreateFile.db"; + String fileName = PATH + "testCreateFile.db"; byte[] metadata = new byte[1024]; // 第一次执行的时候,表中没有数据,不会报错 var iItemStorage = ItemManager.createFile(txContext, fileName, metadata); @@ -63,7 +65,7 @@ public void testCreateFile() throws IOException, FileException, PageException { * 进行插入的测试 */ public void testInsert() throws FileException, IOException, PageException, UUIDException, PageCorruptedException { - String fileName = "testInsert.db"; + String fileName = PATH + "testInsert.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; TransactionContext txContext = TransactionContext.empty(); @@ -80,7 +82,7 @@ public void testInsert() throws FileException, IOException, PageException, UUIDE * 对插入一个已分配UUID的测试 */ public void testInsertWithUUID() throws FileException, IOException, PageException { - String fileName = "testInsertWithUUID.db"; + String fileName = PATH + "testInsertWithUUID.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; long s = 1; @@ -104,7 +106,7 @@ public void testInsertWithUUID() throws FileException, IOException, PageExceptio * 对插入大量数据进行测试 */ public void testManyInsert() throws FileException, IOException, PageException, UUIDException, PageCorruptedException { - String fileName = "testInsertMany.db"; + String fileName = PATH + "testInsertMany.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; TransactionContext txContext = TransactionContext.empty(); @@ -124,7 +126,7 @@ public void testManyInsert() throws FileException, IOException, PageException, U * 获取整个页的数据项进行测试 */ public void testQueryByPageID() throws FileException, IOException, PageException, PageCorruptedException { - String fileName = "testQueryByPageID.db"; + String fileName = PATH + "testQueryByPageID.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; TransactionContext txContext = TransactionContext.empty(); @@ -187,7 +189,7 @@ public void run() { * 测试并发下插入是否有问题 */ public void testSynInsert() throws IOException, FileException, PageException { - String fileName = "testInsert.db"; + String fileName = PATH + "testInsert.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; TransactionContext txContext = TransactionContext.empty(); @@ -202,7 +204,7 @@ public void testSynInsert() throws IOException, FileException, PageException { * 对更新进行测试 */ public void testUpdate() throws FileException, IOException, PageException, UUIDException, PageCorruptedException { - String fileName = "testUpdate.db"; + String fileName = PATH + "testUpdate.db"; TransactionContext txContext = TransactionContext.empty(); IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] bytes = new byte[]{1, 2, 3, 4}; @@ -229,7 +231,7 @@ public void testUpdate() throws FileException, IOException, PageException, UUIDE * 测试修改和获取表头信息 */ public void testMeta() throws FileException, IOException, PageException, UUIDException, PageCorruptedException { - String fileName = "testMeta.db"; + String fileName = PATH + "testMeta.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); byte[] result = new byte[]{1, 2, 3, 4}; TransactionContext txContext = TransactionContext.empty(); diff --git a/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java b/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java index ee91045..aec89c5 100644 --- a/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java @@ -1,9 +1,13 @@ package net.kaaass.rumbase.page; import junit.framework.TestCase; +import net.kaaass.rumbase.page.exception.FileException; +import net.kaaass.rumbase.page.exception.PageException; import java.io.File; import java.io.FileInputStream; +import java.io.IOException; +import java.util.Arrays; import static org.junit.Assert.assertArrayEquals; @@ -14,30 +18,138 @@ * @see net.kaaass.rumbase.page.PageStorage */ public class PageStorageTest extends TestCase { - public void testGet() { + public static String filePath = "build/pageTest.db"; + + public void testGet() throws IOException { + PageStorage rps = null; try { - PageStorage pc = PageManager.fromFile("testFile"); - Page p0 = pc.get(0); - Page p3 = pc.get(3); - byte[] data0 = new byte[1024 * 4]; - byte[] data3 = new byte[1024 * 4]; - p0.getData().read(data0); - p3.getData().read(data3); + rps = PageManager.fromFile(filePath); + } catch (Exception e) { + e.printStackTrace(); + } + assert rps != null; + for (int i = 0; i < 10; i++) { + Page p = rps.get(i); + p.pin(); + } + Page p0 = rps.get(0); + Page p1 = rps.get(1); + p0.unpin(); + p1.unpin(); + Page p10 = rps.get(10); + p10.pin(); + Page p11 = rps.get(11); + p11.pin(); - byte[] testData0 = new byte[1024 * 4]; - for (int j = 0; j < 1024 * 4; j++) { - testData0[j] = (byte)5; - } - byte[] testData3 = new byte[1024 * 4]; - for (int j = 0; j < 1024 * 4; j++) { - testData3[j] = (byte)8; + File file = new File(filePath); + try { + FileInputStream in = new FileInputStream(file); + byte[] dataFromFile = new byte[PageManager.PAGE_SIZE]; + byte[] dataFromPage = new byte[PageManager.PAGE_SIZE]; + in.skip((11 + PageManager.FILE_HEAD_SIZE) * PageManager.PAGE_SIZE); + int readNumber = in.read(dataFromFile); + p11.getData().read(dataFromPage); + assertEquals(readNumber, PageManager.PAGE_SIZE); + assertArrayEquals(dataFromPage, dataFromFile); + } catch (Exception e) { + throw e; + } finally { + p10.unpin(); + p11.unpin(); + } + } + + public void testWriteToFile() throws FileException, PageException { + PageStorage storage = PageManager.fromFile(filePath); + int[] testPage = new int[]{1, 3, 5, 7, 10, 11}; + // 测试每一页是否能正常读写 + for (var pageId: testPage) { + // 准备标志数据 + byte[] data = new byte[PageManager.PAGE_SIZE]; + Arrays.fill(data, (byte) (0xF0 | pageId)); + // 获取页 + var page = storage.get(pageId); + page.pin(); + // 写入页 + try { + // 写数据 + page.patchData(0, data); + page.flush(); + } finally { + page.unpin(); } - assertArrayEquals(testData0,data0); - assertArrayEquals(testData3,data3); + // 检查页数据 + var tempStorage = PageManager.fromFile(filePath); + var pageData = tempStorage.get(pageId).getDataBytes(); + assertArrayEquals(data, pageData); + } + } + public void testFlush() throws PageException { + PageStorage rps = null; + try { + rps = PageManager.fromFile(filePath); } catch (Exception e) { e.printStackTrace(); } - + assert rps != null; + Page p0 = rps.get(0); + Page p3 = rps.get(3); + Page p4 = rps.get(4); + p0.pin(); + p3.pin(); + p4.pin(); + byte[] data0 = new byte[PageManager.PAGE_SIZE]; + byte[] data3 = new byte[PageManager.PAGE_SIZE]; + byte[] data4 = new byte[PageManager.PAGE_SIZE]; + byte[] dataFromFile0 = new byte[PageManager.PAGE_SIZE]; + byte[] dataFromFile3 = new byte[PageManager.PAGE_SIZE]; + byte[] dataFromFile4 = new byte[PageManager.PAGE_SIZE]; + for (int i = 0; i < PageManager.PAGE_SIZE; i++) { + data0[i] = (byte) 0xf0; + data3[i] = (byte) 0xf3; + data4[i] = (byte) 0xf4; + } + // 打印下之前的数据,检查之前写入是否正确 + try { + p0.writeData(data0); + p3.writeData(data3); + p4.writeData(data4); + rps.flush(); + } catch (Exception e) { + throw e; + } finally { + p0.unpin(); + p3.unpin(); + p4.unpin(); + } + File file = new File(filePath); + try { + FileInputStream in = new FileInputStream(file); + in.skip((PageManager.FILE_HEAD_SIZE) * PageManager.PAGE_SIZE); + in.read(dataFromFile0); + in.close(); + in = new FileInputStream(file); + in.skip((3 + PageManager.FILE_HEAD_SIZE) * PageManager.PAGE_SIZE); + in.read(dataFromFile3); + in.close(); + in = new FileInputStream(file); + in.skip((4 + PageManager.FILE_HEAD_SIZE) * PageManager.PAGE_SIZE); + in.read(dataFromFile4); + in.close(); + } catch (Exception e) { + e.printStackTrace(); + } + try { + p0.getData().read(data0); + p3.getData().read(data3); + p4.getData().read(data4); + } catch (Exception e) { + e.printStackTrace(); + } + assertArrayEquals(data0, dataFromFile0); + assertArrayEquals(data3, dataFromFile3); + assertArrayEquals(data4, dataFromFile4); } + } \ No newline at end of file diff --git a/src/test/java/net/kaaass/rumbase/page/PageTest.java b/src/test/java/net/kaaass/rumbase/page/PageTest.java index a00de9b..1832c99 100644 --- a/src/test/java/net/kaaass/rumbase/page/PageTest.java +++ b/src/test/java/net/kaaass/rumbase/page/PageTest.java @@ -1,9 +1,14 @@ package net.kaaass.rumbase.page; import junit.framework.TestCase; +import net.kaaass.rumbase.page.exception.FileException; +import net.kaaass.rumbase.page.exception.PageException; +import org.junit.Assert; import java.io.File; import java.io.FileInputStream; +import java.util.Arrays; +import java.util.Random; import static org.junit.Assert.assertArrayEquals; @@ -14,52 +19,7 @@ * @see net.kaaass.rumbase.page.Page */ public class PageTest extends TestCase { - - public void testGetData() { - try { - PageStorage pc = PageManager.fromFile("testFile"); - Page p0 = pc.get(0); - Page p3 = pc.get(3); - byte[] data0 = new byte[1024 * 4]; - byte[] data3 = new byte[1024 * 4]; - p0.getData().read(data0); - p3.getData().read(data3); - byte[] testData0 = new byte[1024 * 4]; - for (int j = 0; j < 1024 * 4; j++) { - testData0[j] = (byte)5; - } - byte[] testData3 = new byte[1024 * 4]; - for (int j = 0; j < 1024 * 4; j++) { - testData3[j] = (byte)8; - } - assertArrayEquals(testData0,data0); - assertArrayEquals(testData3,data3); - - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void testWriteData() { - byte[] data = new byte[PageManager.PAGE_SIZE]; - for (int i = 0; i < data.length; i++) { - data[i] = (byte) (i % 120); - } - - try { - PageStorage pc = PageManager.fromFile("testFile"); - Page p0 = pc.get(0); - //write之前需要先pin - p0.pin(); - p0.writeData(data); - //pin和unpin成对出现 - p0.unpin(); - assertArrayEquals(data, pc.get(0).getDataBytes()); - } catch (Exception e) { - e.printStackTrace(); - } - } - + public static String filePath = "build/pageTest.db"; public void testPatchData() { int offset = 99; byte[] data = new byte[PageManager.PAGE_SIZE - offset]; @@ -68,7 +28,7 @@ public void testPatchData() { } try { - PageStorage pc = PageManager.fromFile("testFile"); + PageStorage pc = PageManager.fromFile(filePath); Page p0 = pc.get(0); byte[] originalData = p0.getDataBytes(); p0.patchData(offset, data); @@ -80,4 +40,33 @@ public void testPatchData() { e.printStackTrace(); } } + + public void testPatchOffset() throws FileException, PageException { + var storage = PageManager.fromFile(filePath); + var rand = new Random(); + var page = storage.get(2); + page.pin(); + try { + for (int i = 0; i < 50; i++) { + // 随机决定开始、结束 + var st = rand.nextInt(4000); + var ed = st + rand.nextInt(2000); + if (ed > 4096) { + ed = 4096; + } + // 生成相关数据 + byte[] data = new byte[ed - st]; + Arrays.fill(data, (byte) st); + // 写入 + page.patchData(st, data); + // 检查写入效果 + var pageData = page.getDataBytes(); + for (int j = st; j < ed; j++) { + assertEquals((byte) st, pageData[j]); + } + } + } finally { + page.unpin(); + } + } } \ No newline at end of file diff --git a/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java b/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java index b19c727..9b97c6d 100644 --- a/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java +++ b/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java @@ -4,7 +4,12 @@ import lombok.extern.slf4j.Slf4j; import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.transaction.exception.DeadlockException; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import java.io.File; import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; @@ -14,112 +19,145 @@ * @author criki */ @Slf4j -public class TransactionContextTest extends TestCase { +public class TransactionContextTest { + + public static void removeDir(File dir) { + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + removeDir(file); + } else { + file.delete(); + } + } + } + + dir.delete(); + } + + /** + * 创建临时文件生成目录 + */ + @BeforeClass + public static void createTmpDir() { + File dir = new File("test_gen_files"); + if (!dir.exists()) { + dir.mkdir(); + } else { + removeDir(dir); + dir.mkdir(); + } + } /** * 测试创建事务 */ + @Test public void testCreateTransaction() throws IOException, FileException { - var manager = new TransactionManagerImpl(); + var manager = new TransactionManagerImpl("test_gen_files/test_create.log"); // 空事务 var emptyTransaction = TransactionContext.empty(); - assertEquals(0, emptyTransaction.getXid()); - assertEquals(TransactionStatus.COMMITTED, emptyTransaction.getStatus()); - assertEquals(TransactionIsolation.READ_UNCOMMITTED, emptyTransaction.getIsolation()); + Assert.assertEquals(0, emptyTransaction.getXid()); + Assert.assertEquals(TransactionStatus.COMMITTED, emptyTransaction.getStatus()); + Assert.assertEquals(TransactionIsolation.READ_UNCOMMITTED, emptyTransaction.getIsolation()); // 普通事务 var transaction = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); - assertEquals(1, transaction.getXid()); - assertEquals(TransactionStatus.PREPARING, transaction.getStatus()); - assertEquals(TransactionIsolation.READ_COMMITTED, transaction.getIsolation()); - + Assert.assertEquals(1, transaction.getXid()); + Assert.assertEquals(TransactionStatus.PREPARING, transaction.getStatus()); + Assert.assertEquals(TransactionIsolation.READ_COMMITTED, transaction.getIsolation()); } /** * 测试事务变化 */ + @Test public void testChangeStatus() throws IOException, FileException { // TODO 将Mock类改成实现类 - var manager = new TransactionManagerImpl(); + var manager = new TransactionManagerImpl("test_gen_files/test_change.log"); var committedTransaction = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); // 事务初始状态 - assertEquals("new transaction's default status should be PREPARING", TransactionStatus.PREPARING, committedTransaction.getStatus()); + Assert.assertEquals("new transaction's default status should be PREPARING", TransactionStatus.PREPARING, committedTransaction.getStatus()); // 事务开始 committedTransaction.start(); - assertEquals("starting transaction's default status should be ACTIVE", TransactionStatus.ACTIVE, committedTransaction.getStatus()); + Assert.assertEquals("starting transaction's default status should be ACTIVE", TransactionStatus.ACTIVE, committedTransaction.getStatus()); // 事务提交 committedTransaction.commit(); - assertEquals("committed transaction's default status should be COMMITTED", TransactionStatus.COMMITTED, committedTransaction.getStatus()); + Assert.assertEquals("committed transaction's default status should be COMMITTED", TransactionStatus.COMMITTED, committedTransaction.getStatus()); // 事务中止 var abortedTransaction = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); abortedTransaction.start(); abortedTransaction.rollback(); - assertEquals("aborted transaction's default status should be ABORTED", TransactionStatus.ABORTED, abortedTransaction.getStatus()); + Assert.assertEquals("aborted transaction's default status should be ABORTED", TransactionStatus.ABORTED, abortedTransaction.getStatus()); } /** * 测试事务持久化 */ + @Test public void testTransactionPersistence() throws IOException, FileException { // TODO 将Mock类改成实现类 - var manager = new TransactionManagerImpl(); + var manager = new TransactionManagerImpl("test_gen_files/test_persistence.log"); // 事务创建,事务状态记录数改变 var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); - assertEquals(1, manager.getSIZE().get()); + Assert.assertEquals(1, manager.getSIZE().get()); var transaction2 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); - assertEquals(2, manager.getSIZE().get()); + Assert.assertEquals(2, manager.getSIZE().get()); // 事务状态持久化 var txFromDisk1 = manager.getContext(1); var txFromDisk2 = manager.getContext(2); - assertEquals(TransactionStatus.PREPARING, txFromDisk1.getStatus()); - assertEquals(TransactionStatus.PREPARING, txFromDisk2.getStatus()); + Assert.assertEquals(TransactionStatus.PREPARING, txFromDisk1.getStatus()); + Assert.assertEquals(TransactionStatus.PREPARING, txFromDisk2.getStatus()); transaction1.start(); txFromDisk1 = manager.getContext(1); - assertEquals(TransactionStatus.ACTIVE, txFromDisk1.getStatus()); + Assert.assertEquals(TransactionStatus.ACTIVE, txFromDisk1.getStatus()); transaction1.commit(); txFromDisk1 = manager.getContext(1); - assertEquals(TransactionStatus.COMMITTED, txFromDisk1.getStatus()); + Assert.assertEquals(TransactionStatus.COMMITTED, txFromDisk1.getStatus()); transaction2.start(); transaction2.rollback(); txFromDisk2 = manager.getContext(2); - assertEquals(TransactionStatus.ABORTED, txFromDisk2.getStatus()); + Assert.assertEquals(TransactionStatus.ABORTED, txFromDisk2.getStatus()); } /** * 测试事务状态复原 */ + @Test public void testTransactionRecovery() throws IOException, FileException { - var manager = new TransactionManagerImpl(); + var manager = new TransactionManagerImpl("test_gen_files/test_recovery.log"); // 事务创建,事务状态记录数改变 var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); int xid = transaction1.getXid(); // 复原事务 var transactionR = manager.getContext(xid); - assertEquals(TransactionStatus.PREPARING, transactionR.getStatus()); - assertEquals(TransactionIsolation.READ_UNCOMMITTED, transactionR.getIsolation()); + Assert.assertEquals(TransactionStatus.PREPARING, transactionR.getStatus()); + Assert.assertEquals(TransactionIsolation.READ_UNCOMMITTED, transactionR.getIsolation()); // 改变事务状态 transaction1.start(); transactionR = manager.getContext(xid); - assertEquals(TransactionStatus.ACTIVE, transactionR.getStatus()); + Assert.assertEquals(TransactionStatus.ACTIVE, transactionR.getStatus()); } /** * 测试事务上锁 */ + @Test public void testAddLock() throws IOException, FileException { // TODO 将Mock类改成实现类 - var manager = new TransactionManagerImpl(); + var manager = new TransactionManagerImpl("test_gen_files/test_add_lock.log"); var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); var transaction2 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); String tableName = "test"; @@ -128,7 +166,7 @@ public void testAddLock() throws IOException, FileException { // 互斥锁 new Thread(() -> { try { - Thread.sleep(30); + Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } @@ -161,8 +199,9 @@ public void testAddLock() throws IOException, FileException { /** * 测试死锁 */ + @Test public void testDeadlock() throws IOException, FileException, InterruptedException { - var manager = new TransactionManagerImpl(); + var manager = new TransactionManagerImpl("test_gen_files/test_deadlock.log"); var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); var transaction2 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); String tableName = "test"; @@ -192,6 +231,6 @@ public void testDeadlock() throws IOException, FileException, InterruptedExcepti e.printStackTrace(); } Thread.sleep(10); - assertTrue("Deadlock should be detected", deadlockDetect.get()); + Assert.assertTrue("Deadlock should be detected", deadlockDetect.get()); } } From fd4f7a5f298cd6206a476fa0815a779183fbcffe Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Sat, 16 Jan 2021 01:30:37 +0800 Subject: [PATCH 12/18] =?UTF-8?q?[+]=E5=A2=9E=E5=8A=A0=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 增加错误基类、简单的记录接口 * 增加.gitignore * 完成记录的mock * 增加记录接口的简易单元测试 * 引入Slf4j、log4j、jbbp外部库 * [+]add table mock and unit test * 增加Travis配置 * fix: transform java14 to java11 * 更新Gradle配置以允许Scan ToS * 删除冗余Travis配置 * README增加Travis Badge * 增加UTF-8 gradle配置 * 对于数据项管理的mock和测试部分 * 修改测试用例的文件名 * index mock * 增加事务的Mock * testPage * index mock plus test * 对于数据项管理的mock和测试部分的修改 * testPage1.01 * dataitem模块修改建议 * 修正编码风格 * 对于数据项管理的mock和测试部分 * 对于数据项管理的mock和测试部分 * Index审计、修改建议 * 增加JBBP的使用示例 * page模块审计、修改建议 * 删除table模块,更改record模块 * recovery模块审计 * transaction模块审计 * 对于日志恢复的接口和测试 * 完善事务模块接口 * testPage1.02 * testPage1.03 * 复审recovery,tx * 格式化代码 * 更新README * 修改异常名称 * 修正编译失败 * 表创建、表头解析以及页头解析 * 索引审计修改 * RumbaseException增加错误原因 * 完善事务模块接口,增加事务模块测试用例 * 修正事务接口 * 修正事务接口 * 测试用例修改 * B+树索引,没有flush,没有锁,用synchronized并发也有小问题 * 改进测试用例 * synchronized修改 * 加了pin和unpin,听从lmx建议取消了root页作为成员 * 加了flush,完善了pin和unpin,添加了相对更为复杂的并发测试,由于mock的限制,flush和pin功能并不能得到良好的保证 * 修正、改进单元测试 * 缩小单元测试规模,解决文件冲突 Co-authored-by: Kevin Axel Manjaro Co-authored-by: Kevin Axel <41867249+KveinAxel@users.noreply.github.com> Co-authored-by: xiaoxineryi <529086017@qq.com> Co-authored-by: DctorWei1314 <61504793+DctorWei1314@users.noreply.github.com> Co-authored-by: Criterionist <1229089076@qq.com> Co-authored-by: XuanLaoYee <1115810634@qq.com> --- .../java/net/kaaass/rumbase/index/Index.java | 33 +- .../rumbase/index/btree/BPlusTreeIndex.java | 1194 +++++++++++++++++ .../kaaass/rumbase/index/btree/ByteUtil.java | 90 ++ .../exception/ItemInNextPageException.java | 42 + .../index/exception/PageFullException.java | 36 + .../index/exception/PageTypeException.java | 37 + .../rumbase/page/mock/MockPageStorage.java | 4 +- .../net/kaaass/rumbase/index/BTreeTest.java | 188 +++ .../rumbase/index/ConcurrentIndexTest.java | 117 ++ .../net/kaaass/rumbase/index/IndexTest.java | 53 +- 10 files changed, 1749 insertions(+), 45 deletions(-) create mode 100644 src/main/java/net/kaaass/rumbase/index/btree/BPlusTreeIndex.java create mode 100644 src/main/java/net/kaaass/rumbase/index/btree/ByteUtil.java create mode 100644 src/main/java/net/kaaass/rumbase/index/exception/ItemInNextPageException.java create mode 100644 src/main/java/net/kaaass/rumbase/index/exception/PageFullException.java create mode 100644 src/main/java/net/kaaass/rumbase/index/exception/PageTypeException.java create mode 100644 src/test/java/net/kaaass/rumbase/index/BTreeTest.java create mode 100644 src/test/java/net/kaaass/rumbase/index/ConcurrentIndexTest.java diff --git a/src/main/java/net/kaaass/rumbase/index/Index.java b/src/main/java/net/kaaass/rumbase/index/Index.java index 28f90ff..aba3871 100644 --- a/src/main/java/net/kaaass/rumbase/index/Index.java +++ b/src/main/java/net/kaaass/rumbase/index/Index.java @@ -1,9 +1,17 @@ package net.kaaass.rumbase.index; +import net.kaaass.rumbase.index.btree.BPlusTreeIndex; import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; import net.kaaass.rumbase.index.exception.IndexNotFoundException; import net.kaaass.rumbase.index.mock.MockBtreeIndex; +import net.kaaass.rumbase.page.Page; +import net.kaaass.rumbase.page.PageManager; +import net.kaaass.rumbase.page.PageStorage; +import net.kaaass.rumbase.page.exception.FileException; +import javax.swing.*; +import java.io.File; +import java.io.IOException; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -23,10 +31,16 @@ public interface Index extends Iterable { * @throws IndexNotFoundException */ static Index getIndex(String indexFileName) throws IndexNotFoundException { - if (MockBtreeIndex.MOCK_BTREE_INDEX_MAP.get(indexFileName) == null) { - throw new IndexNotFoundException(1); - } - return MockBtreeIndex.MOCK_BTREE_INDEX_MAP.get(indexFileName); + if (BPlusTreeIndex.B_PLUS_TREE_INDEX_MAP.get(indexFileName) != null) { + return BPlusTreeIndex.B_PLUS_TREE_INDEX_MAP.get(indexFileName); + } else { + BPlusTreeIndex bPlusTreeIndex = new BPlusTreeIndex(indexFileName); + if (!bPlusTreeIndex.isIndexedFile()) { + throw new IndexNotFoundException(1); + } + BPlusTreeIndex.B_PLUS_TREE_INDEX_MAP.put(indexFileName, bPlusTreeIndex); + return bPlusTreeIndex; + } } /** @@ -36,7 +50,7 @@ static Index getIndex(String indexFileName) throws IndexNotFoundException { * @return */ static boolean exists(String indexFileName) { - return MockBtreeIndex.MOCK_BTREE_INDEX_MAP.get(indexFileName) != null; + return new File(indexFileName).exists(); } /** @@ -47,12 +61,13 @@ static boolean exists(String indexFileName) { * @throws IndexAlreadyExistException */ static Index createEmptyIndex(String indexFileName) throws IndexAlreadyExistException { - if (MockBtreeIndex.MOCK_BTREE_INDEX_MAP.get(indexFileName) == null) { - MockBtreeIndex.MOCK_BTREE_INDEX_MAP.put(indexFileName, new MockBtreeIndex()); - } else { + BPlusTreeIndex bPlusTreeIndex = new BPlusTreeIndex(indexFileName); + if (bPlusTreeIndex.isIndexedFile()) { throw new IndexAlreadyExistException(1); } - return MockBtreeIndex.MOCK_BTREE_INDEX_MAP.get(indexFileName); + bPlusTreeIndex.initPage(); + BPlusTreeIndex.B_PLUS_TREE_INDEX_MAP.put(indexFileName, bPlusTreeIndex); + return bPlusTreeIndex; } /** diff --git a/src/main/java/net/kaaass/rumbase/index/btree/BPlusTreeIndex.java b/src/main/java/net/kaaass/rumbase/index/btree/BPlusTreeIndex.java new file mode 100644 index 0000000..5b4b81d --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/index/btree/BPlusTreeIndex.java @@ -0,0 +1,1194 @@ +package net.kaaass.rumbase.index.btree; + +import net.kaaass.rumbase.index.Index; +import net.kaaass.rumbase.index.Pair; +import net.kaaass.rumbase.index.exception.ItemInNextPageException; +import net.kaaass.rumbase.index.exception.PageFullException; +import net.kaaass.rumbase.index.exception.PageTypeException; +import net.kaaass.rumbase.page.Page; +import net.kaaass.rumbase.page.PageManager; +import net.kaaass.rumbase.page.PageStorage; +import net.kaaass.rumbase.page.exception.FileException; +import net.kaaass.rumbase.page.exception.PageException; + +import javax.management.loading.MLet; +import java.nio.file.attribute.UserDefinedFileAttributeView; +import java.util.*; +import java.util.concurrent.locks.ReadWriteLock; + +/** + * @author 无索魏 + */ +public class BPlusTreeIndex implements Index { +// Page root; + PageStorage pageStorage; + + private enum PageType { //页类型 + INTERNAL, + //树枝节点 + LEAF, + //树叶节点 + META; + //元信息节点 + } + + /** + * 内存中已经加载的的BPlusTreeIndex + */ + + public static Map B_PLUS_TREE_INDEX_MAP = new HashMap<>(); + public static int MAX_PAGE_ITEM = (4096 - 24)/16; + public static int PAGE_BEGIN_POSTION = 24; + public static int PAGE_MID_POSTION = PAGE_BEGIN_POSTION + 16 * (BPlusTreeIndex.MAX_PAGE_ITEM)/2 ; + private static final int rootNum = 4; + + private Map RW_LOCKS = new HashMap<>(); + + public BPlusTreeIndex(String indexName) { + try { + pageStorage = PageManager.fromFile(indexName); + } catch (FileException e) { + e.printStackTrace(); + } + } + + + synchronized public void initPage() { + initRootAsLeaf(); + Page metaPage = this.pageStorage.get(0); + //pin + metaPage.pin(); + setPageType(metaPage,PageType.META); + byte[] bs = ByteUtil.long2Bytes(5); + byte[] indexFlag = ByteUtil.long2Bytes(Long.MAX_VALUE);//作为索引文件的标志 + try { + metaPage.patchData(4,bs); + metaPage.patchData(12,indexFlag); + } catch (PageException e) { + e.printStackTrace(); + }finally { + //flush + try { + metaPage.flush(); + } catch (FileException e) { + e.printStackTrace(); + } + //unpin + metaPage.unpin(); + } + } + + public boolean isIndexedFile() { + return Long.MAX_VALUE == ByteUtil.bytes2Long(ByteUtil.subByte(this.pageStorage.get(0).getDataBytes(),12,8)); + } + + @Override + public void replace(Map uuidMap) { + //TODO + } + + @Override + synchronized public void insert(long dataHash, long uuid) { + Stack pageStack = new Stack<>(); + Page currentPage = this.pageStorage.get(rootNum); + //pin + currentPage.pin(); + while (getPageType(currentPage) != PageType.LEAF) { + long nextPageNum = 0; + + try { + nextPageNum = queryFirstInternalItem(currentPage,dataHash); + } catch (ItemInNextPageException e) { + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(e.getNextPageNum()); + //pin + currentPage.pin(); + continue; + } + + pageStack.add(nextPageNum); + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(nextPageNum); + //pin + currentPage.pin(); + } + boolean isInsert = false; + while (!isInsert) { + + try { + insertLeafItem(currentPage, dataHash, uuid); + isInsert = true; + //flush + try { + currentPage.flush(); + } catch (FileException e) { + e.printStackTrace(); + } + //unpin + currentPage.unpin(); + } catch (ItemInNextPageException e) { + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(e.getNextPageNum()); + //pin + currentPage.pin(); + } catch (PageFullException e) { + long splitPageNum = 0; + if (pageStack.size() != 0) { + splitPageNum = pageStack.pop(); + } + long rawPageNum = this.getRawPageNum(); + Page rawPage = this.pageStorage.get(rawPageNum); + //pin + rawPage.pin(); + long newKey = insertFullLeaf(currentPage, rawPage, rawPageNum, dataHash, uuid); + //flush + try { + currentPage.flush(); + } catch (FileException eee) { + eee.printStackTrace(); + } + //unpin + currentPage.unpin(); + boolean isInsertInternal = false, stackNeed = true; + Page parent = null; + long parentNum = 0; + while (!isInsertInternal) { + if (stackNeed) { + if (pageStack.size() > 0) { + parentNum = pageStack.pop(); + parent = this.pageStorage.get(parentNum); + //pin + parent.pin(); + } else { + Page root = this.pageStorage.get(rootNum); + //pin + root.pin(); + if (getPageType(root) == PageType.LEAF) { + long rawPageNum0 = this.getRawPageNum(); + Page rawPage0 = this.pageStorage.get(rawPageNum0); + //pin + rawPage0.pin(); + + try { + rawPage0.writeData(root.getDataBytes()); + } catch (PageException pageException) { + //flush + try { + rawPage.flush(); + rawPage0.flush(); + } catch (FileException fileException) { + fileException.printStackTrace(); + } + //unpin + rawPage.unpin(); + rawPage0.unpin(); + root.unpin(); + pageException.printStackTrace(); + } + + //unpin + rawPage.unpin(); + rawPage0.unpin(); + root.unpin(); + initRootAsInternal(newKey, rawPageNum0, rawPageNum); + } else { + + try { + insertInternalItem(root, getMaxKey(rawPage), splitPageNum, rawPageNum, newKey); + //flesh + try { + rawPage.flush(); + root.flush(); + } catch (FileException fileException) { + fileException.printStackTrace(); + } + //unpin + rawPage.unpin(); + root.unpin(); + } catch (PageFullException pageFullException) { + long rawPageNum0 = this.getRawPageNum(); + Page rawPage0 = this.pageStorage.get(rawPageNum0); + long rawPageNum1 = this.getRawPageNum(); + Page rawPage1 = this.pageStorage.get(rawPageNum1); + try { + rawPage0.writeData(root.getDataBytes()); + } catch (PageException pageException) { + //unpin + root.unpin(); + rawPage0.unpin(); + rawPage1.unpin(); + rawPage.unpin(); + pageException.printStackTrace(); + } + + newKey = insertFullInternal(rawPage0, rawPage1, rawPageNum1, getMaxKey(rawPage), splitPageNum, rawPageNum, newKey); + //flush + try { + rawPage0.flush(); + rawPage1.flush(); + rawPage.flush(); + } catch (FileException fileException) { + fileException.printStackTrace(); + } + //unpin + rawPage0.unpin(); + rawPage1.unpin(); + rawPage.unpin(); + root.unpin(); + initRootAsInternal(newKey, rawPageNum0, rawPageNum1); + } catch (ItemInNextPageException itemInNextPageException) { + //unpin + rawPage.unpin(); + root.unpin(); + itemInNextPageException.printStackTrace(); + } + + } + isInsertInternal = true; + isInsert = true; + continue; + } + } else { + stackNeed = true; + } + + try { + insertInternalItem(parent, getMaxKey(rawPage), splitPageNum, rawPageNum, newKey); + isInsertInternal = true; + isInsert = true; + //unpin + parent.unpin(); + rawPage.unpin(); + } catch (PageFullException ee) { + long rawPageNum0 = this.getRawPageNum(); + Page rawPage0 = this.pageStorage.get(rawPageNum0); + //pin + rawPage0.pin(); + newKey = insertFullInternal(parent, rawPage0, rawPageNum0, getMaxKey(rawPage), splitPageNum, rawPageNum, newKey); + rawPageNum = rawPageNum0; + //flush + try { + rawPage.flush(); + parent.flush(); + rawPage0.flush(); + } catch (FileException fileException) { + fileException.printStackTrace(); + } + //unpin + rawPage.unpin(); + parent.unpin(); + rawPage = rawPage0; + splitPageNum = parentNum; + } catch (ItemInNextPageException ee) { + //unpin + parent.unpin(); + parent = this.pageStorage.get(ee.getNextPageNum()); + parent.pin(); + stackNeed = false; + } + + } + } + + } + } + + @Override + synchronized public List query(long keyHash) { + Page currentPage = this.pageStorage.get(rootNum); + //pin + currentPage.pin(); + long nextPageNum = 0; + while (getPageType(currentPage) != PageType.LEAF) { + try { + nextPageNum = queryFirstInternalItem(currentPage, keyHash); + } catch (ItemInNextPageException e) { + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(e.getNextPageNum()); + //pin + currentPage.pin(); + continue; + } + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(nextPageNum); + //pin + currentPage.pin(); + } + int pos = 0; + while (true) { + try { + pos = queryKeyPos(currentPage, keyHash); + break; + } catch (ItemInNextPageException e) { + var nextPageNum0 = getPageNextPage(currentPage); + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(nextPageNum0); + //pin + currentPage.pin(); + } + } + List res = new LinkedList<>(); + long val = 0; + while (true) { + if (pos >= getPageItemNum(currentPage)) { + pos = 0; + var nextPageNum0 = getPageNextPage(currentPage); + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(nextPageNum0); + //pin + currentPage.pin(); + } + long key = getKeyByPosition(currentPage, pos); + if (key == keyHash) { + val = getValByPosition(currentPage, pos); + res.add(val); + } else { + break; + } + pos ++; + } + //unpin + currentPage.unpin(); + return res; + } + + @Override + synchronized public Iterator findFirst(long dataHash) { + Page currentPage = this.pageStorage.get(rootNum); + //pin + currentPage.pin(); + long nextPageNum = 0; + while (getPageType(currentPage) != PageType.LEAF) { + try { + nextPageNum = queryFirstInternalItem(currentPage, dataHash); + } catch (ItemInNextPageException e) { + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(e.getNextPageNum()); + //pin + currentPage.pin(); + continue; + } + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(nextPageNum); + //pin + currentPage.pin(); + } + int position = 0; + while (true) { + try { + position = queryKeyPos(currentPage, dataHash); + break; + } catch (ItemInNextPageException e) { + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(e.getNextPageNum()); + //pin + currentPage.pin(); + continue; + } + } + Iterator res= new BPlusTreeIterator(this,currentPage, position); + currentPage.unpin(); + return res; + } + + @Override + synchronized public Iterator findUpperbound(long dataHash) { + Page currentPage = this.pageStorage.get(rootNum); + //pin + currentPage.pin(); + long nextPageNum = 0; + while (getPageType(currentPage) != PageType.LEAF) { + try { + nextPageNum = queryUpperboundInternal(currentPage, dataHash); + } catch (ItemInNextPageException e) { + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(e.getNextPageNum()); + //pin + currentPage.pin(); + continue; + } + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(nextPageNum); + //pin + currentPage.pin(); + } + int position = 0; + while (true) { + try { + position = queryUpperboundKeyPos(currentPage, dataHash); + break; + } catch (ItemInNextPageException e) { + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(e.getNextPageNum()); + //pin + currentPage.pin(); + } + } + Iterator res= new BPlusTreeIterator(this,currentPage, position); + currentPage.unpin(); + return res; + } + + @Override + synchronized public Iterator findFirst() { + Page currentPage = this.pageStorage.get(rootNum); + //pin + currentPage.pin(); + long nextPageNum = 0; + while (getPageType(currentPage) != PageType.LEAF) { + nextPageNum = getMinChild(currentPage); + //unpin + currentPage.unpin(); + currentPage = this.pageStorage.get(nextPageNum); + //pin + currentPage.pin(); + } + Iterator res= new BPlusTreeIterator(this,currentPage, 0); + currentPage.unpin(); + return res; + } + + /** + * 用于Internal节点找到最小的儿子,服务于findFirst() + * @param page 儿子的页号 + * @return + */ + private long getMinChild(Page page) { + return ByteUtil.bytes2Long(ByteUtil.subByte(page.getDataBytes(),BPlusTreeIndex.PAGE_BEGIN_POSTION + 8,8)); + } + + /** + * LEAF节点的指定key的最前面的位置,服务于findFirst(dataHash) + * @return key的位置 + */ + private int queryKeyPos(Page page, long key) throws ItemInNextPageException { + if ( key > getMaxKey(page) ) { + throw new ItemInNextPageException(1, getPageNextPage(page)); + } + int itemNum = getPageItemNum(page); + int i = 0,j = itemNum; + int temp = (i + j)/2; + while (i < j) { + if (getKeyByPosition(page, temp) > key){ + j = temp; + }else if (getKeyByPosition(page, temp) < key){ + i = temp + 1; + }else { + break; + } + temp = (i + j ) / 2; + } + while (temp > 0 && getKeyByPosition(page, temp-1) >= key){ + temp--; + } + return temp; + } + + /** + * LEAF节点的指定大于key的最前面的位置,服务于findUpperbound(dataHash) + * @param page + * @param key + * @return + */ + private int queryUpperboundKeyPos(Page page, long key) throws ItemInNextPageException { + if ( key >= getMaxKey(page) ) { + throw new ItemInNextPageException(1, getPageNextPage(page)); + } + int itemNum = getPageItemNum(page); + int i = 0,j = itemNum; + int temp = (i + j)/2; + while (i < j) { + if (getKeyByPosition(page, temp) > key){ + j = temp; + }else if (getKeyByPosition(page, temp) < key){ + i = temp + 1; + }else { + break; + } + temp = (i + j ) / 2; + } + while (temp < itemNum - 1){ + if (getKeyByPosition(page, temp) > key) { + break; + } + temp++; + } + return temp; + } + + /** + * 获取一个新页,并元页总页数自动加一 + * @return 新页的页号 + * @throws PageTypeException + */ + private long getRawPageNum() { + Page page = this.pageStorage.get(0); + //pin + page.pin(); + if (getPageType(page) != PageType.META) { + try { + throw new PageTypeException(2); + } catch (PageTypeException e) { + //unpin + page.unpin(); + e.printStackTrace(); + throw new RuntimeException(); + } + } + long res = ByteUtil.bytes2Long(ByteUtil.subByte(page.getDataBytes(),4,8)); + byte[] bs = ByteUtil.long2Bytes(res + 1); + try { + page.patchData(4,bs); + } catch (PageException e) { + e.printStackTrace(); + } finally { + //unpin + page.unpin(); + } + return res; + } + + /** + * 将根,从LEAF节点变为INTERNAL节点 + * @param minKey 通过minKey初始化根节点 + * @param minPageNum 小于minKey的页的页号 + * @param maxPageNum 大于minKey的页的页号 + */ + private void initRootAsInternal(long minKey, long minPageNum, long maxPageNum) { + //pin + Page page = this.pageStorage.get(rootNum); + page.pin(); + setPageType(page, PageType.INTERNAL); + setPageItemNum(page, 2); + setMaxKey(page, Long.MAX_VALUE); + byte[] inserted; + try { + inserted = ByteUtil.byteMerger(ByteUtil.long2Bytes(minKey), ByteUtil.long2Bytes(minPageNum)); + page.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION,inserted); + inserted = ByteUtil.byteMerger(ByteUtil.long2Bytes(Long.MAX_VALUE), ByteUtil.long2Bytes(maxPageNum)); + page.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + 16,inserted); + } catch (PageException e) { + e.printStackTrace(); + } finally { + //flush + try { + page.flush(); + } catch (FileException e) { + e.printStackTrace(); + } + //unpin + page.unpin(); + } + } + + /** + * 将一个页初始化为根,且为LEAF节点 + */ + private void initRootAsLeaf() { + //pin + Page page = this.pageStorage.get(rootNum); + page.pin(); + setPageType(page, PageType.LEAF); + setPageItemNum(page, 0); + setMaxKey(page, Long.MAX_VALUE); + setPageNextPage(page, 0); + //flush + try { + page.flush(); + } catch (FileException e) { + e.printStackTrace(); + } + //unpin + page.unpin(); + } + + /** + *将internal节点的一个key(如果存在重复的key,指第一个)指向的页面替换成新的子页,同时在该条目前插入新的key.主要服务于于分裂操作 + * 如果满了或要插入的值 + *

+     * oldKey - oldPageNum
+     * >>>
+     * newKey - oldPageNum
+     * oldKey - replacePageNum
+     * 
+     * @param page
+     * @param oldKey 待替换的key
+     * @param replacePageNum 替换的新子页号
+     * @param newKey 新插入的key
+     * @param oldPageNum 待替换的页号
+     * @return
+     */
+    private void insertInternalItem(Page page, long oldKey, long oldPageNum, long replacePageNum, long newKey) throws PageFullException, ItemInNextPageException {
+        if ( oldKey > getMaxKey(page) ) {
+            throw new ItemInNextPageException(1, getPageNextPage(page));
+        }
+        int itemNum = getPageItemNum(page);
+        if ( itemNum >= BPlusTreeIndex.MAX_PAGE_ITEM ) {
+            throw new PageFullException(1);
+        }
+        int i = 0,j = itemNum;
+        int temp = (i + j)/2;
+        while (i < j) {
+            if (getKeyByPosition(page, temp) > oldKey){
+                j = temp;
+            }else if (getKeyByPosition(page, temp) < oldKey){
+                i = temp + 1;
+            }else {
+                break;
+            }
+            temp = (i + j ) / 2;
+        }
+        int low = temp, high = temp + 1;
+        boolean isFind = false;
+        while (getKeyByPosition(page, low) == oldKey && low >= 0){
+            if(getValByPosition(page, low) == oldPageNum) {
+                temp = low;
+                isFind = true;
+                break;
+            }
+            low--;
+        }
+        while (getKeyByPosition(page, high) == oldKey && !isFind && high <= (BPlusTreeIndex.MAX_PAGE_ITEM - 1)) {
+            if(getValByPosition(page, high) == oldPageNum) {
+                temp = high;
+                isFind = true;
+                break;
+            }
+            high++;
+        }
+        if (!isFind) {
+            throw new ItemInNextPageException(1,getPageNextPage(page));
+        }
+
+        try {
+            byte[] bytes = ByteUtil.long2Bytes(replacePageNum);
+            page.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + temp * 16 + 8, bytes);
+            byte[] bs = new byte[16];
+            for (int k = itemNum; k > temp; k--) {
+                System.arraycopy(page.getDataBytes(), BPlusTreeIndex.PAGE_BEGIN_POSTION + (k - 1) * 16 , bs, 0, 16);
+                page.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + k * 16 ,bs);
+            }
+            byte[] inserted = ByteUtil.byteMerger(ByteUtil.long2Bytes(newKey), ByteUtil.long2Bytes(oldPageNum));
+            page.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + temp * 16,inserted);
+        } catch (PageException e) {
+            e.printStackTrace();
+        }
+        setPageItemNum(page, itemNum + 1);
+    }
+
+    /**
+     *将一个满的internal节点的一个key(如果存在重复的key,指第一个)指向的页面替换成新的子页,同时在该条目前插入新的key.主要服务于于分裂操作
+     * 
+     * oldKey - oldPageNum
+     * >>>
+     * newKey - oldPageNum
+     * oldKey - replacePageNum
+     * 
+     * @param page
+     * @param newPage
+     * @param newPageNum 新页的页号
+     * @param oldKey 带替换的key
+     * @param oldPageNum 待替换的页号
+     * @param replacePageNum 替换的新子页号
+     * @param newKey 新插入的key
+     * @return 为分裂后新的key
+     */
+    private long insertFullInternal(Page page,  Page newPage, long newPageNum, long oldKey, long oldPageNum, long replacePageNum, long newKey) {
+        if ( oldKey > getMaxKey(page)) {
+            try {
+                throw new ItemInNextPageException(1, getPageNextPage(page));
+            } catch (ItemInNextPageException e) {
+                e.printStackTrace();
+            }
+        }
+        setPageType(newPage, PageType.INTERNAL);
+        setMaxKey(newPage, getMaxKey(page));
+        setPageNextPage(newPage, getPageNextPage(page));
+        setPageNextPage(page, newPageNum);
+        int i = 0,j = BPlusTreeIndex.MAX_PAGE_ITEM;
+        int temp = (i + j)/2;
+        while (i < j) {
+            if (getKeyByPosition(page, temp) > oldKey){
+                j = temp;
+            }else if (getKeyByPosition(page, temp) < oldKey){
+                i = temp + 1;
+            }else {
+                break;
+            }
+            temp = (i + j ) / 2;
+        }
+        int low = temp, high = temp + 1;
+        boolean isFind = false;
+        while (getKeyByPosition(page, low) == oldKey && low >= 0){
+            if(getValByPosition(page, low) == oldPageNum) {
+                temp = low;
+                isFind = true;
+                break;
+            }
+            low--;
+        }
+        while (getKeyByPosition(page, high) == oldKey && !isFind && high <= (BPlusTreeIndex.MAX_PAGE_ITEM - 1)) {
+            if(getValByPosition(page, high) == oldPageNum) {
+                temp = high;
+                isFind = true;
+                break;
+            }
+            high++;
+        }
+        if (!isFind) {
+            try {
+                throw new ItemInNextPageException(1, getPageNextPage(page));
+            } catch (ItemInNextPageException e) {
+                e.printStackTrace();
+            }
+        }
+        //先对半分
+        byte[] bs = new byte[16];
+        try {
+            for (int k = 0; k < BPlusTreeIndex.MAX_PAGE_ITEM/2; k++) {
+                System.arraycopy(page.getDataBytes(), BPlusTreeIndex.PAGE_MID_POSTION + k * 16, bs, 0, 16);
+                newPage.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + k * 16,bs);
+            }
+        }
+        catch (PageException e) {
+            e.printStackTrace();
+        }
+        //在根据temp的取值情况分别处理
+        if (temp < (BPlusTreeIndex.MAX_PAGE_ITEM)/2) {
+            try {
+                for (int k = BPlusTreeIndex.MAX_PAGE_ITEM/2; k > temp; k--) {
+                    System.arraycopy(page.getDataBytes(), BPlusTreeIndex.PAGE_BEGIN_POSTION + (k - 1) * 16, bs, 0, 16);
+                    page.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + k * 16,bs);
+                }
+                page.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + (temp + 1) * 16 + 8,ByteUtil.long2Bytes(replacePageNum));
+                byte[] inserted = ByteUtil.byteMerger(ByteUtil.long2Bytes(newKey), ByteUtil.long2Bytes(oldPageNum));
+                page.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + temp * 16,inserted);
+            } catch (PageException e) {
+                e.printStackTrace();
+            }
+            setPageItemNum(page, BPlusTreeIndex.MAX_PAGE_ITEM/2 + 1);
+            setPageItemNum(newPage,BPlusTreeIndex.MAX_PAGE_ITEM/2);
+            long newKey0 = getKeyByPosition(page, BPlusTreeIndex.MAX_PAGE_ITEM/2);
+            setMaxKey(page, newKey0);
+            return newKey0;
+        } else {
+            temp = temp - BPlusTreeIndex.MAX_PAGE_ITEM/2;
+            try {
+                for (int k = BPlusTreeIndex.MAX_PAGE_ITEM/2; k > temp; k--) {
+                    System.arraycopy(newPage.getDataBytes(), BPlusTreeIndex.PAGE_BEGIN_POSTION + (k - 1) * 16, bs, 0, 16);
+                    newPage.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + k * 16,bs);
+                }
+                newPage.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + (temp + 1) * 16 + 8,ByteUtil.long2Bytes(replacePageNum));
+                byte[] inserted = ByteUtil.byteMerger(ByteUtil.long2Bytes(newKey), ByteUtil.long2Bytes(oldPageNum));
+                newPage.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + temp * 16,inserted);
+            } catch (PageException e) {
+                e.printStackTrace();
+            }
+            setPageItemNum(page, BPlusTreeIndex.MAX_PAGE_ITEM/2);
+            setPageItemNum(newPage,BPlusTreeIndex.MAX_PAGE_ITEM/2 + 1);
+            long newKey0 = getKeyByPosition(page, BPlusTreeIndex.MAX_PAGE_ITEM/2 - 1);
+            setMaxKey(page, newKey0);
+            return newKey0;
+        }
+    }
+
+    /**
+     * 在internal节点中找到第一个keyHash有关的下一个节点,如果不满maxKey检验,则表明搜索的条目不再此页,抛出异常
+     * @param page
+     * @param keyHash
+     * @return 相应儿子节点页号
+     */
+    private long queryFirstInternalItem(Page page,  long keyHash) throws ItemInNextPageException {
+        if ( keyHash > getMaxKey(page) ) {
+            throw  new ItemInNextPageException(1, getPageNextPage(page));
+        }
+        int itemNum = getPageItemNum(page);
+        int i = 0,j = itemNum;
+        int temp = (i + j)/2;
+//        try {
+            while (i < j ) {
+                if (getKeyByPosition(page, temp) > keyHash){
+                    j = temp;
+                }else if (getKeyByPosition(page, temp) < keyHash){
+                    i = temp + 1;
+                }else {
+                    break;
+                }
+                temp = (i + j ) / 2;
+            }
+//        }catch (ArrayIndexOutOfBoundsException e) {
+//            System.out.println("werwe");
+//        }
+        while (temp >= 1 && getKeyByPosition(page, temp-1) >= keyHash){
+            temp--;
+        }
+        return getValByPosition(page, temp);
+    }
+
+    /**
+     * 在internal节点中找到大于keyHash有关的下一个节点,如果不满maxKey检验,则表明搜索的条目不再此页,抛出异常
+     * @param page
+     * @param keyHash
+     * @return 相应儿子节点页号
+     */
+    private long queryUpperboundInternal(Page page,  long keyHash) throws ItemInNextPageException {
+        if ( keyHash > getMaxKey(page) ) {
+            throw  new ItemInNextPageException(1, getPageNextPage(page));
+        }
+        int itemNum = getPageItemNum(page);
+        int i = 0,j = itemNum;
+        int temp = (i + j)/2;
+        while (i < j ) {
+            if (getKeyByPosition(page, temp) > keyHash){
+                j = temp;
+            }else if (getKeyByPosition(page, temp) < keyHash){
+                i = temp + 1;
+            }else {
+                break;
+            }
+            temp = (i + j ) / 2;
+        }
+        while (temp < itemNum && getKeyByPosition(page, temp) <= keyHash){
+            temp++;
+        }
+        return getValByPosition(page, temp);
+    }
+
+    /**
+     * 对树叶节点插入item,相应会抛出页满,条目不在相应页的异常
+     * @param page
+     * @param keyHash
+     * @param uuid
+     * @return
+     */
+    private void insertLeafItem(Page page, long keyHash, long uuid) throws ItemInNextPageException, PageFullException {
+        if ( keyHash > getMaxKey(page)) {
+            throw new ItemInNextPageException(1, getPageNextPage(page));
+        }
+        int itemNum = getPageItemNum(page);
+        if ( itemNum >= BPlusTreeIndex.MAX_PAGE_ITEM ) {
+            throw new PageFullException(1);
+        }
+        int i = 0,j = itemNum;
+        int temp = (i + j)/2;
+        while (i < j ) {
+            long cmp = getKeyByPosition(page, temp);
+//            long cmp0 = getKeyByPosition(page, temp - 1);
+//            long cmp1 = getKeyByPosition(page, temp - 2);
+            if (cmp > keyHash){
+                j = temp;
+            }else if (cmp < keyHash){
+                i = temp + 1;
+            }else {
+                break;
+            }
+            temp = (i + j ) / 2;
+        }
+        try {
+            byte[] bs = new byte[16];
+            for (int k = itemNum; k > temp; k--) {
+                System.arraycopy(page.getDataBytes(), BPlusTreeIndex.PAGE_BEGIN_POSTION + (k - 1) * 16, bs, 0, 16);
+                page.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + k * 16,bs);
+            }
+            byte[] inserted = ByteUtil.byteMerger(ByteUtil.long2Bytes(keyHash), ByteUtil.long2Bytes(uuid));
+            page.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + temp * 16,inserted);
+        } catch (PageException e) {
+            e.printStackTrace();
+        }
+        setPageItemNum(page, itemNum + 1);
+    }
+
+    /**
+     *对一个已经满了的叶插入item
+     * @param page 待分裂已满的页
+     * @param newPage 一个未用过的新的页,用于分裂
+     * @param newPageNum 新页的页号
+     * @param keyHash 分裂时顺便插入的条目的键
+     * @param uuid 分裂时顺便插入的条目的值
+     * @return 为分裂后新的key
+     */
+    private long insertFullLeaf(Page page, Page newPage, long newPageNum, long keyHash, long uuid) {
+        if ( keyHash > getMaxKey(page)) {
+            try {
+                throw new ItemInNextPageException(1, getPageNextPage(page));
+            } catch (ItemInNextPageException e) {
+                e.printStackTrace();
+            }
+        }
+        setPageType(newPage, PageType.LEAF);
+//        setPageItemNum(newPage,(BPlusTreeIndex.MAX_PAGE_ITEM + 1)/2);
+        setMaxKey(newPage, getMaxKey(page));
+        setPageNextPage(newPage, getPageNextPage(page));
+        setPageNextPage(page, newPageNum);
+        int i = 0,j = BPlusTreeIndex.MAX_PAGE_ITEM;
+        int temp = (i + j)/2;
+        while (i < j) {
+            if (getKeyByPosition(page, temp) > keyHash){
+                j = temp;
+            }else if (getKeyByPosition(page, temp) < keyHash){
+                i = temp + 1;
+            }else {
+                break;
+            }
+            temp = (i + j ) / 2;
+        }
+        if (temp < (BPlusTreeIndex.MAX_PAGE_ITEM)/2) {
+            byte[] bs = new byte[16];
+            try {
+                for (int k = 0; k < BPlusTreeIndex.MAX_PAGE_ITEM/2; k++) {
+                    System.arraycopy(page.getDataBytes(), BPlusTreeIndex.PAGE_MID_POSTION + k * 16, bs, 0, 16);
+                    newPage.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + k * 16, bs);
+                }
+            }
+            catch (PageException e) {
+                e.printStackTrace();
+            }
+            try {
+                for (int k = BPlusTreeIndex.MAX_PAGE_ITEM/2; k > temp; k--) {
+                    System.arraycopy(page.getDataBytes(), BPlusTreeIndex.PAGE_BEGIN_POSTION + (k - 1) * 16, bs, 0, 16);
+                    page.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + k * 16,bs);
+                }
+                byte[] inserted = ByteUtil.byteMerger(ByteUtil.long2Bytes(keyHash), ByteUtil.long2Bytes(uuid));
+                page.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + temp * 16,inserted);
+            } catch (PageException e) {
+                e.printStackTrace();
+            }
+            setPageItemNum(newPage,BPlusTreeIndex.MAX_PAGE_ITEM/2);
+            setPageItemNum(page, BPlusTreeIndex.MAX_PAGE_ITEM/2 + 1);
+            long newKey = ByteUtil.bytes2Long(ByteUtil.subByte(page.getDataBytes(), BPlusTreeIndex.PAGE_MID_POSTION,8));
+            setMaxKey(page, newKey);
+            return newKey;
+        } else {
+            byte[] bs = new byte[16];
+            try {
+                boolean isInsert = false;
+                for (int k = 0; k < BPlusTreeIndex.MAX_PAGE_ITEM/2; k++) {
+                    System.arraycopy(page.getDataBytes(), BPlusTreeIndex.PAGE_MID_POSTION + k * 16, bs, 0, 16);
+                    if (keyHash < ByteUtil.bytes2Long(ByteUtil.subByte(bs, 0, 8)) && !isInsert) {
+                        byte[] inserted = ByteUtil.byteMerger(ByteUtil.long2Bytes(keyHash), ByteUtil.long2Bytes(uuid));
+                        newPage.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + k * 16,inserted);
+                        isInsert = true;
+                    }
+                    if (isInsert) {
+                        newPage.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + ( k + 1 ) * 16,bs);
+                    } else {
+                        newPage.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + k * 16,bs);
+                    }
+                }
+                if (!isInsert) {
+                    byte[] inserted = ByteUtil.byteMerger(ByteUtil.long2Bytes(keyHash), ByteUtil.long2Bytes(uuid));
+                    newPage.patchData(BPlusTreeIndex.PAGE_BEGIN_POSTION + BPlusTreeIndex.MAX_PAGE_ITEM/2 * 16,inserted);
+                }
+            }
+            catch (PageException e) {
+                e.printStackTrace();
+            }
+            setPageItemNum(newPage,BPlusTreeIndex.MAX_PAGE_ITEM/2 + 1);
+            setPageItemNum(page, BPlusTreeIndex.MAX_PAGE_ITEM/2);
+            long newKey = ByteUtil.bytes2Long(ByteUtil.subByte(page.getDataBytes(),BPlusTreeIndex.PAGE_MID_POSTION - 16,8));
+            setMaxKey(page, newKey);
+            return newKey;
+        }
+    }
+
+    /**
+     * 通过位置获得key
+     * @param page
+     * @param position
+     * @return
+     */
+    private long getKeyByPosition(Page page, int position) {
+        int pos = BPlusTreeIndex.PAGE_BEGIN_POSTION + 16*position;
+        return ByteUtil.bytes2Long(ByteUtil.subByte(page.getDataBytes(),pos,8));
+    }
+
+    private long getValByPosition(Page page, int position) {
+        int pos = BPlusTreeIndex.PAGE_BEGIN_POSTION + 16*position + 8;
+        return ByteUtil.bytes2Long(ByteUtil.subByte(page.getDataBytes(),pos,8));
+    }
+
+    /**
+     * 设置页的最大key字段
+     * @param page
+     * @param maxKey
+     */
+    private void setMaxKey(Page page, long maxKey) {
+        byte[] bs = ByteUtil.long2Bytes(maxKey);
+        try {
+            page.patchData(16,bs);
+        } catch (PageException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 读取页的最大key字段
+     * @param page
+     * @return
+     */
+    private long getMaxKey(Page page) {
+        return ByteUtil.bytes2Long(ByteUtil.subByte(page.getDataBytes(),16,8));
+    }
+
+    /**
+     * 设置某页的项目数量
+     * @param page
+     * @param itemNum
+     */
+    private void setPageItemNum(Page page, int itemNum) {
+        byte[] bs = ByteUtil.int2Bytes(itemNum);
+        try {
+            page.patchData(4,bs);
+        } catch (PageException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 获得某页的项目数量
+     * @param page
+     * @return
+     */
+    private int getPageItemNum(Page page) {
+        return ByteUtil.bytes2Int(ByteUtil.subByte(page.getDataBytes(),4,4));
+    }
+
+    /**
+     * 设置某页的下一页的页码
+     * @param page
+     * @param pageNum
+     */
+    private void setPageNextPage(Page page, long pageNum) {
+        byte[] bs = ByteUtil.long2Bytes(pageNum);
+        try {
+            page.patchData(8,bs);
+        } catch (PageException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *
+     * @param page
+     * @return 返回下一页的页码
+     */
+    private long getPageNextPage(Page page) {
+        return ByteUtil.bytes2Long(ByteUtil.subByte(page.getDataBytes(),8,8));
+    }
+
+    /**
+     *
+     * @param page 页
+     * @param pageType 设置页的类型
+     */
+    private void setPageType(Page page, PageType pageType) {
+        int temp = pageType.ordinal();
+        byte[] bs = ByteUtil.int2Bytes(temp);
+        try {
+            page.patchData(0,bs);
+        } catch (PageException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     *
+     * @param page  页
+     * @return 页的类型,如果不存在对应页类型,则抛出异常
+     */
+    public PageType getPageType(Page page) {
+        int typeNum = ByteUtil.bytes2Int(ByteUtil.subByte(page.getDataBytes(),0,4));
+        if (typeNum == PageType.INTERNAL.ordinal()) {
+            return  PageType.INTERNAL;
+        } else if (typeNum == PageType.LEAF.ordinal()) {
+            return  PageType.LEAF;
+        } else if (typeNum == PageType.META.ordinal()) {
+            return PageType.META;
+        }
+        try {
+            throw new PageTypeException(1);
+        } catch (PageTypeException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    private class BPlusTreeIterator implements Iterator {
+        BPlusTreeIndex bPlusTreeIndex;
+        Page currentPage;
+        int currentPosition;
+        int currentPageItemNum;
+        boolean isEnd = false;
+
+        public BPlusTreeIterator(BPlusTreeIndex bPlusTreeIndex, Page currentPage, int currentPosition) {
+            this.bPlusTreeIndex = bPlusTreeIndex;
+            this.currentPage = currentPage;
+            //pin 双重pin 传进来之前已经pin过次此来
+            currentPage.pin();
+            this.currentPosition = currentPosition;
+            this.currentPageItemNum = getPageItemNum(currentPage);
+        }
+
+        @Override
+        public boolean hasNext() {
+            synchronized (bPlusTreeIndex) {
+                if (currentPageItemNum > currentPosition) {
+                    return true;
+                } else {
+                    if (!isEnd && getPageNextPage(currentPage) != 0) {
+                        return true;
+                    } else{
+                        return false;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public Pair next() {
+            synchronized (bPlusTreeIndex) {
+                if ( currentPageItemNum > currentPosition ) {
+                    Pair res = new Pair(getKeyByPosition(currentPage,currentPosition), getValByPosition(currentPage,currentPosition));
+                    currentPosition++;
+                    return res;
+                }
+                else {
+                    long nextPageNum = getPageNextPage(currentPage);
+                    if (nextPageNum == 0) {
+                        if (isEnd) {
+                            isEnd = true;
+                            //unpin
+                            currentPage.unpin();
+                        }
+                        return null;
+                    } else {
+                        System.out.println("nextPage*****************");
+                        //unpin
+                        currentPage.unpin();
+                        currentPage = this.bPlusTreeIndex.pageStorage.get(nextPageNum);
+                        //pin
+                        currentPage.pin();
+                        currentPageItemNum = getPageItemNum(currentPage);
+                        currentPosition = 0;
+                        Pair res = new Pair(getKeyByPosition(currentPage,currentPosition), getValByPosition(currentPage,currentPosition));
+                        currentPosition++;
+                        return res;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/net/kaaass/rumbase/index/btree/ByteUtil.java b/src/main/java/net/kaaass/rumbase/index/btree/ByteUtil.java
new file mode 100644
index 0000000..057b474
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/index/btree/ByteUtil.java
@@ -0,0 +1,90 @@
+package net.kaaass.rumbase.index.btree;
+
+/**
+ *下面为byte[]与其他类型有关的转化关系代码内容
+ *
+ * @author 无索魏
+ */
+
+public class ByteUtil {
+
+    /**
+     * 合并byte[]数组 (不改变原数组)
+     * @param byte_1
+     * @param byte_2
+     * @return 合并后的数组
+     */
+    public static byte[] byteMerger(byte[] byte_1, byte[] byte_2){
+        byte[] byte_3 = new byte[byte_1.length+byte_2.length];
+        System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
+        System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
+        return byte_3;
+    }
+
+    /**
+     * 截取byte数组   不改变原数组
+     * @param b 原数组
+     * @param off 偏差值(索引)
+     * @param length 长度
+     * @return 截取后的数组
+     */
+    public static byte[] subByte(byte[] b,int off,int length){
+        byte[] b1 = new byte[length];
+        System.arraycopy(b, off, b1, 0, length);
+        return b1;
+    }
+
+    /**
+     * Long 转 byte[]
+     * @return
+     */
+    public static byte[] long2Bytes(long num) {
+        byte[] byteNum = new byte[8];
+        for (int ix = 0; ix < 8; ++ix) {
+            int offset = 64 - (ix + 1) * 8;
+            byteNum[ix] = (byte) ((num >> offset) & 0xff);
+        }
+        return byteNum;
+    }
+
+    /**
+     * byte[] 转 Long
+     * @return
+     */
+    public static long bytes2Long(byte[] byteNum) {
+        long num = 0;
+        for (int ix = 0; ix < 8; ++ix) {
+            num <<= 8;
+            num |= (byteNum[ix] & 0xff);
+        }
+        return num;
+    }
+
+    /**
+     * byte 转 Int
+     * @param byteNum
+     * @return
+     */
+    public static int bytes2Int(byte[] byteNum) {
+        int num = 0;
+        for (int ix = 0; ix < 4; ++ix) {
+            num <<= 8;
+            num |= (byteNum[ix] & 0xff);
+        }
+        return num;
+    }
+
+    /**
+     * Int 转 byte[]
+     * @param num
+     * @return
+     */
+    public static byte[] int2Bytes(int num) {
+        byte[] byteNum = new byte[4];
+        for (int ix = 0; ix < 4; ++ix) {
+            int offset = 32 - (ix + 1) * 8;
+            byteNum[ix] = (byte) ((num >> offset) & 0xff);
+        }
+        return byteNum;
+    }
+}
diff --git a/src/main/java/net/kaaass/rumbase/index/exception/ItemInNextPageException.java b/src/main/java/net/kaaass/rumbase/index/exception/ItemInNextPageException.java
new file mode 100644
index 0000000..cfa392f
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/index/exception/ItemInNextPageException.java
@@ -0,0 +1,42 @@
+package net.kaaass.rumbase.index.exception;
+
+import lombok.Getter;
+import net.kaaass.rumbase.exception.RumbaseException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 要找的条目不在本页,而下一页
+ * @author 无索魏
+ */
+public class ItemInNextPageException extends RumbaseException {
+
+    @Getter
+    private long nextPageNum;
+
+    /**
+     * 构造Rumbase异常
+     *
+     * @param mainId 主错误号
+     * @param subId  子错误号
+     * @param reason 错误原因
+     */
+    public ItemInNextPageException(int mainId, int subId, String reason) {
+        super(mainId, subId, reason);
+    }
+
+    public static final Map REASONS = new HashMap<>() {{
+        put(1, "页类型不存在");
+    }};
+
+    /**
+     * 条目不在本页
+     *
+     * @param subId 子错误号
+     */
+    public ItemInNextPageException(int subId, long nextPageNum) {
+        super(9004, subId, REASONS.get(subId));
+        this.nextPageNum = nextPageNum;
+    }
+}
diff --git a/src/main/java/net/kaaass/rumbase/index/exception/PageFullException.java b/src/main/java/net/kaaass/rumbase/index/exception/PageFullException.java
new file mode 100644
index 0000000..e26f94a
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/index/exception/PageFullException.java
@@ -0,0 +1,36 @@
+package net.kaaass.rumbase.index.exception;
+
+import net.kaaass.rumbase.exception.RumbaseException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 页已经满了,无法进行原先的插入操作
+ * @author 无索魏
+ */
+public class PageFullException extends RumbaseException {
+    /**
+     * 构造Rumbase异常
+     *
+     * @param mainId 主错误号
+     * @param subId  子错误号
+     * @param reason 错误原因
+     */
+    public PageFullException(int mainId, int subId, String reason) {
+        super(mainId, subId, reason);
+    }
+
+    public static final Map REASONS = new HashMap<>() {{
+        put(1, "页已满,无法插入");
+    }};
+
+    /**
+     * 索引不存在
+     *
+     * @param subId 子错误号
+     */
+    public PageFullException(int subId) {
+        super(9005, subId, REASONS.get(subId));
+    }
+}
diff --git a/src/main/java/net/kaaass/rumbase/index/exception/PageTypeException.java b/src/main/java/net/kaaass/rumbase/index/exception/PageTypeException.java
new file mode 100644
index 0000000..d0942db
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/index/exception/PageTypeException.java
@@ -0,0 +1,37 @@
+package net.kaaass.rumbase.index.exception;
+
+import net.kaaass.rumbase.exception.RumbaseException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author 无索魏
+ */
+public class PageTypeException extends RumbaseException {
+    /**
+     * 构造Rumbase异常
+     *
+     * @param mainId 主错误号
+     * @param subId  子错误号
+     * @param reason 错误原因
+     */
+    public PageTypeException(int mainId, int subId, String reason) {
+        super(mainId, subId, reason);
+    }
+
+    public static final Map REASONS = new HashMap<>() {{
+        put(1, "页类型不存在");
+        put(2, "非MEAT页不能读取文件总页数");
+    }};
+
+    /**
+     * 索引不存在
+     *
+     * @param subId 子错误号
+     */
+    public PageTypeException(int subId) {
+        super(9003, subId, REASONS.get(subId));
+    }
+}
diff --git a/src/main/java/net/kaaass/rumbase/page/mock/MockPageStorage.java b/src/main/java/net/kaaass/rumbase/page/mock/MockPageStorage.java
index 9f48cfc..d995199 100644
--- a/src/main/java/net/kaaass/rumbase/page/mock/MockPageStorage.java
+++ b/src/main/java/net/kaaass/rumbase/page/mock/MockPageStorage.java
@@ -17,8 +17,8 @@ public class MockPageStorage implements PageStorage {
     public MockPageStorage(String filepath) throws FileException {
         this.filepath = filepath;
         pageMap = new HashMap<>();
-        this.fakeFile = new byte[1024 * 4 * 20];
-        for (int i = 0; i < 20; i++) {
+        this.fakeFile = new byte[1024 * 4 * 10000];
+        for (int i = 0; i < 10000; i++) {
             for (int j = 0; j < 1024 * 4; j++) {
                 this.fakeFile[1024 * 4 * i + j] = (byte) i;
             }
diff --git a/src/test/java/net/kaaass/rumbase/index/BTreeTest.java b/src/test/java/net/kaaass/rumbase/index/BTreeTest.java
new file mode 100644
index 0000000..e9ab737
--- /dev/null
+++ b/src/test/java/net/kaaass/rumbase/index/BTreeTest.java
@@ -0,0 +1,188 @@
+package net.kaaass.rumbase.index;
+
+import junit.framework.TestCase;
+import lombok.extern.slf4j.Slf4j;
+import net.kaaass.rumbase.index.exception.IndexAlreadyExistException;
+import net.kaaass.rumbase.index.exception.IndexNotFoundException;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.Random;
+
+/**
+ * 对B+树进行测试
+ *
+ * @author DoctorWei1314
+ * @see net.kaaass.rumbase.index.BTreeTest
+ */
+@Slf4j
+public class BTreeTest extends TestCase {
+    public static final String fileDir = "build/";
+
+    /**
+     * 测试索引的插入与第一个迭代器功能
+     */
+    public void testInsert() {
+        Index testIndex = null;
+        try {
+            new File(fileDir + "BtreetestInsert$id").deleteOnExit();
+            testIndex = Index.createEmptyIndex(fileDir + "BtreetestInsert$id");
+        } catch (IndexAlreadyExistException e) {
+            log.error("Exception Error :", e);
+        }
+
+        for (int i = 4000; i >= 0; i--) {
+
+            assert testIndex != null;
+            testIndex.insert(i, new Random().nextLong());
+
+            testIndex.insert(i, new Random().nextLong());
+        }
+
+        // 测试数据是否符合预期
+        int cnt = 0;
+        for (var pair : testIndex) {
+            assertEquals(cnt / 2,
+                    pair.getKey());
+            // log.debug("{}", pair);
+            cnt++;
+        }
+    }
+
+    /**
+     * 测试不按顺寻key插入的情况
+     */
+    public void testInsertRandomKey() {
+        Index testIndex = null;
+        try {
+            new File(fileDir + "testInsertRandomKey$id").deleteOnExit();
+            testIndex = Index.createEmptyIndex(fileDir + "testInsertRandomKey$id");
+        } catch (IndexAlreadyExistException e) {
+            log.error("Exception Error :", e);
+        }
+
+        for (int i = 4000; i >= 0; i--) {
+
+            assert testIndex != null;
+            testIndex.insert(i, new Random().nextLong());
+
+            testIndex.insert(4000 - i, new Random().nextLong());
+        }
+
+        // 测试数据是否符合预期
+        int cnt = 0;
+        for (var pair : testIndex) {
+            assertEquals(cnt / 2,
+                    pair.getKey());
+            // log.debug("{}", pair);
+            cnt++;
+        }
+    }
+
+    /**
+     * 测试索引的查询功能
+     */
+    public void testQuery() {
+        Index testIndex = null;
+
+        try {
+            new File(fileDir + "BtreetestQuery$id").deleteOnExit();
+            testIndex = Index.createEmptyIndex(fileDir + "BtreetestQuery$id");
+        } catch (IndexAlreadyExistException e) {
+            log.error("Exception Error :", e);
+        }
+
+        // 倒序添加若干随机数据
+        for (int i = 2000; i > 0; i--) {
+            var rand = new Random().nextLong();
+
+            assert testIndex != null;
+            testIndex.insert(i, rand);
+
+            testIndex.insert(i, new Random().nextLong());
+        }
+
+        // 打印当前索引情况
+        for (var pair : testIndex) {
+            log.debug("{}", pair);
+        }
+
+        // 测试 findFirst 方法
+        // keyHash在内的迭代器 1122->334455
+        Iterator it1 = testIndex.findFirst(3);
+        assertTrue(it1.hasNext());
+        assertEquals(3, it1.next().getKey());
+        assertEquals(3, it1.next().getKey());
+        assertEquals(4, it1.next().getKey());
+
+        // 测试 findUpperbound 方法
+        // 不包括keyHash在内的迭代器 112233->4455
+        Iterator it2 = testIndex.findUpperbound(3);
+        assertTrue(it2.hasNext());
+        assertEquals(4, it2.next().getKey());
+        assertEquals(4, it2.next().getKey());
+        assertEquals(5, it2.next().getKey());
+
+        // 测试 query 方法
+        var results = testIndex.query(4);
+        System.out.println(results);
+//        assertTrue(results.contains(standardRand.get(2)));
+//        assertTrue(results.contains(standardRand.get(2 + 1)));
+    }
+
+    /**
+     * 测试multiKey索引的查询功能
+     */
+    public void testMultiKeyQuery() {
+        Index testIndex = null;
+
+        try {
+            new File(fileDir + "testMultiKeyQuery$id").deleteOnExit();
+            testIndex = Index.createEmptyIndex(fileDir + "testMultiKeyQuery$id");
+        } catch (IndexAlreadyExistException e) {
+            log.error("Exception Error :", e);
+        }
+
+        // 倒序添加若干随机数据
+        for (int i = 30; i > 0; i--) {
+            assert testIndex != null;
+            for (int j = 0; j < 50; j++) {
+                testIndex.insert(i, new Random().nextLong());
+                testIndex.insert(i, new Random().nextLong());
+            }
+        }
+
+        int y = 0;
+        // 打印当前索引情况
+        for (var pair : testIndex) {
+            log.debug("{}{}", y % 100, pair);
+            y++;
+        }
+
+        // 测试 query 方法
+        var results = testIndex.query(4);
+        System.out.println(results);
+
+        // 测试 findFirst 方法
+        // keyHash在内的迭代器 1122->334455
+        Iterator it1 = testIndex.findFirst(3);
+        assertTrue(it1.hasNext());
+        assertEquals(3, it1.next().getKey());
+        assertEquals(3, it1.next().getKey());
+        assertEquals(3, it1.next().getKey());
+
+        // 测试 findUpperbound 方法
+        // 不包括keyHash在内的迭代器 112233->4455
+        Iterator it2 = testIndex.findUpperbound(3);
+        assertTrue(it2.hasNext());
+        assertEquals(4, it2.next().getKey());
+        assertEquals(4, it2.next().getKey());
+        assertEquals(4, it2.next().getKey());
+        for (int i = 0; i < 3; i++) {
+            log.debug("{}", it2.next().getUuid());
+        }
+
+//        assertTrue(results.contains(standardRand.get(2)));
+//        assertTrue(results.contains(standardRand.get(2 + 1)));
+    }
+}
diff --git a/src/test/java/net/kaaass/rumbase/index/ConcurrentIndexTest.java b/src/test/java/net/kaaass/rumbase/index/ConcurrentIndexTest.java
new file mode 100644
index 0000000..c06af40
--- /dev/null
+++ b/src/test/java/net/kaaass/rumbase/index/ConcurrentIndexTest.java
@@ -0,0 +1,117 @@
+package net.kaaass.rumbase.index;
+
+import junit.framework.TestCase;
+import lombok.extern.slf4j.Slf4j;
+import net.kaaass.rumbase.index.exception.IndexAlreadyExistException;
+
+import java.io.File;
+import java.util.Random;
+
+@Slf4j
+public class ConcurrentIndexTest extends TestCase {
+    public static final String fileDir = "build/";
+
+    /**
+     * 测试索引的并发功能
+     */
+    public void test() {
+        Index testIndex = null;
+        try {
+            new File(fileDir + "test$id").deleteOnExit();
+            testIndex = Index.createEmptyIndex(fileDir + "test$id");
+        } catch (IndexAlreadyExistException e) {
+            log.error("Exception Error :", e);
+        }
+
+        Index finalTestIndex = testIndex;
+        Thread thread = new Thread(() -> {
+            for (int i = 4000; i >= 0; i--) {
+
+                assert finalTestIndex != null;
+
+                finalTestIndex.insert(i, new Random().nextLong());
+            }
+        });
+
+        thread.start();
+
+        for (int i = 4000; i >= 0; i--) {
+
+            assert testIndex != null;
+            testIndex.insert(i, new Random().nextLong());
+        }
+
+        try {
+            thread.join();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        // 测试数据是否符合预期
+        int cnt = 0;
+        for (var pair : testIndex) {
+            assertEquals(cnt / 2,
+                    pair.getKey());
+            //log.debug("{}", pair);
+            cnt++;
+        }
+    }
+
+    /**
+     * 测试索引的更复杂的并发功能
+     */
+    public void testComplex() {
+        Index testIndex = null;
+        try {
+            new File(fileDir + "ConcurrenttestComplex$id").deleteOnExit();
+            testIndex = Index.createEmptyIndex(fileDir + "ConcurrenttestComplex$id");
+        } catch (IndexAlreadyExistException e) {
+            log.error("Exception Error :", e);
+        }
+
+        Index finalTestIndex = testIndex;
+        Thread thread1 = new Thread(() -> {
+            for (int i = 4000; i >= 0; i--) {
+
+                assert finalTestIndex != null;
+
+                finalTestIndex.insert(i, new Random().nextLong());
+            }
+        });
+
+        thread1.start();
+
+        Thread thread2 = new Thread(() -> {
+            for (int i = 0; i <= 4000; i++) {
+
+                assert finalTestIndex != null;
+
+                finalTestIndex.insert(i, new Random().nextLong());
+            }
+        });
+
+        thread2.start();
+
+        for (int i = 4000; i >= 0; i--) {
+
+            assert testIndex != null;
+            testIndex.insert(i, new Random().nextLong());
+        }
+
+        try {
+            thread1.join();
+            thread2.join();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        // 测试数据是否符合预期
+        int cnt = 0;
+        for (var pair : testIndex) {
+            assertEquals(cnt / 3,
+                    pair.getKey());
+            //log.debug("{}", pair);
+            cnt++;
+        }
+    }
+}
diff --git a/src/test/java/net/kaaass/rumbase/index/IndexTest.java b/src/test/java/net/kaaass/rumbase/index/IndexTest.java
index 27a89d4..597d7aa 100644
--- a/src/test/java/net/kaaass/rumbase/index/IndexTest.java
+++ b/src/test/java/net/kaaass/rumbase/index/IndexTest.java
@@ -5,8 +5,10 @@
 import net.kaaass.rumbase.index.exception.IndexAlreadyExistException;
 import net.kaaass.rumbase.index.exception.IndexNotFoundException;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Random;
 
 /**
@@ -17,31 +19,7 @@
  */
 @Slf4j
 public class IndexTest extends TestCase {
-    /**
-     * 测试索引对象管理与拿取
-     */
-    public void testIndexManagement() {
-        // 测试索引是否存在,表示student表的id字段索引,table_name$field_name
-        assertFalse("don't exists such a index", Index.exists("student$id"));
-
-        // 创建一个空索引,如果已经存在,则抛出异常
-        try {
-            Index.createEmptyIndex("student$id");
-            Index.createEmptyIndex("student$name");
-            Index.createEmptyIndex("student$score");
-            Index.createEmptyIndex("student$score");
-        } catch (IndexAlreadyExistException e) {
-            log.error("Exception Error :", e);
-        }
-
-        // 拿到这个索引,若没有则抛出异常
-        try {
-            Index.getIndex("student$id");
-            Index.getIndex("employee$id");
-        } catch (IndexNotFoundException e) {
-            log.error("Exception Error :", e);
-        }
-    }
+    public static final String fileDir = "build/";
 
     /**
      * 测试索引的插入与第一个迭代器功能
@@ -50,7 +28,8 @@ public void testInsert() {
         Index testIndex = null;
         var standardRand = new ArrayList();
         try {
-            testIndex = Index.createEmptyIndex("testInsert$id");
+            new File(fileDir + "testInsert$id").deleteOnExit();
+            testIndex = Index.createEmptyIndex(fileDir + "testInsert$id");
         } catch (IndexAlreadyExistException e) {
             log.error("Exception Error :", e);
         }
@@ -61,9 +40,6 @@ public void testInsert() {
             standardRand.add(rand);
             assert testIndex != null;
             testIndex.insert(i, rand);
-
-            standardRand.add(rand = new Random().nextLong());
-            testIndex.insert(i, rand);
         }
 
         // 测试数据是否符合预期
@@ -83,7 +59,8 @@ public void testQuery() {
         var standardRand = new ArrayList();
 
         try {
-            testIndex = Index.createEmptyIndex("testQuery$id");
+            new File(fileDir + "testQuery$id").deleteOnExit();
+            testIndex = Index.createEmptyIndex(fileDir + "testQuery$id");
         } catch (IndexAlreadyExistException e) {
             log.error("Exception Error :", e);
         }
@@ -109,15 +86,23 @@ public void testQuery() {
         // keyHash在内的迭代器 1122->334455
         Iterator it1 = testIndex.findFirst(3);
         assertTrue(it1.hasNext());
-        assertEquals(standardRand.get(2 * 2).longValue(), it1.next().getUuid());
-        assertEquals(standardRand.get(2 * 2 + 1).longValue(), it1.next().getUuid());
+        var expected = List.of(
+                standardRand.get(2 * 2),
+                standardRand.get(2 * 2 + 1)
+        );
+        assertTrue(expected.contains(it1.next().getUuid()));
+        assertTrue(expected.contains(it1.next().getUuid()));
 
         // 测试 findUpperbound 方法
         // 不包括keyHash在内的迭代器 112233->4455
         Iterator it2 = testIndex.findUpperbound(3);
         assertTrue(it2.hasNext());
-        assertEquals(standardRand.get(2).longValue(), it2.next().getUuid());
-        assertEquals(standardRand.get(2 + 1).longValue(), it2.next().getUuid());
+        expected = List.of(
+                standardRand.get(2),
+                standardRand.get(2 + 1)
+        );
+        assertTrue(expected.contains(it2.next().getUuid()));
+        assertTrue(expected.contains(it2.next().getUuid()));
 
         // 测试 query 方法
         var results = testIndex.query(4);

From 770b6ac98ed19906100c78e1dac843098b0e876e Mon Sep 17 00:00:00 2001
From: KAAAsS 
Date: Sat, 16 Jan 2021 01:37:14 +0800
Subject: [PATCH 13/18] =?UTF-8?q?[+]=E5=A2=9E=E5=8A=A0=E8=AF=AD=E5=8F=A5?=
 =?UTF-8?q?=E8=A7=A3=E6=9E=90=E6=A8=A1=E5=9D=97=20(#14)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* 完成Select语句的语法树定义

* 完成Insert语句的语法树定义

* 完成Update语句的语法树定义

* 完成Delete语句的语法树定义

* 完成Create table语句的语法树定义

* 完成Create index语句的语法树定义

* 格式化代码

* 完成基本解析框架,实现Insert语句解析

* 实现CreateTable语句解析

* 实现CreateIndex语句解析

* 包移动

* 修正列名获取问题

* 完成表达式求值

* 完成Delete语句解析

* 完成Update语句解析

* 建表语句允许not null

* 完成Select语句解析

* 整理代码
---
 build.gradle                                  |   2 +
 .../rumbase/parse/ColumnIdentifier.java       |  18 +
 .../rumbase/parse/ConditionExpression.java    | 430 ++++++++++++++++++
 .../kaaass/rumbase/parse/ISqlStatement.java   |   9 +
 .../net/kaaass/rumbase/parse/SqlParser.java   |  63 +++
 .../parse/exception/EvaluationException.java  |  30 ++
 .../parse/exception/SqlSyntaxException.java   |  30 ++
 .../parser/CreateIndexStatementParser.java    |  35 ++
 .../parser/CreateTableStatementParser.java    |  55 +++
 .../parse/parser/DeleteStatementParser.java   |  33 ++
 .../parse/parser/InsertStatementParser.java   |  51 +++
 .../parse/parser/JsqlpStatementParser.java    |  12 +
 .../rumbase/parse/parser/ParserUtil.java      |  45 ++
 .../parse/parser/SelectStatementParser.java   | 119 +++++
 .../parse/parser/UpdateStatementParser.java   |  47 ++
 .../parse/stmt/CreateIndexStatement.java      |  33 ++
 .../parse/stmt/CreateTableStatement.java      |  68 +++
 .../rumbase/parse/stmt/DeleteStatement.java   |  26 ++
 .../rumbase/parse/stmt/InsertStatement.java   |  33 ++
 .../rumbase/parse/stmt/SelectStatement.java   |  94 ++++
 .../rumbase/parse/stmt/UpdateStatement.java   |  39 ++
 .../parse/ConditionExpressionTest.java        |  54 +++
 .../net/kaaass/rumbase/parse/SqlTest.java     |  98 ++++
 .../CreateIndexStatementParserTest.java       |  51 +++
 .../CreateTableStatementParserTest.java       |  50 ++
 .../parser/DeleteStatementParserTest.java     |  42 ++
 .../parser/InsertStatementParserTest.java     |  57 +++
 .../parser/SelectStatementParserTest.java     |  35 ++
 .../parser/UpdateStatementParserTest.java     |  56 +++
 29 files changed, 1715 insertions(+)
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/ColumnIdentifier.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/ConditionExpression.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/ISqlStatement.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/SqlParser.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/exception/EvaluationException.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/exception/SqlSyntaxException.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/parser/CreateIndexStatementParser.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/parser/CreateTableStatementParser.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/parser/DeleteStatementParser.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/parser/InsertStatementParser.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/parser/JsqlpStatementParser.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/parser/ParserUtil.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/parser/SelectStatementParser.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/parser/UpdateStatementParser.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/CreateIndexStatement.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/CreateTableStatement.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/DeleteStatement.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/InsertStatement.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/SelectStatement.java
 create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/UpdateStatement.java
 create mode 100644 src/test/java/net/kaaass/rumbase/parse/ConditionExpressionTest.java
 create mode 100644 src/test/java/net/kaaass/rumbase/parse/SqlTest.java
 create mode 100644 src/test/java/net/kaaass/rumbase/parse/parser/CreateIndexStatementParserTest.java
 create mode 100644 src/test/java/net/kaaass/rumbase/parse/parser/CreateTableStatementParserTest.java
 create mode 100644 src/test/java/net/kaaass/rumbase/parse/parser/DeleteStatementParserTest.java
 create mode 100644 src/test/java/net/kaaass/rumbase/parse/parser/InsertStatementParserTest.java
 create mode 100644 src/test/java/net/kaaass/rumbase/parse/parser/SelectStatementParserTest.java
 create mode 100644 src/test/java/net/kaaass/rumbase/parse/parser/UpdateStatementParserTest.java

diff --git a/build.gradle b/build.gradle
index b0f85f1..15a5525 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,6 +24,8 @@ dependencies {
     // Slf4j
     compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30'
     compile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.30'
+    // JSQLParser
+    compile 'com.github.jsqlparser:jsqlparser:4.0'
     // JUnit
     testCompile group: 'junit', name: 'junit', version: '4.12'
 }
diff --git a/src/main/java/net/kaaass/rumbase/parse/ColumnIdentifier.java b/src/main/java/net/kaaass/rumbase/parse/ColumnIdentifier.java
new file mode 100644
index 0000000..5cc462c
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/parse/ColumnIdentifier.java
@@ -0,0 +1,18 @@
+package net.kaaass.rumbase.parse;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * 操作、标志字段的标识符
+ *
+ * @author kaaass
+ */
+@Data
+@AllArgsConstructor
+public class ColumnIdentifier {
+
+    private String tableName;
+
+    private String fieldName;
+}
diff --git a/src/main/java/net/kaaass/rumbase/parse/ConditionExpression.java b/src/main/java/net/kaaass/rumbase/parse/ConditionExpression.java
new file mode 100644
index 0000000..60ea8a4
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/parse/ConditionExpression.java
@@ -0,0 +1,430 @@
+package net.kaaass.rumbase.parse;
+
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import net.kaaass.rumbase.parse.exception.EvaluationException;
+import net.kaaass.rumbase.parse.parser.ParserUtil;
+import net.sf.jsqlparser.expression.*;
+import net.sf.jsqlparser.expression.operators.arithmetic.*;
+import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
+import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
+import net.sf.jsqlparser.expression.operators.relational.*;
+import net.sf.jsqlparser.schema.Column;
+import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
+
+import java.util.*;
+
+/**
+ * 条件表达式操作相关
+ *
+ * @author kaaass
+ */
+@RequiredArgsConstructor
+public class ConditionExpression {
+
+    public static double PRECISION = 0.00001;
+
+    @NonNull
+    private final Expression expression;
+
+    @NonNull
+    private final String defaultTableName;
+
+    private Map paramColumn = null;
+
+    /**
+     * 根据参数列表求值
+     *
+     * @param paramMap 参数列表,其中参数必须是原生类型的装箱对象,如Integer、String
+     */
+    public boolean evaluate(Map paramMap) {
+        updateParam();
+        var parser = new DeParser(paramMap);
+        expression.accept(parser);
+        var result = parser.getResult();
+        if (result instanceof Double) {
+            return Math.abs((Double) result) > PRECISION;
+        } else if (result instanceof Long) {
+            return ((Long) result) != 0;
+        } else if (result instanceof Boolean) {
+            return (boolean) result;
+        } else if (result instanceof String) {
+            return !"".equals(result);
+        }
+        throw new EvaluationException(1);
+    }
+
+    /**
+     * 获得表达式求值需要的参数
+     */
+    public List getParams() {
+        updateParam();
+        return List.copyOf(paramColumn.values());
+    }
+
+    private void updateParam() {
+        if (paramColumn == null) {
+            paramColumn = new HashMap<>();
+            expression.accept(new ExpressionVisitorAdapter() {
+
+                @Override
+                public void visit(Column column) {
+                    paramColumn.put(column, ParserUtil.mapColumn(column, defaultTableName));
+                }
+            });
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ConditionExpression{" +
+                "expression=" + expression +
+                ", defaultTableName='" + defaultTableName + '\'' +
+                '}';
+    }
+
+    /**
+     * 实现表达式执行的具体访问者
+     */
+    @RequiredArgsConstructor
+    class DeParser extends ExpressionDeParser {
+
+        /*
+         * 类型:数字暂时都转为Double、Boolean、String
+         */
+        Stack stack = new Stack<>();
+
+        @NonNull
+        Map params;
+
+        /**
+         * 获得表达式求值结果
+         */
+        public Object getResult() {
+            if (stack.size() != 1) {
+                throw new EvaluationException(2);
+            }
+            return stack.get(0);
+        }
+
+        @Override
+        public void visit(Addition addition) {
+            super.visit(addition);
+            // +
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Double;
+            assert b instanceof Double;
+            stack.push((double) a + (double) b);
+        }
+
+        @Override
+        public void visit(Division division) {
+            super.visit(division);
+            // /
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Double;
+            assert b instanceof Double;
+            stack.push((double) a / (double) b);
+        }
+
+        @Override
+        public void visit(Multiplication multiplication) {
+            super.visit(multiplication);
+            // *
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Double;
+            assert b instanceof Double;
+            stack.push((double) a * (double) b);
+        }
+
+        @Override
+        public void visit(Subtraction subtraction) {
+            super.visit(subtraction);
+            // -
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Double;
+            assert b instanceof Double;
+            stack.push((double) a - (double) b);
+        }
+
+        @Override
+        public void visit(SignedExpression signedExpression) {
+            super.visit(signedExpression);
+            //
+            var a = stack.pop();
+            assert a instanceof Double;
+            if (signedExpression.getSign() == '+') {
+                stack.push(a);
+            } else if (signedExpression.getSign() == '-') {
+                stack.push(-(double) a);
+            } else if (signedExpression.getSign() == '~') {
+                stack.push((double) (~(long) a));
+            }
+        }
+
+        @Override
+        public void visit(AndExpression andExpression) {
+            super.visit(andExpression);
+            // and
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Boolean;
+            assert b instanceof Boolean;
+            stack.push((boolean) a && (boolean) b);
+        }
+
+        @Override
+        public void visit(NotExpression notExpr) {
+            super.visit(notExpr);
+            // !
+            var a = stack.pop();
+            assert a instanceof Boolean;
+            stack.push(!(boolean) a);
+        }
+
+        @Override
+        public void visit(OrExpression orExpression) {
+            super.visit(orExpression);
+            // or
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Boolean;
+            assert b instanceof Boolean;
+            stack.push((boolean) a || (boolean) b);
+        }
+
+        @Override
+        public void visit(Between between) {
+            super.visit(between);
+            // between
+            var c = stack.pop();
+            var b = stack.pop();
+            var a = stack.pop();
+            Comparator cmp = Comparator.naturalOrder();
+            stack.push(cmp.compare(a, b) <= 0 && cmp.compare(b, c) < 0);
+        }
+
+        @Override
+        public void visit(GreaterThan greaterThan) {
+            super.visit(greaterThan);
+            // >
+            var b = stack.pop();
+            var a = stack.pop();
+            Comparator cmp = Comparator.naturalOrder();
+            stack.push(cmp.compare(a, b) > 0);
+        }
+
+        @Override
+        public void visit(GreaterThanEquals greaterThanEquals) {
+            super.visit(greaterThanEquals);
+            // >=
+            var b = stack.pop();
+            var a = stack.pop();
+            if (a instanceof Double && b instanceof Double) {
+                stack.push((double) a - (double) b > -PRECISION);
+            } else {
+                Comparator cmp = Comparator.naturalOrder();
+                stack.push(cmp.compare(a, b) >= 0);
+            }
+        }
+
+        @Override
+        public void visit(MinorThan minorThan) {
+            super.visit(minorThan);
+            // <
+            var b = stack.pop();
+            var a = stack.pop();
+            Comparator cmp = Comparator.naturalOrder();
+            stack.push(cmp.compare(a, b) < 0);
+        }
+
+        @Override
+        public void visit(MinorThanEquals minorThanEquals) {
+            super.visit(minorThanEquals);
+            // <=
+            var b = stack.pop();
+            var a = stack.pop();
+            if (a instanceof Double && b instanceof Double) {
+                stack.push((double) a - (double) b < PRECISION);
+            } else {
+                Comparator cmp = Comparator.naturalOrder();
+                stack.push(cmp.compare(a, b) <= 0);
+            }
+        }
+
+        @Override
+        public void visit(EqualsTo equalsTo) {
+            super.visit(equalsTo);
+            // =
+            var b = stack.pop();
+            var a = stack.pop();
+            if (a instanceof Double && b instanceof Double) {
+                stack.push(Math.abs((double) a - (double) b) < PRECISION);
+            } else {
+                stack.push(a.equals(b));
+            }
+        }
+
+        @Override
+        public void visit(NotEqualsTo notEqualsTo) {
+            super.visit(notEqualsTo);
+            // !=
+            var b = stack.pop();
+            var a = stack.pop();
+            if (a instanceof Double && b instanceof Double) {
+                stack.push(Math.abs((double) a - (double) b) >= PRECISION);
+            } else {
+                stack.push(!a.equals(b));
+            }
+        }
+
+        @Override
+        public void visit(IntegerDivision division) {
+            super.visit(division);
+            // //
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Double;
+            assert b instanceof Double;
+            stack.push((double) ((long) a / (long) b));
+        }
+
+        @Override
+        public void visit(BitwiseRightShift expr) {
+            super.visit(expr);
+            // >>
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Double;
+            assert b instanceof Double;
+            stack.push((double) ((long) a >> (long) b));
+        }
+
+        @Override
+        public void visit(BitwiseLeftShift expr) {
+            super.visit(expr);
+            // <<
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Double;
+            assert b instanceof Double;
+            stack.push((double) ((long) a << (long) b));
+        }
+
+        @Override
+        public void visit(BitwiseAnd bitwiseAnd) {
+            super.visit(bitwiseAnd);
+            // &
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Double;
+            assert b instanceof Double;
+            stack.push((double) ((long) a & (long) b));
+        }
+
+        @Override
+        public void visit(BitwiseOr bitwiseOr) {
+            super.visit(bitwiseOr);
+            // |
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Double;
+            assert b instanceof Double;
+            stack.push((double) ((long) a | (long) b));
+        }
+
+        @Override
+        public void visit(BitwiseXor bitwiseXor) {
+            super.visit(bitwiseXor);
+            // ^
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Double;
+            assert b instanceof Double;
+            stack.push((double) ((long) a ^ (long) b));
+        }
+
+        @Override
+        public void visit(Modulo modulo) {
+            super.visit(modulo);
+            // %
+            var b = stack.pop();
+            var a = stack.pop();
+            assert a instanceof Double;
+            assert b instanceof Double;
+            stack.push((double) ((long) a % (long) b));
+        }
+
+        @Override
+        public void visit(DoubleValue doubleValue) {
+            super.visit(doubleValue);
+            //
+            stack.push(doubleValue.getValue());
+        }
+
+        @Override
+        public void visit(HexValue hexValue) {
+            super.visit(hexValue);
+            //
+            stack.push(Integer.valueOf(hexValue.getValue(), 16).doubleValue());
+        }
+
+        @Override
+        public void visit(LongValue longValue) {
+            super.visit(longValue);
+            //
+            stack.push((double) longValue.getValue());
+        }
+
+        @Override
+        public void visit(NullValue nullValue) {
+            super.visit(nullValue);
+            //
+            stack.push(null);
+        }
+
+        @Override
+        public void visit(StringValue stringValue) {
+            super.visit(stringValue);
+            //
+            stack.push(stringValue.getValue());
+        }
+
+        @Override
+        public void visit(Column tableColumn) {
+            super.visit(tableColumn);
+            //
+            var key = paramColumn.get(tableColumn);
+            var value = params.get(key);
+            if (value instanceof Number) {
+                value = ((Number) value).doubleValue();
+            }
+            stack.push(value);
+        }
+
+        @Override
+        public void visit(IsNullExpression isNullExpression) {
+            super.visit(isNullExpression);
+            //
+            stack.push(stack.pop() == null);
+        }
+
+        @Override
+        public void visit(IsBooleanExpression isBooleanExpression) {
+            super.visit(isBooleanExpression);
+            //
+            stack.push(stack.pop() instanceof Boolean);
+        }
+
+        @Override
+        public void visit(Concat concat) {
+            super.visit(concat);
+            //
+            stack.push(stack.pop().toString() + stack.pop().toString());
+        }
+    }
+}
diff --git a/src/main/java/net/kaaass/rumbase/parse/ISqlStatement.java b/src/main/java/net/kaaass/rumbase/parse/ISqlStatement.java
new file mode 100644
index 0000000..90236c0
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/parse/ISqlStatement.java
@@ -0,0 +1,9 @@
+package net.kaaass.rumbase.parse;
+
+/**
+ * 标志用接口,用于标志SQL语句
+ *
+ * @author kaaass
+ */
+public interface ISqlStatement {
+}
diff --git a/src/main/java/net/kaaass/rumbase/parse/SqlParser.java b/src/main/java/net/kaaass/rumbase/parse/SqlParser.java
new file mode 100644
index 0000000..2e6417e
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/parse/SqlParser.java
@@ -0,0 +1,63 @@
+package net.kaaass.rumbase.parse;
+
+import net.kaaass.rumbase.parse.exception.SqlSyntaxException;
+import net.kaaass.rumbase.parse.parser.*;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.statement.Statement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 解析任意给定的SQL语句
+ *
+ * @author kaaass
+ */
+public class SqlParser {
+
+    private static List jsqlpStatementParsers = new ArrayList<>() {{
+        add(new SelectStatementParser());
+        add(new InsertStatementParser());
+        add(new UpdateStatementParser());
+        add(new DeleteStatementParser());
+        add(new CreateTableStatementParser());
+        add(new CreateIndexStatementParser());
+    }};
+
+    /**
+     * 将语句解析为SQL语法树
+     */
+    public static ISqlStatement parseStatement(String sql) throws SqlSyntaxException {
+        // TODO
+        // 尝试 JSqlParser 解析
+        Statement stmt;
+        try {
+            stmt = CCJSqlParserUtil.parse(sql);
+        } catch (JSQLParserException e) {
+            throw new SqlSyntaxException(1, e);
+        }
+        for (var parser : jsqlpStatementParsers) {
+            if (parser.checkStatement(stmt)) {
+                return parser.parse(stmt);
+            }
+        }
+        throw new SqlSyntaxException(2);
+    }
+
+    /**
+     * 语句解析器的通用接口
+     */
+    public interface StatementParser {
+
+        /**
+         * 将输入解析为SQL语法树
+         */
+        ISqlStatement parse(T input) throws SqlSyntaxException;
+
+        /**
+         * 检查语句可否被当前解析器解析
+         */
+        boolean checkStatement(T input);
+    }
+}
diff --git a/src/main/java/net/kaaass/rumbase/parse/exception/EvaluationException.java b/src/main/java/net/kaaass/rumbase/parse/exception/EvaluationException.java
new file mode 100644
index 0000000..8ae0093
--- /dev/null
+++ b/src/main/java/net/kaaass/rumbase/parse/exception/EvaluationException.java
@@ -0,0 +1,30 @@
+package net.kaaass.rumbase.parse.exception;
+
+import net.kaaass.rumbase.exception.RumbaseRuntimeException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * E1002 表达式运算错误
+ * 

+ * E1002-1 运算结果类型错误 + * E1002-2 运算过程错误,可能存在不支持的表达式 + * + * @author kaaass + */ +public class EvaluationException extends RumbaseRuntimeException { + + public static final Map REASONS = new HashMap<>() {{ + put(1, "运算结果类型错误"); + put(2, "运算过程错误,可能存在不支持的表达式"); + }}; + + public EvaluationException(int subId) { + super(5001, subId, REASONS.get(subId)); + } + + public EvaluationException(int subId, Throwable e) { + super(5001, subId, REASONS.get(subId), e); + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/exception/SqlSyntaxException.java b/src/main/java/net/kaaass/rumbase/parse/exception/SqlSyntaxException.java new file mode 100644 index 0000000..a8b24ee --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/exception/SqlSyntaxException.java @@ -0,0 +1,30 @@ +package net.kaaass.rumbase.parse.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E1001 记录不存在异常 + *

+ * E1001-1 SQL语句语法错误 + * E1001-2 不支持的SQL语句 + * + * @author kaaass + */ +public class SqlSyntaxException extends RumbaseException { + + public static final Map REASONS = new HashMap<>() {{ + put(1, "SQL语句语法错误"); + put(2, "不支持的SQL语句"); + }}; + + public SqlSyntaxException(int subId) { + super(5001, subId, REASONS.get(subId)); + } + + public SqlSyntaxException(int subId, Throwable e) { + super(5001, subId, REASONS.get(subId), e); + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/CreateIndexStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/CreateIndexStatementParser.java new file mode 100644 index 0000000..fcb614c --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/parser/CreateIndexStatementParser.java @@ -0,0 +1,35 @@ +package net.kaaass.rumbase.parse.parser; + +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.stmt.CreateIndexStatement; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.create.index.CreateIndex; + +import java.util.stream.Collectors; + +/** + * 将建索引语句解释为对应语法树 + * + * @author kaaass + */ +public class CreateIndexStatementParser implements JsqlpStatementParser { + @Override + public ISqlStatement parse(Statement input) { + var stmt = (CreateIndex) input; + // 解析索引名 + var indexName = stmt.getIndex().getName(); + // 解析表名 + var tableName = stmt.getTable().getName(); + // 解析字段 + var columns = stmt.getIndex().getColumnsNames().stream() + .map(name -> new ColumnIdentifier(tableName, name)) + .collect(Collectors.toList()); + return new CreateIndexStatement(indexName, tableName, columns); + } + + @Override + public boolean checkStatement(Statement input) { + return input instanceof CreateIndex; + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/CreateTableStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/CreateTableStatementParser.java new file mode 100644 index 0000000..9bfb26a --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/parser/CreateTableStatementParser.java @@ -0,0 +1,55 @@ +package net.kaaass.rumbase.parse.parser; + +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.stmt.CreateTableStatement; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.create.table.ColDataType; +import net.sf.jsqlparser.statement.create.table.CreateTable; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 将建表语句解释为对应语法树 + * + * @author kaaass + */ +public class CreateTableStatementParser implements JsqlpStatementParser { + @Override + public ISqlStatement parse(Statement input) { + var stmt = (CreateTable) input; + // 解析表名 + var tableName = stmt.getTable().getName(); + // 解析字段定义 + var columnDefs = stmt.getColumnDefinitions().stream() + .map(def -> new CreateTableStatement.ColumnDefinition( + mapColType(def.getColDataType()), + def.getColumnName(), + def.getColumnSpecs() != null + && checkNotNull(def.getColumnSpecs()) + )) + .collect(Collectors.toList()); + return new CreateTableStatement(tableName, columnDefs); + } + + private boolean checkNotNull(List specs) { + var a = specs.indexOf("not"); + if (a < 0) { + a = specs.indexOf("NOT"); + } + var b = specs.indexOf("null"); + if (b < 0) { + b = specs.indexOf("NULL"); + } + return a >= 0 && b > a; + } + + public static CreateTableStatement.ColumnType mapColType(ColDataType type) { + return new CreateTableStatement.ColumnType(type.getDataType(), type.getArgumentsStringList()); + } + + @Override + public boolean checkStatement(Statement input) { + return input instanceof CreateTable; + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/DeleteStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/DeleteStatementParser.java new file mode 100644 index 0000000..c70d7d1 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/parser/DeleteStatementParser.java @@ -0,0 +1,33 @@ +package net.kaaass.rumbase.parse.parser; + +import net.kaaass.rumbase.parse.ConditionExpression; +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.stmt.DeleteStatement; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.delete.Delete; + +/** + * 将删除语句解释为对应语法树 + * + * @author kaaass + */ +public class DeleteStatementParser implements JsqlpStatementParser { + @Override + public ISqlStatement parse(Statement input) { + Delete stmt = (Delete) input; + // 解析表名 + var tableName = stmt.getTable().getName(); + // 解析where + ConditionExpression where = null; + if (stmt.getWhere() != null) { + where = new ConditionExpression(stmt.getWhere(), tableName); + } + // 拼接结果 + return new DeleteStatement(tableName, where); + } + + @Override + public boolean checkStatement(Statement input) { + return input instanceof Delete; + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/InsertStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/InsertStatementParser.java new file mode 100644 index 0000000..a82ffea --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/parser/InsertStatementParser.java @@ -0,0 +1,51 @@ +package net.kaaass.rumbase.parse.parser; + +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.stmt.InsertStatement; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.expression.operators.relational.ItemsListVisitorAdapter; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.insert.Insert; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * 将插入语句解释为对应语法树 + * + * @author kaaass + */ +public class InsertStatementParser implements JsqlpStatementParser { + @Override + public ISqlStatement parse(Statement input) { + var stmt = (Insert) input; + // 解析表名 + var tableName = stmt.getTable().getName(); + // 解析列 + List columns = null; + var parsedColumn = stmt.getColumns(); + if (parsedColumn != null) { + columns = ParserUtil.mapColumnList(parsedColumn, tableName); + } + // 解析插入的数据 + var values = new ArrayList(); + stmt.getItemsList().accept(new ItemsListVisitorAdapter() { + + @Override + public void visit(ExpressionList expressionList) { + expressionList.getExpressions().stream() + .map(Objects::toString) + .forEach(values::add); + } + }); + // 拼接结果 + return new InsertStatement(tableName, columns, values); + } + + @Override + public boolean checkStatement(Statement stmt) { + return stmt instanceof Insert; + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/JsqlpStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/JsqlpStatementParser.java new file mode 100644 index 0000000..27793cd --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/parser/JsqlpStatementParser.java @@ -0,0 +1,12 @@ +package net.kaaass.rumbase.parse.parser; + +import net.kaaass.rumbase.parse.SqlParser; +import net.sf.jsqlparser.statement.Statement; + +/** + * 适配JSqlParser库的解析器 + * + * @author kaaass + */ +public interface JsqlpStatementParser extends SqlParser.StatementParser { +} diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/ParserUtil.java b/src/main/java/net/kaaass/rumbase/parse/parser/ParserUtil.java new file mode 100644 index 0000000..caa97ab --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/parser/ParserUtil.java @@ -0,0 +1,45 @@ +package net.kaaass.rumbase.parse.parser; + +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.sf.jsqlparser.schema.Column; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 解析相关的工具函数 + * + * @author kaaass + */ +public class ParserUtil { + + /** + * 映射字段 + * + * @param column 字段 + * @param defaultTableName 若不存在表,默认填充的表字段 + * @return 语法树字段 + */ + public static ColumnIdentifier mapColumn(Column column, String defaultTableName) { + return new ColumnIdentifier( + column.getTable() == null ? defaultTableName : column.getTable().getName(), + column.getColumnName() + ); + } + + /** + * 映射字段列表 + * + * @param columnList 字段列表 + * @param defaultTableName 若不存在表,默认填充的表字段 + * @return 语法树字段列表 + */ + public static List mapColumnList(List columnList, String defaultTableName) { + return columnList.stream() + .map(column -> new ColumnIdentifier( + column.getTable() == null ? defaultTableName : column.getTable().getName(), + column.getColumnName() + )) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/SelectStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/SelectStatementParser.java new file mode 100644 index 0000000..dafbdd5 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/parser/SelectStatementParser.java @@ -0,0 +1,119 @@ +package net.kaaass.rumbase.parse.parser; + +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.ConditionExpression; +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.stmt.SelectStatement; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 将选择语句解释为对应语法树 + * + * @author kaaass + */ +@Slf4j +public class SelectStatementParser implements JsqlpStatementParser { + @Override + public ISqlStatement parse(Statement input) { + final PlainSelect[] selectStmtArr = {null}; + ((Select) input).getSelectBody().accept(new SelectVisitorAdapter() { + @Override + public void visit(PlainSelect plainSelect) { + selectStmtArr[0] = plainSelect; + } + }); + PlainSelect stmt = selectStmtArr[0]; + // 解析distinct + var distinct = stmt.getDistinct() != null; + // 解析表名 + var tableName = getTableFromItem(stmt.getFromItem()); + // 解析列 + List columns = new ArrayList<>(); + var columnVisitor = new ExpressionVisitorAdapter() { + + @Override + public void visit(Column column) { + columns.add(ParserUtil.mapColumn(column, tableName)); + } + }; + stmt.getSelectItems().forEach(selectItem -> selectItem.accept(new SelectItemVisitorAdapter() { + + @Override + public void visit(AllColumns column) { + columns.clear(); + } + + @Override + public void visit(SelectExpressionItem item) { + item.getExpression().accept(columnVisitor); + } + })); + // 解析join + var joins = stmt.getJoins().stream() + .map(join -> { + ConditionExpression joinOn = null; + if (join.getOnExpression() != null) { + joinOn = new ConditionExpression(join.getOnExpression(), tableName); + } + var table = getTableFromItem(join.getRightItem()); + var result = new SelectStatement.JoinTable(table, joinOn); + result.setOuter(join.isOuter()); + result.setRight(join.isRight()); + result.setLeft(join.isLeft()); + result.setNatural(join.isNatural()); + result.setFull(join.isFull()); + result.setInner(join.isInner()); + result.setSimple(join.isSimple()); + return result; + }) + .collect(Collectors.toList()); + // 解析where + ConditionExpression where = null; + if (stmt.getWhere() != null) { + where = new ConditionExpression(stmt.getWhere(), ""); + } + // 解析orderBy + ColumnIdentifier[] columnIdentifiers = new ColumnIdentifier[1]; + var orderVisitor = new ExpressionVisitorAdapter() { + + @Override + public void visit(Column column) { + columnIdentifiers[0] = new ColumnIdentifier(column.getTable().getName(), column.getColumnName()); + } + }; + var orderBys = stmt.getOrderByElements().stream() + .map(orderByElement -> { + orderByElement.getExpression().accept(orderVisitor); + return new SelectStatement.OrderBy(columnIdentifiers[0], orderByElement.isAsc()); + }) + .collect(Collectors.toList()); + // 拼接结果 + return new SelectStatement(distinct, columns, tableName, joins, where, orderBys); + } + + private String getTableFromItem(FromItem fi) { + final String[] tableNames = new String[1]; + fi.accept(new FromItemVisitorAdapter() { + + @Override + public void visit(Table table) { + tableNames[0] = table.getName(); + } + }); + return tableNames[0]; + } + + @Override + public boolean checkStatement(Statement input) { + return input instanceof Select; + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/UpdateStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/UpdateStatementParser.java new file mode 100644 index 0000000..f3e2383 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/parser/UpdateStatementParser.java @@ -0,0 +1,47 @@ +package net.kaaass.rumbase.parse.parser; + +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.ConditionExpression; +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.stmt.UpdateStatement; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.update.Update; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 将更新语句解释为对应语法树 + * + * @author kaaass + */ +public class UpdateStatementParser implements JsqlpStatementParser { + @Override + public ISqlStatement parse(Statement input) { + Update stmt = (Update) input; + // 解析表名 + var tableName = stmt.getTable().getName(); + // 解析列 + List columns = null; + var parsedColumn = stmt.getColumns(); + if (parsedColumn != null) { + columns = ParserUtil.mapColumnList(parsedColumn, tableName); + } + // 解析插入的数据 + var values = stmt.getExpressions().stream() + .map(Objects::toString) + .collect(Collectors.toList()); + // 解析where + ConditionExpression where = null; + if (stmt.getWhere() != null) { + where = new ConditionExpression(stmt.getWhere(), tableName); + } + return new UpdateStatement(tableName, columns, values, where); + } + + @Override + public boolean checkStatement(Statement input) { + return input instanceof Update; + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/CreateIndexStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/CreateIndexStatement.java new file mode 100644 index 0000000..3f5e8fb --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/CreateIndexStatement.java @@ -0,0 +1,33 @@ +package net.kaaass.rumbase.parse.stmt; + +import lombok.AllArgsConstructor; +import lombok.Data; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.ISqlStatement; + +import java.util.List; + +/** + * SQL语法树:创建索引语句 + * + * @author kaaass + */ +@Data +@AllArgsConstructor +public class CreateIndexStatement implements ISqlStatement { + + /** + * 待创建的索引名称 + */ + private String indexName; + + /** + * 索引的目标表名称 + */ + private String tableName; + + /** + * 索引的目标列 + */ + private List columns; +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/CreateTableStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/CreateTableStatement.java new file mode 100644 index 0000000..5a0dd50 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/CreateTableStatement.java @@ -0,0 +1,68 @@ +package net.kaaass.rumbase.parse.stmt; + +import lombok.AllArgsConstructor; +import lombok.Data; +import net.kaaass.rumbase.parse.ISqlStatement; + +import java.util.List; + +/** + * SQL语法树:建表语句 + * + * @author kaaass + */ +@Data +@AllArgsConstructor +public class CreateTableStatement implements ISqlStatement { + + /** + * 创建表的表名 + */ + private String tableName; + + /** + * 创建表中的列定义 + */ + private List columnDefinitions; + + /** + * SQL语法树:列定义 + */ + @Data + @AllArgsConstructor + public static class ColumnDefinition { + + /** + * 字段类型 + */ + private ColumnType columnType; + + /** + * 字段名称 + */ + private String columnName; + + /** + * 字段值非空 + */ + private boolean notNull; + } + + /** + * SQL语法树:字段类型 + */ + @Data + @AllArgsConstructor + public static class ColumnType { + + /** + * 字段的SQL类型名,全小写 + */ + private String typeName; + + /** + * 字段参数,如varchar(255)则为['255'] + */ + private List arguments; + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/DeleteStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/DeleteStatement.java new file mode 100644 index 0000000..23590d4 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/DeleteStatement.java @@ -0,0 +1,26 @@ +package net.kaaass.rumbase.parse.stmt; + +import lombok.AllArgsConstructor; +import lombok.Data; +import net.kaaass.rumbase.parse.ConditionExpression; +import net.kaaass.rumbase.parse.ISqlStatement; + +/** + * SQL语法树:删除语句 + * + * @author kaaass + */ +@Data +@AllArgsConstructor +public class DeleteStatement implements ISqlStatement { + + /** + * 删除的目标表名 + */ + private String tableName; + + /** + * 删除的筛选条件,为null则代表清空表 + */ + private ConditionExpression where; +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/InsertStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/InsertStatement.java new file mode 100644 index 0000000..c9c3f87 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/InsertStatement.java @@ -0,0 +1,33 @@ +package net.kaaass.rumbase.parse.stmt; + +import lombok.AllArgsConstructor; +import lombok.Data; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.ISqlStatement; + +import java.util.List; + +/** + * SQL语法树:插入语句 + * + * @author kaaass + */ +@Data +@AllArgsConstructor +public class InsertStatement implements ISqlStatement { + + /** + * 插入行的目标表名 + */ + private String tableName; + + /** + * 插入数据对应的列,若为null则说明值为完整元组 + */ + private List columns; + + /** + * 插入的数据,以字符串表示 + */ + private List values; +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/SelectStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/SelectStatement.java new file mode 100644 index 0000000..cf21d2a --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/SelectStatement.java @@ -0,0 +1,94 @@ +package net.kaaass.rumbase.parse.stmt; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.ConditionExpression; +import net.kaaass.rumbase.parse.ISqlStatement; + +import java.util.List; + +/** + * SQL语法树:选择语句 + * + * @author kaaass + */ +@Data +@AllArgsConstructor +public class SelectStatement implements ISqlStatement { + + /** + * 选择的结果行是否不允许重复 + */ + private boolean distinct; + + /** + * 选择的字段,如果为null则代表选择全部字段,即“*” + */ + private List selectColumns; + + /** + * 选择的源表,其他表以Join的方式表示 + */ + private String fromTable; + + /** + * 需要Join的表,按顺序从fromTable开始连接,为null代表不join + * 如:a join b on cond1 join c on cond2, d + * 则fromTable为a,joins有3项[b, c, d] + */ + private List joins; + + /** + * 选择时用于过滤的条件,为null代表不进行过滤 + */ + private ConditionExpression where; + + /** + * 选择后结果排序的规则,为null代表不排序 + */ + private List orderBys; + + /** + * SQL语法树:Join表 + */ + @Data + @RequiredArgsConstructor + public static class JoinTable { + + private final String tableName; + + private final ConditionExpression joinOn; + + /* + * Join方式 + */ + + private boolean outer = false; + private boolean right = false; + private boolean left = false; + private boolean natural = false; + private boolean full = false; + private boolean inner = false; + private boolean simple = false; + } + + /** + * SQL语法树:排序方式 + */ + @Data + @AllArgsConstructor + public static class OrderBy { + + /** + * 排序依据的列 + */ + private ColumnIdentifier column; + + /** + * 是否升序 + */ + private boolean ascending; + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/UpdateStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/UpdateStatement.java new file mode 100644 index 0000000..280aef5 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/UpdateStatement.java @@ -0,0 +1,39 @@ +package net.kaaass.rumbase.parse.stmt; + +import lombok.AllArgsConstructor; +import lombok.Data; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.ConditionExpression; +import net.kaaass.rumbase.parse.ISqlStatement; + +import java.util.List; + +/** + * SQL语法树:更新语句 + * + * @author kaaass + */ +@Data +@AllArgsConstructor +public class UpdateStatement implements ISqlStatement { + + /** + * 更新的目标表名 + */ + private String tableName; + + /** + * 要更新的列,与值对应 + */ + private List columns; + + /** + * 更新的目标值 + */ + private List values; + + /** + * 更新行的条件 + */ + private ConditionExpression where; +} diff --git a/src/test/java/net/kaaass/rumbase/parse/ConditionExpressionTest.java b/src/test/java/net/kaaass/rumbase/parse/ConditionExpressionTest.java new file mode 100644 index 0000000..f848629 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/parse/ConditionExpressionTest.java @@ -0,0 +1,54 @@ +package net.kaaass.rumbase.parse; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; + +import java.util.ArrayList; +import java.util.HashMap; + +@Slf4j +public class ConditionExpressionTest extends TestCase { + + public void testEvaluate() throws JSQLParserException { + var exp = CCJSqlParserUtil.parseCondExpression("a.ID - b.ID + ID >= 3"); + var cond = new ConditionExpression(exp, "c"); + // 测试 + var params = new HashMap(); + params.put(new ColumnIdentifier("a", "ID"), 1); + params.put(new ColumnIdentifier("b", "ID"), 2); + params.put(new ColumnIdentifier("c", "ID"), 3); + assertFalse(cond.evaluate(params)); + // + params.replace(new ColumnIdentifier("b", "ID"), 1); + assertTrue(cond.evaluate(params)); + } + + public void testEvaluateString() throws JSQLParserException { + var exp = CCJSqlParserUtil.parseCondExpression("Name <= 'Kaaass'"); + var cond = new ConditionExpression(exp, "stu"); + // 测试 + var params = new HashMap(); + params.put(new ColumnIdentifier("stu", "Name"), "KAAAsS"); + assertTrue(cond.evaluate(params)); + // + params.put(new ColumnIdentifier("stu", "Name"), "kaaass"); + assertFalse(cond.evaluate(params)); + // + params.put(new ColumnIdentifier("stu", "Name"), "Kaaass"); + assertTrue(cond.evaluate(params)); + } + + public void testGetParams() throws JSQLParserException { + var exp = CCJSqlParserUtil.parseExpression("a.ID - b.ID + ID >= 3"); + var cond = new ConditionExpression(exp, "c"); + var result = cond.getParams(); + log.info("Parsed: {}", result); + // 比较结果 + var expected = new ArrayList(); + assertTrue(result.contains(new ColumnIdentifier("a", "ID"))); + assertTrue(result.contains(new ColumnIdentifier("b", "ID"))); + assertTrue(result.contains(new ColumnIdentifier("c", "ID"))); + } +} \ No newline at end of file diff --git a/src/test/java/net/kaaass/rumbase/parse/SqlTest.java b/src/test/java/net/kaaass/rumbase/parse/SqlTest.java new file mode 100644 index 0000000..031a630 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/parse/SqlTest.java @@ -0,0 +1,98 @@ +package net.kaaass.rumbase.parse; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.create.index.CreateIndex; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectVisitorAdapter; +import net.sf.jsqlparser.statement.update.Update; + +@Slf4j +public class SqlTest extends TestCase { + + public void testParseSelect() throws JSQLParserException { + Select select = (Select) CCJSqlParserUtil.parse( + "SELECT name, a.ID as aid, `account`.`balance` " + + "from `account` as a join `payment` as p on a.ID = p.ID, `file` as f " + + "WHERE a.`ID` > 1 and (p.`type` = 'N' or p.`type` = 'T') " + + "order by acc.ID desc;" + ); + final PlainSelect[] selectStmtArr = {null}; + select.getSelectBody().accept(new SelectVisitorAdapter() { + @Override + public void visit(PlainSelect plainSelect) { + selectStmtArr[0] = plainSelect; + } + }); + PlainSelect selectStmt = selectStmtArr[0]; + log.info("Select: {}", selectStmt.getSelectItems()); + log.info("From: {}", selectStmt.getFromItem()); + log.info("Join: {}", selectStmt.getJoins()); + log.info("Where: {}", selectStmt.getWhere()); + log.info("OrderBy: {}", selectStmt.getOrderByElements()); + var expr = selectStmt.getWhere(); + } + + public void testParseInsert() throws JSQLParserException { + Insert insert = (Insert) CCJSqlParserUtil.parse( + "INSERT INTO Persons (LastName, Address) VALUES ('Wilson', 'Champs-Elysees')" + ); + log.info("Table: {}", insert.getTable()); + log.info("Values: {}", insert.getItemsList()); + log.info("Columns: {}", insert.getColumns()); + } + + public void testParseUpdate() throws JSQLParserException { + Update stmt = (Update) CCJSqlParserUtil.parse( + "UPDATE Person SET Address = 'Zhongshan 23', City = 'Nanjing'\n" + + "WHERE LastName = 'Wilson'" + ); + log.info("Table: {}", stmt.getTable()); + log.info("Columns: {}", stmt.getColumns()); + log.info("Expressions: {}", stmt.getExpressions()); + log.info("Where: {}", stmt.getWhere()); + stmt.getWhere().accept(new ExpressionVisitorAdapter() { + + @Override + public void visit(Column column) { + log.info("Column in where: {} {}", column.getTable(), column.getColumnName()); + } + }); + } + + public void testParseDelete() throws JSQLParserException { + Delete stmt = (Delete) CCJSqlParserUtil.parse("DELETE FROM Person WHERE LastName = 'Wilson'"); + log.info("Table: {}", stmt.getTable()); + log.info("Where: {}", stmt.getWhere()); + } + + public void testParseCreateTable() throws JSQLParserException { + CreateTable stmt = (CreateTable) CCJSqlParserUtil.parse( + "CREATE TABLE Persons\n" + + "(\n" + + "Id_P int,\n" + + "LastName varchar(255),\n" + + "FirstName varchar(255),\n" + + "Address varchar(255),\n" + + "City varchar(255)\n" + + ")" + ); + log.info("Table: {}", stmt.getTable()); + log.info("Columns: {}", stmt.getColumnDefinitions()); + } + + public void testCreateIndex() throws JSQLParserException { + CreateIndex stmt = (CreateIndex) CCJSqlParserUtil.parse("CREATE INDEX PersonIndex ON Person (LastName) "); + log.info("Table: {}", stmt.getTable()); + log.info("Name: {}", stmt.getIndex().getName()); + log.info("Columns: {}", stmt.getIndex().getColumns()); + } +} diff --git a/src/test/java/net/kaaass/rumbase/parse/parser/CreateIndexStatementParserTest.java b/src/test/java/net/kaaass/rumbase/parse/parser/CreateIndexStatementParserTest.java new file mode 100644 index 0000000..79e43b4 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/parse/parser/CreateIndexStatementParserTest.java @@ -0,0 +1,51 @@ +package net.kaaass.rumbase.parse.parser; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.CreateIndexStatement; + +import java.util.ArrayList; + +@Slf4j +public class CreateIndexStatementParserTest extends TestCase { + + public void testParseSingle() throws SqlSyntaxException { + var sql = "CREATE INDEX PersonIndex ON Person (LastName) ;"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof CreateIndexStatement); + log.info("Parsed: {}", stmt); + // 准备预期结果 + var columns = new ArrayList(); + columns.add(new ColumnIdentifier("Person", "LastName")); + var expected = new CreateIndexStatement( + "PersonIndex", + "Person", + columns + ); + // 比较 + assertEquals(expected, stmt); + } + + public void testParseMulti() throws SqlSyntaxException { + var sql = "CREATE INDEX PersonIndex ON Person (LastName, ID) ;"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof CreateIndexStatement); + log.info("Parsed: {}", stmt); + // 准备预期结果 + var columns = new ArrayList(); + columns.add(new ColumnIdentifier("Person", "LastName")); + columns.add(new ColumnIdentifier("Person", "ID")); + var expected = new CreateIndexStatement( + "PersonIndex", + "Person", + columns + ); + // 比较 + assertEquals(expected, stmt); + } +} \ No newline at end of file diff --git a/src/test/java/net/kaaass/rumbase/parse/parser/CreateTableStatementParserTest.java b/src/test/java/net/kaaass/rumbase/parse/parser/CreateTableStatementParserTest.java new file mode 100644 index 0000000..79e3e97 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/parse/parser/CreateTableStatementParserTest.java @@ -0,0 +1,50 @@ +package net.kaaass.rumbase.parse.parser; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.CreateTableStatement; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +public class CreateTableStatementParserTest extends TestCase { + + public void testParse() throws SqlSyntaxException { + var sql = "CREATE TABLE Persons\n" + + "(\n" + + "Id_P int not null,\n" + + "LastName varchar(255),\n" + + "FirstName varchar(255) NOT NULL\n" + + ")"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof CreateTableStatement); + log.info("Parsed: {}", stmt); + // 准备预期结果 + var columns = new ArrayList(); + columns.add(new CreateTableStatement.ColumnDefinition( + new CreateTableStatement.ColumnType("int", null), + "Id_P", + true + )); + columns.add(new CreateTableStatement.ColumnDefinition( + new CreateTableStatement.ColumnType("varchar", List.of("255")), + "LastName", + false + )); + columns.add(new CreateTableStatement.ColumnDefinition( + new CreateTableStatement.ColumnType("varchar", List.of("255")), + "FirstName", + true + )); + var expected = new CreateTableStatement( + "Persons", + columns + ); + // 比较 + assertEquals(expected, stmt); + } +} \ No newline at end of file diff --git a/src/test/java/net/kaaass/rumbase/parse/parser/DeleteStatementParserTest.java b/src/test/java/net/kaaass/rumbase/parse/parser/DeleteStatementParserTest.java new file mode 100644 index 0000000..d54fdf7 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/parse/parser/DeleteStatementParserTest.java @@ -0,0 +1,42 @@ +package net.kaaass.rumbase.parse.parser; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.DeleteStatement; + +import java.util.HashMap; + +@Slf4j +public class DeleteStatementParserTest extends TestCase { + + public void testParse() throws SqlSyntaxException { + var sql = "DELETE FROM Person WHERE LastName = 'Wilson'"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof DeleteStatement); + log.info("Parsed: {}", stmt); + // 比较 + assertEquals("Person", ((DeleteStatement) stmt).getTableName()); + // 测试 where + var params = new HashMap(); + params.put(new ColumnIdentifier("Person", "LastName"), "Wilson"); + assertTrue(((DeleteStatement) stmt).getWhere().evaluate(params)); + // + params.put(new ColumnIdentifier("Person", "LastName"), "KAAAsS"); + assertFalse(((DeleteStatement) stmt).getWhere().evaluate(params)); + } + + public void testParseNull() throws SqlSyntaxException { + var sql = "DELETE FROM Person "; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof DeleteStatement); + log.info("Parsed: {}", stmt); + // 比较 + assertEquals("Person", ((DeleteStatement) stmt).getTableName()); + assertNull(((DeleteStatement) stmt).getWhere()); + } +} \ No newline at end of file diff --git a/src/test/java/net/kaaass/rumbase/parse/parser/InsertStatementParserTest.java b/src/test/java/net/kaaass/rumbase/parse/parser/InsertStatementParserTest.java new file mode 100644 index 0000000..08736f1 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/parse/parser/InsertStatementParserTest.java @@ -0,0 +1,57 @@ +package net.kaaass.rumbase.parse.parser; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.InsertStatement; + +import java.util.ArrayList; + +@Slf4j +public class InsertStatementParserTest extends TestCase { + + public void testParseColumnValue() throws SqlSyntaxException { + var sql = "INSERT INTO Persons (Persons.LastName, Address) VALUES ('Wilson', 'Champs-Elysees')"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof InsertStatement); + log.info("Parsed: {}", stmt); + // 准备预期结果 + var columns = new ArrayList(); + columns.add(new ColumnIdentifier("Persons", "LastName")); + columns.add(new ColumnIdentifier("Persons", "Address")); + var values = new ArrayList(); + values.add("'Wilson'"); + values.add("'Champs-Elysees'"); + var expected = new InsertStatement( + "Persons", + columns, + values + ); + // 比较 + assertEquals(expected, stmt); + } + + public void testParseValue() throws SqlSyntaxException { + var sql = "INSERT INTO stu VALUES (20200101, 'KAAAsS', true, 3.9)"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof InsertStatement); + log.info("Parsed: {}", stmt); + // 准备预期结果 + var values = new ArrayList(); + values.add("20200101"); + values.add("'KAAAsS'"); + values.add("true"); + values.add("3.9"); + var expected = new InsertStatement( + "stu", + null, + values + ); + // 比较 + assertEquals(expected, stmt); + } +} \ No newline at end of file diff --git a/src/test/java/net/kaaass/rumbase/parse/parser/SelectStatementParserTest.java b/src/test/java/net/kaaass/rumbase/parse/parser/SelectStatementParserTest.java new file mode 100644 index 0000000..64a4413 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/parse/parser/SelectStatementParserTest.java @@ -0,0 +1,35 @@ +package net.kaaass.rumbase.parse.parser; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.SelectStatement; + +import java.util.HashMap; + +@Slf4j +public class SelectStatementParserTest extends TestCase { + + public void testParse() throws SqlSyntaxException { + var sql = "SELECT distinct name, account.ID, account.balance \n" + + "from account join payment on account.ID = payment.ID, file\n" + + "WHERE account.ID > 1 and (payment.type = 'N' or payment.type = 'T') \n" + + "order by account.ID desc;"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof SelectStatement); + log.info("Parsed: {}", stmt); + // 测试 + var where = ((SelectStatement) stmt).getWhere(); + var params = new HashMap(); + params.put(new ColumnIdentifier("account", "ID"), 2); + params.put(new ColumnIdentifier("payment", "type"), "L"); + assertFalse(where.evaluate(params)); + // + params.put(new ColumnIdentifier("payment", "type"), "N"); + assertTrue(where.evaluate(params)); + // TODO 其他测试暂时懒得写,太麻烦了,目检正确 + } +} \ No newline at end of file diff --git a/src/test/java/net/kaaass/rumbase/parse/parser/UpdateStatementParserTest.java b/src/test/java/net/kaaass/rumbase/parse/parser/UpdateStatementParserTest.java new file mode 100644 index 0000000..8a71284 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/parse/parser/UpdateStatementParserTest.java @@ -0,0 +1,56 @@ +package net.kaaass.rumbase.parse.parser; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.UpdateStatement; + +import java.util.HashMap; +import java.util.List; + +@Slf4j +public class UpdateStatementParserTest extends TestCase { + + public void testParse() throws SqlSyntaxException { + var sql = "UPDATE Person SET Address = 'Zhongshan 23', City = 'Nanjing'\n" + + "WHERE LastName = 'Wilson'"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof UpdateStatement); + log.info("Parsed: {}", stmt); + // 比较 + assertEquals("Person", ((UpdateStatement) stmt).getTableName()); + assertEquals(List.of( + new ColumnIdentifier("Person", "Address"), + new ColumnIdentifier("Person", "City") + ), ((UpdateStatement) stmt).getColumns()); + assertEquals("Person", ((UpdateStatement) stmt).getTableName()); + assertEquals(List.of("'Zhongshan 23'", "'Nanjing'"), ((UpdateStatement) stmt).getValues()); + // 测试 where + var params = new HashMap(); + params.put(new ColumnIdentifier("Person", "LastName"), "Wilson"); + assertTrue(((UpdateStatement) stmt).getWhere().evaluate(params)); + // + params.put(new ColumnIdentifier("Person", "LastName"), "KAAAsS"); + assertFalse(((UpdateStatement) stmt).getWhere().evaluate(params)); + } + + public void testParseNull() throws SqlSyntaxException { + var sql = "UPDATE Person SET Address = 'Zhongshan 23', City = 'Nanjing'"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof UpdateStatement); + log.info("Parsed: {}", stmt); + // 比较 + assertEquals("Person", ((UpdateStatement) stmt).getTableName()); + assertEquals(List.of( + new ColumnIdentifier("Person", "Address"), + new ColumnIdentifier("Person", "City") + ), ((UpdateStatement) stmt).getColumns()); + assertEquals("Person", ((UpdateStatement) stmt).getTableName()); + assertEquals(List.of("'Zhongshan 23'", "'Nanjing'"), ((UpdateStatement) stmt).getValues()); + assertNull(((UpdateStatement) stmt).getWhere()); + } +} \ No newline at end of file From 525913893c0ca97a80146c01256099a991bdafce Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Sat, 16 Jan 2021 18:17:41 +0800 Subject: [PATCH 14/18] =?UTF-8?q?[+]=E9=A1=B5=E7=AE=A1=E7=90=86=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E9=80=80=E5=87=BA=E6=97=B6=E5=9B=9E=E5=86=99=20(#15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add flush all files function * Add test for flushing all files function * Turn file operations into object attributes * 修正测试用例重复运行错误 * Revert "Turn file operations into object attributes" This reverts commit cc827db3 * 改进一处日志输出 Co-authored-by: XuanLaoYee <1115810634@qq.com> --- .../rumbase/index/btree/BPlusTreeIndex.java | 4 +- .../net/kaaass/rumbase/page/PageManager.java | 18 ++++++++- .../kaaass/rumbase/page/PageStorageTest.java | 39 +++++++++++++++++++ .../rumbase/record/IRecordStorageTest.java | 2 + 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/kaaass/rumbase/index/btree/BPlusTreeIndex.java b/src/main/java/net/kaaass/rumbase/index/btree/BPlusTreeIndex.java index 5b4b81d..9a45793 100644 --- a/src/main/java/net/kaaass/rumbase/index/btree/BPlusTreeIndex.java +++ b/src/main/java/net/kaaass/rumbase/index/btree/BPlusTreeIndex.java @@ -1,5 +1,6 @@ package net.kaaass.rumbase.index.btree; +import lombok.extern.slf4j.Slf4j; import net.kaaass.rumbase.index.Index; import net.kaaass.rumbase.index.Pair; import net.kaaass.rumbase.index.exception.ItemInNextPageException; @@ -19,6 +20,7 @@ /** * @author 无索魏 */ +@Slf4j public class BPlusTreeIndex implements Index { // Page root; PageStorage pageStorage; @@ -1175,7 +1177,7 @@ public Pair next() { } return null; } else { - System.out.println("nextPage*****************"); + log.debug("nextPage, num = {}", nextPageNum); //unpin currentPage.unpin(); currentPage = this.bPlusTreeIndex.pageStorage.get(nextPageNum); diff --git a/src/main/java/net/kaaass/rumbase/page/PageManager.java b/src/main/java/net/kaaass/rumbase/page/PageManager.java index 0483634..ba4e166 100644 --- a/src/main/java/net/kaaass/rumbase/page/PageManager.java +++ b/src/main/java/net/kaaass/rumbase/page/PageManager.java @@ -3,6 +3,11 @@ import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.page.mock.MockPageStorage; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * @author XuanLaoYee */ @@ -20,6 +25,17 @@ public class PageManager { * @throws FileException 若文件不存在则创建,创建过程中出现错误会抛出错误 */ public static PageStorage fromFile(String filepath) throws FileException { - return new RumPageStorage(filepath); + PageStorage storage = new RumPageStorage(filepath); + storages.add(storage); + return storage; + } + /** + * 取数据库文件生成文件管理的对象 + */ + public static void flush(){ + for(PageStorage storage : storages){ + storage.flush(); + } } + public static List storages = new ArrayList<>(); } diff --git a/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java b/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java index aec89c5..15c1c8e 100644 --- a/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java @@ -152,4 +152,43 @@ public void testFlush() throws PageException { assertArrayEquals(data4, dataFromFile4); } + public void testFlushAll() throws FileException, PageException { + PageStorage storage = PageManager.fromFile(filePath); + int[] testPage = new int[]{1, 3, 5, 7, 10, 11}; + // 测试每一页是否能正常读写 + for (var pageId : testPage) { + // 准备标志数据 + byte[] data = new byte[PageManager.PAGE_SIZE]; + Arrays.fill(data, (byte) (0xF0 | pageId)); + // 获取页 + var page = storage.get(pageId); + page.pin(); + // 写入页 + try { + // 写数据 + page.patchData(0, data); + } finally { + page.unpin(); + } + } + //将内存中的所有页都写回 + PageManager.flush(); + for (var pageId : testPage) { + byte[] data = new byte[PageManager.PAGE_SIZE]; + Arrays.fill(data, (byte) (0xF0 | pageId)); + // 获取页 + var page = storage.get(pageId); + page.pin(); + // 写入页 + try { + // 检查页数据 + var tempStorage = PageManager.fromFile(filePath); + var pageData = tempStorage.get(pageId).getDataBytes(); + assertArrayEquals(data, pageData); + } finally { + page.unpin(); + } + + } + } } \ No newline at end of file diff --git a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java index 297a118..8ab58a2 100644 --- a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java @@ -5,6 +5,7 @@ import net.kaaass.rumbase.record.exception.RecordNotFoundException; import net.kaaass.rumbase.transaction.TransactionContext; +import java.io.File; import java.util.UUID; import static org.junit.Assert.assertArrayEquals; @@ -60,6 +61,7 @@ public void testDelete() throws RecordNotFoundException { } public void testMetadata() { + new File(PATH + "test_metadata").deleteOnExit(); var storage = RecordManager.fromFile(PATH + "test_metadata"); var context = TransactionContext.empty(); From dbd35e2785fd30fe0f17489023fc0c55cb6fce46 Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Sat, 16 Jan 2021 18:34:33 +0800 Subject: [PATCH 15/18] =?UTF-8?q?[+]=E5=AE=8C=E6=88=90=E8=A1=A8=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=A8=A1=E5=9D=97=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 将接口、mock改成普通类 * 将接口、mock改成普通类 * 使用访问者模式替代原先的switch匹配 * 去除visitor, 重构表管理 * 去除visitor, 重构表管理 * 重写jbbp解析方法,增加测试 * 增加表配置信息操作 * 增加表加载测试 * 增加系统表和测试 * 完成Select语句的语法树定义 * 完成Insert语句的语法树定义 * 完成Update语句的语法树定义 * 完成Delete语句的语法树定义 * 完成Create table语句的语法树定义 * 完成Create index语句的语法树定义 * 格式化代码 * 增加空字段,增加field的方法 * 增加query模块 * 更新测试用例的序列化格式 * 修复空值Condition * 增加创建索引测试 * 更改测试文件路径 * 字符串解析增加单引号 * 增加增删改、创建表的执行测试 * 更改测试路径 * 增加select测试 * 修复delete测试 * 将mock换为RecordManager * 完成todo/fixme * 增加获取所有文件路径的api Co-authored-by: Kevin Axel Manjaro --- .../rumbase/query/AbstractJoinExecutor.java | 39 ++ .../rumbase/query/ConditionExecutor.java | 60 ++ .../rumbase/query/CreateIndexExecutor.java | 52 ++ .../rumbase/query/CreateTableExecutor.java | 65 ++ .../kaaass/rumbase/query/DeleteExecutor.java | 76 +++ .../net/kaaass/rumbase/query/Executable.java | 26 + .../rumbase/query/InnerJoinExecutor.java | 121 ++++ .../kaaass/rumbase/query/InsertExecutor.java | 60 ++ .../kaaass/rumbase/query/ProjectExecutor.java | 55 ++ .../kaaass/rumbase/query/SelectExecutor.java | 98 +++ .../kaaass/rumbase/query/SortExecutor.java | 85 +++ .../kaaass/rumbase/query/UpdateExecutor.java | 93 +++ .../query/exception/ArgumentException.java | 31 + .../java/net/kaaass/rumbase/table/Table.java | 572 +++++++++++++++++ .../kaaass/rumbase/table/TableManager.java | 176 +++++ .../net/kaaass/rumbase/table/TableStatus.java | 17 + .../exception/TableConflictException.java | 33 + .../exception/TableExistenceException.java | 44 ++ .../kaaass/rumbase/table/field/BaseField.java | 319 +++++++++ .../kaaass/rumbase/table/field/FieldType.java | 36 ++ .../rumbase/table/field/FloatField.java | 314 +++++++++ .../kaaass/rumbase/table/field/IntField.java | 312 +++++++++ .../rumbase/table/field/VarcharField.java | 290 +++++++++ .../query/CreateIndexExecutorTest.java | 101 +++ .../query/CreateTableExecutorTest.java | 72 +++ .../rumbase/query/DeleteExecutorTest.java | 191 ++++++ .../rumbase/query/InsertExecutorTest.java | 104 +++ .../rumbase/query/SelectExecutorTest.java | 201 ++++++ .../rumbase/query/UpdateExecutorTest.java | 215 +++++++ .../kaaass/rumbase/table/BaseFieldTest.java | 547 ++++++++++++++++ .../rumbase/table/TableManagerTest.java | 124 ++++ .../net/kaaass/rumbase/table/TableTest.java | 604 ++++++++++++++++++ 32 files changed, 5133 insertions(+) create mode 100644 src/main/java/net/kaaass/rumbase/query/AbstractJoinExecutor.java create mode 100644 src/main/java/net/kaaass/rumbase/query/ConditionExecutor.java create mode 100644 src/main/java/net/kaaass/rumbase/query/CreateIndexExecutor.java create mode 100644 src/main/java/net/kaaass/rumbase/query/CreateTableExecutor.java create mode 100644 src/main/java/net/kaaass/rumbase/query/DeleteExecutor.java create mode 100644 src/main/java/net/kaaass/rumbase/query/Executable.java create mode 100644 src/main/java/net/kaaass/rumbase/query/InnerJoinExecutor.java create mode 100644 src/main/java/net/kaaass/rumbase/query/InsertExecutor.java create mode 100644 src/main/java/net/kaaass/rumbase/query/ProjectExecutor.java create mode 100644 src/main/java/net/kaaass/rumbase/query/SelectExecutor.java create mode 100644 src/main/java/net/kaaass/rumbase/query/SortExecutor.java create mode 100644 src/main/java/net/kaaass/rumbase/query/UpdateExecutor.java create mode 100644 src/main/java/net/kaaass/rumbase/query/exception/ArgumentException.java create mode 100644 src/main/java/net/kaaass/rumbase/table/Table.java create mode 100644 src/main/java/net/kaaass/rumbase/table/TableManager.java create mode 100644 src/main/java/net/kaaass/rumbase/table/TableStatus.java create mode 100644 src/main/java/net/kaaass/rumbase/table/exception/TableConflictException.java create mode 100644 src/main/java/net/kaaass/rumbase/table/exception/TableExistenceException.java create mode 100644 src/main/java/net/kaaass/rumbase/table/field/BaseField.java create mode 100644 src/main/java/net/kaaass/rumbase/table/field/FieldType.java create mode 100644 src/main/java/net/kaaass/rumbase/table/field/FloatField.java create mode 100644 src/main/java/net/kaaass/rumbase/table/field/IntField.java create mode 100644 src/main/java/net/kaaass/rumbase/table/field/VarcharField.java create mode 100644 src/test/java/net/kaaass/rumbase/query/CreateIndexExecutorTest.java create mode 100644 src/test/java/net/kaaass/rumbase/query/CreateTableExecutorTest.java create mode 100644 src/test/java/net/kaaass/rumbase/query/DeleteExecutorTest.java create mode 100644 src/test/java/net/kaaass/rumbase/query/InsertExecutorTest.java create mode 100644 src/test/java/net/kaaass/rumbase/query/SelectExecutorTest.java create mode 100644 src/test/java/net/kaaass/rumbase/query/UpdateExecutorTest.java create mode 100644 src/test/java/net/kaaass/rumbase/table/BaseFieldTest.java create mode 100644 src/test/java/net/kaaass/rumbase/table/TableManagerTest.java create mode 100644 src/test/java/net/kaaass/rumbase/table/TableTest.java diff --git a/src/main/java/net/kaaass/rumbase/query/AbstractJoinExecutor.java b/src/main/java/net/kaaass/rumbase/query/AbstractJoinExecutor.java new file mode 100644 index 0000000..68979aa --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/AbstractJoinExecutor.java @@ -0,0 +1,39 @@ +package net.kaaass.rumbase.query; + +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.parse.ColumnIdentifier; + +import net.kaaass.rumbase.parse.stmt.SelectStatement; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.util.List; + +/** + * + * @author @KveinAxel + */ +@RequiredArgsConstructor +public abstract class AbstractJoinExecutor implements Executable{ + + @NonNull + protected final String fromTable; + + @NonNull + protected final List joins; + + @NonNull + protected final TableManager manager; + + @NonNull + protected final TransactionContext context; + + @Getter + protected List resultIdr; + + @Getter + protected List> resultRows; + +} diff --git a/src/main/java/net/kaaass/rumbase/query/ConditionExecutor.java b/src/main/java/net/kaaass/rumbase/query/ConditionExecutor.java new file mode 100644 index 0000000..2e66305 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/ConditionExecutor.java @@ -0,0 +1,60 @@ +package net.kaaass.rumbase.query; + +import lombok.*; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.ConditionExpression; + +import java.util.*; + +/** + * @author @KveinAxel + */ +@RequiredArgsConstructor +public class ConditionExecutor implements Executable { + + @NonNull + private final List idrs; + + @NonNull + private final List> tableData; + + @NonNull + private final ConditionExpression expression; + + @Getter + private final List> result = new ArrayList<>(); + + @Override + public void execute() { + + var params = expression.getParams(); + var len = params.size(); + var paramMap = new HashMap(len); + boolean ok; + for (var row: tableData) { + paramMap.clear(); + ok = true; + for (var param: params) { + for (int i = 0; i < idrs.size(); i++) { + var idr = idrs.get(i); + if (idr.getTableName().equals(param.getTableName()) && idr.getFieldName().equals(param.getFieldName())) { + var val = row.get(i); + if (val == null) { + ok = false; + } else { + paramMap.put(idr, val); + } + break; + } + } + if (!ok) { + break; + } + } + if (ok && expression.evaluate(paramMap)) { + result.add(row); + } + } + + } +} diff --git a/src/main/java/net/kaaass/rumbase/query/CreateIndexExecutor.java b/src/main/java/net/kaaass/rumbase/query/CreateIndexExecutor.java new file mode 100644 index 0000000..804e756 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/CreateIndexExecutor.java @@ -0,0 +1,52 @@ +package net.kaaass.rumbase.query; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.parse.stmt.CreateIndexStatement; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.BaseField; + +/** + * + * + * @author @KveinAxel + */ +@RequiredArgsConstructor +public class CreateIndexExecutor implements Executable{ + + @NonNull + private final CreateIndexStatement statement; + + @NonNull + private final TableManager manager; + + @Override + public void execute() throws TableExistenceException, IndexAlreadyExistException { + var table = manager.getTable(statement.getTableName()); + BaseField field = null; + + boolean ok; + for (var tableField: table.getFields()) { + ok = false; + for (var column: statement.getColumns()) { + if (tableField.getName().equals(column.getFieldName())) { + field = tableField; + field.setIndexName(statement.getIndexName()); + ok = true; + break; + } + } + if (ok) { + break; + } + } + + if (field != null) { + field.createIndex(); + } else { + throw new TableExistenceException(2); + } + } +} diff --git a/src/main/java/net/kaaass/rumbase/query/CreateTableExecutor.java b/src/main/java/net/kaaass/rumbase/query/CreateTableExecutor.java new file mode 100644 index 0000000..f146a0c --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/CreateTableExecutor.java @@ -0,0 +1,65 @@ +package net.kaaass.rumbase.query; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.parse.stmt.CreateTableStatement; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.*; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.util.ArrayList; +import java.util.Locale; + +/** + * @author @KveinAxel + */ +@RequiredArgsConstructor +public class CreateTableExecutor implements Executable { + + @NonNull + private final CreateTableStatement statement; + + @NonNull + private final TableManager manager; + + @NonNull + private final TransactionContext context; + + @Override + public void execute() throws TableExistenceException, TableConflictException, ArgumentException { + var tableName = statement.getTableName(); + var baseFields = new ArrayList(); + var dummyTable = new Table(tableName, baseFields); + boolean nullable; + for (var def : statement.getColumnDefinitions()) { + nullable = !def.isNotNull(); + var fieldName = def.getColumnName(); + var fieldType = FieldType.valueOf(def.getColumnType().getTypeName().toUpperCase(Locale.ROOT)); + + try { + switch (fieldType) { + case INT: + baseFields.add(new IntField(fieldName, nullable, dummyTable)); + break; + case FLOAT: + baseFields.add(new FloatField(fieldName, nullable, dummyTable)); + break; + case VARCHAR: + baseFields.add(new VarcharField(fieldName, Integer.parseInt(def.getColumnType().getArguments().get(0)), nullable, dummyTable)); + break; + default: + throw new TableConflictException(1); + } + } catch (NumberFormatException | IndexOutOfBoundsException e) { + throw new ArgumentException(1); + } + + } + + manager.createTable(context, tableName, baseFields, tableName + ".db"); + } +} diff --git a/src/main/java/net/kaaass/rumbase/query/DeleteExecutor.java b/src/main/java/net/kaaass/rumbase/query/DeleteExecutor.java new file mode 100644 index 0000000..16befdf --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/DeleteExecutor.java @@ -0,0 +1,76 @@ +package net.kaaass.rumbase.query; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.stmt.DeleteStatement; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * + * @author @KveinAxel + */ +@RequiredArgsConstructor +public class DeleteExecutor implements Executable{ + + @NonNull + private final DeleteStatement statement; + + @NonNull + private final TableManager manager; + + @NonNull + private final TransactionContext context; + + @Override + public void execute() throws TableExistenceException, ArgumentException, IndexAlreadyExistException, TableConflictException, RecordNotFoundException { + var table = manager.getTable(statement.getTableName()); + var idrs = new ArrayList(); + table.getFields().forEach(f -> idrs.add(new ColumnIdentifier(table.getTableName(), f.getName()))); + idrs.add(new ColumnIdentifier("__reserved__", "id")); + + var field = table.getFirstIndexedField(); + if (field == null) { + throw new ArgumentException(2); + } + + List> rows = new ArrayList<>(); + var iter = table.searchAll(field.getName()); + while(iter.hasNext()) { + var uuid = iter.next().getUuid(); + List> finalRows = rows; + table.read(context, uuid).ifPresent(row -> { + row.add(uuid); + finalRows.add(row); + }); + } + + var where = statement.getWhere(); + if (where != null) { + + var conditionExe = new ConditionExecutor(idrs, rows, where); + conditionExe.execute(); + rows = conditionExe.getResult(); + + } + + for (var row: rows) { + try { + table.delete(context, (long) row.get(row.size() - 1)); + } catch (ClassCastException e) { + throw new RuntimeException(e); + } + } + + } +} diff --git a/src/main/java/net/kaaass/rumbase/query/Executable.java b/src/main/java/net/kaaass/rumbase/query/Executable.java new file mode 100644 index 0000000..e017f1f --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/Executable.java @@ -0,0 +1,26 @@ +package net.kaaass.rumbase.query; + +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; + +/** + * 执行接口,实现该接口可以接受Statement语句并执行 + * + * @author @KveinAxel + */ +public interface Executable { + + /** + * 执行器的执行接口 + * + * @throws IndexAlreadyExistException 索引已存在 + * @throws TableConflictException 表模块冲突 + * @throws ArgumentException sql参数异常 + * @throws RecordNotFoundException 记录未找到 + * @throws TableExistenceException 表存在性异常 + */ + void execute() throws TableExistenceException, IndexAlreadyExistException, TableConflictException, ArgumentException, RecordNotFoundException; +} diff --git a/src/main/java/net/kaaass/rumbase/query/InnerJoinExecutor.java b/src/main/java/net/kaaass/rumbase/query/InnerJoinExecutor.java new file mode 100644 index 0000000..35008d9 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/InnerJoinExecutor.java @@ -0,0 +1,121 @@ +package net.kaaass.rumbase.query; + +import lombok.NonNull; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.stmt.SelectStatement; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * + * + * @author @KveinAxel + */ +public class InnerJoinExecutor extends AbstractJoinExecutor{ + + + public InnerJoinExecutor(@NonNull String fromTable, @NonNull List joins, TableManager manager, TransactionContext context) { + super(fromTable, joins, manager, context); + } + + @Override + public void execute() throws TableExistenceException, TableConflictException, ArgumentException, RecordNotFoundException { + var table = manager.getTable(fromTable); + var rows = table.readAll(context); + + var idrs = new ArrayList(); + table + .getFields() + .forEach( + f -> idrs.add(new ColumnIdentifier(table.getTableName(), f.getName())) + ); + + + for (var join: joins) { + var joinTable = manager.getTable(join.getTableName()); + var joinRows = joinTable.readAll(context); + var intermediateRows = new ArrayList>(); + var joinIdrs = new ArrayList(); + + joinTable + .getFields() + .forEach( + f -> joinIdrs.add(new ColumnIdentifier(joinTable.getTableName(), f.getName())) + ); + + var len = idrs.size(); + var joinLen = joinIdrs.size(); + var paramMap = new HashMap(idrs.size()); + var params = join.getJoinOn().getParams(); + boolean ok; + + for (var row: rows) { + for (var joinRow: joinRows) { + paramMap.clear(); + ok = true; + + for (var param: params) { + + for (int i = 0; i < len; i++) { + var idr = idrs.get(i); + if (idr.getTableName().equals(param.getTableName()) && idr.getFieldName().equals(param.getFieldName())) { + var val = row.get(i); + if (val == null) { + ok = false; + } else { + paramMap.put(idr, val); + ok = true; + } + break; + } + } + if (!ok) { + break; + } + for (int i = 0; i < joinLen; i++) { + var idr = joinIdrs.get(i); + if (idr.getTableName().equals(param.getTableName()) && idr.getFieldName().equals(param.getFieldName())) { + var val = joinRow.get(i); + if (val == null) { + ok = false; + } else { + paramMap.put(idr, val); + ok = true; + } + break; + } + } + if (!ok) { + break; + } + } + if (ok && join.getJoinOn().evaluate(paramMap)) { + intermediateRows.add( + Stream + .of(row, joinRow) + .flatMap(Collection::stream) + .collect(Collectors.toList()) + ); + + } + + + } + } + + rows = intermediateRows; + idrs.addAll(joinIdrs); + } + + resultIdr = idrs; + resultRows = rows; + } +} diff --git a/src/main/java/net/kaaass/rumbase/query/InsertExecutor.java b/src/main/java/net/kaaass/rumbase/query/InsertExecutor.java new file mode 100644 index 0000000..b1cf71f --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/InsertExecutor.java @@ -0,0 +1,60 @@ +package net.kaaass.rumbase.query; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.parse.stmt.InsertStatement; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.BaseField; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.util.ArrayList; + +/** + * + * + * @author @KveinAxel + */ +@RequiredArgsConstructor +public class InsertExecutor implements Executable{ + + @NonNull + private final InsertStatement statement; + + @NonNull + private final TableManager manager; + + @NonNull + private final TransactionContext context; + + @Override + public void execute() throws TableExistenceException, TableConflictException, ArgumentException { + var table = manager.getTable(statement.getTableName()); + + var columns = statement.getColumns(); + var len = columns.size(); + var insertArray = new ArrayList(); + boolean ok; + + for (BaseField f : table.getFields()) { + ok = false; + for (int j = 0; j < len; j++) { + var insertField = columns.get(j); + if (f.getName().equals(insertField.getFieldName())) { + + insertArray.add(statement.getValues().get(j)); + ok = true; + + } + } + if (!ok) { + insertArray.add(""); + } + } + + table.insert(context, insertArray); + + } +} diff --git a/src/main/java/net/kaaass/rumbase/query/ProjectExecutor.java b/src/main/java/net/kaaass/rumbase/query/ProjectExecutor.java new file mode 100644 index 0000000..b836a32 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/ProjectExecutor.java @@ -0,0 +1,55 @@ +package net.kaaass.rumbase.query; + +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author @KveinAxel + */ +@RequiredArgsConstructor +public class ProjectExecutor implements Executable{ + + @NonNull + private final List idrs; + + @NonNull + private final List selectedColumns; + + @NonNull + private final List> data; + + @NonNull + @Getter + private final List> projectedResult = new ArrayList<>(); + + @Override + public void execute() throws TableExistenceException, IndexAlreadyExistException, TableConflictException, ArgumentException { + var newColumnsLen = selectedColumns.size(); + var indexList = new ArrayList(); + for (int i = 0; i < newColumnsLen; i++) { + var col = selectedColumns.get(i); + int finalI = i; + idrs.forEach(idr -> { + if (idr.getTableName().equals(col.getTableName()) && idr.getFieldName().equals(col.getFieldName())) { + indexList.add(finalI); + } + }); + } + + data.forEach(row -> { + var newRow = new ArrayList<>(); + indexList.forEach(index -> newRow.add(row.get(index))); + projectedResult.add(newRow); + }); + } +} diff --git a/src/main/java/net/kaaass/rumbase/query/SelectExecutor.java b/src/main/java/net/kaaass/rumbase/query/SelectExecutor.java new file mode 100644 index 0000000..b079cae --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/SelectExecutor.java @@ -0,0 +1,98 @@ +package net.kaaass.rumbase.query; + +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.stmt.SelectStatement; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +/** + * + * @author @KveinAxel + */ +@RequiredArgsConstructor +public class SelectExecutor implements Executable{ + + @NonNull + private final SelectStatement statement; + + @NonNull + private final TableManager manager; + + @NonNull + private final TransactionContext context; + + @Getter + private List resultTable; + + @Getter + private List> resultData; + + @Override + public void execute() throws TableConflictException, ArgumentException, TableExistenceException, IndexAlreadyExistException, RecordNotFoundException { + + + // 连接 + var joins = statement.getJoins(); + if (joins == null) { + joins = new ArrayList<>(); + } + var joinExe = new InnerJoinExecutor(statement.getFromTable(), joins, manager, context); + joinExe.execute(); + resultData = joinExe.resultRows; + resultTable = joinExe.resultIdr; + + // 选择 + var where = statement.getWhere(); + if (where != null) { + var conditionExe = new ConditionExecutor(resultTable, resultData, where); + conditionExe.execute(); + resultData = conditionExe.getResult(); + } + + // 排序 + var orderBys = statement.getOrderBys(); + if (orderBys != null) { + var sortExe = new SortExecutor(resultTable, resultData, orderBys, manager); + sortExe.execute(); + resultData = sortExe.getData(); + } + + // 投影 + var selectCols = statement.getSelectColumns(); + if (selectCols != null) { + var projectExe = new ProjectExecutor(resultTable, selectCols, resultData); + projectExe.execute(); + resultData = projectExe.getProjectedResult(); + resultTable = selectCols; + } + + // 去重 + // FIXME: 2021/1/16 存储过大 + if (statement.isDistinct()) { + var len = resultData.size(); + var hashSet = new HashSet>(len); + var resultList = new ArrayList>(len); + + for (var item: resultData) { + if (hashSet.add(item)) { + resultList.add(item); + } + } + + resultData = resultList; + } + + } +} diff --git a/src/main/java/net/kaaass/rumbase/query/SortExecutor.java b/src/main/java/net/kaaass/rumbase/query/SortExecutor.java new file mode 100644 index 0000000..bc86b2f --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/SortExecutor.java @@ -0,0 +1,85 @@ +package net.kaaass.rumbase.query; + +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.stmt.SelectStatement; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * + * @author @KveinAxel + */ +@RequiredArgsConstructor +public class SortExecutor implements Executable { + + @NonNull + private final List idrs; + + @Getter + @NonNull + private List> data; + + @NonNull + private final List orderBys; + + @NonNull + private final TableManager manager; + + + @Override + public void execute() throws TableExistenceException, IndexAlreadyExistException, TableConflictException, ArgumentException { + var len = idrs.size(); + for (var orderBy : orderBys) { + + int index = -1; + ColumnIdentifier orderIdr = null; + for (int i = 0; i < len; i++) { + var column = orderBy.getColumn(); + var idr = idrs.get(i); + if (idr.getTableName().equals(column.getTableName()) && idr.getFieldName().equals(column.getFieldName())) { + index = i; + orderIdr = idr; + break; + } + } + + if (index == -1) { + throw new ArgumentException(1); + } + + var table = manager.getTable(orderIdr.getTableName()); + var field = table + .getField(orderBy.getColumn().getFieldName()) + .orElseThrow(() -> new ArgumentException(1)); + + final int finalIndex = index; + if (orderBy.isAscending()) { + data = data.stream().sorted((list1, list2) -> { + try { + return field.compare(list1.get(finalIndex), list2.get(finalIndex)); + } catch (TableConflictException e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + } else { + data = data.stream().sorted((list1, list2) -> { + try { + return field.compare(list2.get(finalIndex), list1.get(finalIndex)); + } catch (TableConflictException e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + } + + } + } +} diff --git a/src/main/java/net/kaaass/rumbase/query/UpdateExecutor.java b/src/main/java/net/kaaass/rumbase/query/UpdateExecutor.java new file mode 100644 index 0000000..018eeb1 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/UpdateExecutor.java @@ -0,0 +1,93 @@ +package net.kaaass.rumbase.query; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.parse.ColumnIdentifier; +import net.kaaass.rumbase.parse.stmt.UpdateStatement; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * + * @author @KveinAxel + */ +@RequiredArgsConstructor +public class UpdateExecutor implements Executable{ + + @NonNull + private final UpdateStatement statement; + + @NonNull + private final TableManager manager; + + @NonNull + private final TransactionContext context; + + @Override + public void execute() throws TableExistenceException, ArgumentException, IndexAlreadyExistException, TableConflictException, RecordNotFoundException { + var table = manager.getTable(statement.getTableName()); + var idrs = new ArrayList(); + table.getFields().forEach(f -> idrs.add(new ColumnIdentifier(table.getTableName(), f.getName()))); + idrs.add(new ColumnIdentifier("__reserved__", "id")); + + var indexedField = table.getFirstIndexedField(); + if (indexedField == null) { + throw new ArgumentException(2); + } + + List> rows = new ArrayList<>(); + var iter = table.searchAll(indexedField.getName()); + while(iter.hasNext()) { + var uuid = iter.next().getUuid(); + List> finalRows = rows; + table.read(context, uuid).ifPresent(row -> { + row.add(uuid); + finalRows.add(row); + }); + } + + var where = statement.getWhere(); + if (where != null) { + var conditionExe = new ConditionExecutor(idrs, rows, where); + conditionExe.execute(); + rows = conditionExe.getResult(); + } + + var uuids = new ArrayList(); + + for (var row : rows) { + uuids.add((long) row.remove(row.size() - 1)); + } + + var fields = table.getFields(); + var len = fields.size(); + var rowLen = rows.size(); + var updateFieldLen = statement.getColumns().size(); + + for (int i = 0; i < len; i++) { + for (int j = 0; j < updateFieldLen; j++) { + var field = fields.get(i); + if (field.getName().equals(statement.getColumns().get(j).getFieldName())) { + for (var list : rows) { + var val = fields.get(i).strToValue(statement.getValues().get(j)); + list.set(i, val); + } + } + } + } + + for (int i = 0; i < rowLen; i++) { + table.updateObjs(context, uuids.get(i), rows.get(i)); + } + + } +} diff --git a/src/main/java/net/kaaass/rumbase/query/exception/ArgumentException.java b/src/main/java/net/kaaass/rumbase/query/exception/ArgumentException.java new file mode 100644 index 0000000..e748c7e --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/query/exception/ArgumentException.java @@ -0,0 +1,31 @@ +package net.kaaass.rumbase.query.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E2001 参数异常 + *

+ * E2001-1 列参数异常 + *

+ * E2001-2 请求不包含索引列 + * + * @author @KveinAxel + */ +public class ArgumentException extends RumbaseException { + public static final Map REASONS = new HashMap<>(){{ + put(1, "列参数异常"); + put(2, "请求不包含索引列"); + }}; + + /** + * 类型不匹配异常 + * + * @param subId 子错误号 + */ + public ArgumentException(int subId) { + super(2001, subId, REASONS.get(subId)); + } +} diff --git a/src/main/java/net/kaaass/rumbase/table/Table.java b/src/main/java/net/kaaass/rumbase/table/Table.java new file mode 100644 index 0000000..2f3110c --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/Table.java @@ -0,0 +1,572 @@ +package net.kaaass.rumbase.table; + +import com.igormaznitsa.jbbp.io.JBBPBitInputStream; +import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; +import com.igormaznitsa.jbbp.io.JBBPByteOrder; +import lombok.*; +import net.kaaass.rumbase.index.Pair; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.IRecordStorage; +import net.kaaass.rumbase.record.RecordManager; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.field.BaseField; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; + +/** + * 表结构 + *

+ * 验证数据是否满足表约束 + *

+ * 提供行数据解析服务 + * + * @author @KveinAxel + */ +@RequiredArgsConstructor +public class Table { + + /** + * 表名 + */ + @NonNull + @Getter + String tableName; + + /** + * 表所在文件的记录接口 + */ + @Getter + @NonNull + IRecordStorage recordStorage; + + /** + * 表的状态,可能的状态有: + *

+ * 正常 + *

+ *

+ * 被删除 + *

+ */ + @Getter + TableStatus status = TableStatus.NORMAL; + + /** + * 下一张表的uuid + */ + @Setter + @Getter + long next; + + /** + * 表结构拥有的字段 + */ + @Getter + @Setter + List fields = new ArrayList<>(); + + @Getter + private final String path; + + /** + * 直接通过表名、字段创建表 + *

+ * 不检测是否与已存在表冲突,这个留给表管理器处检测 + *

+ * + * @param tableName 表名 + * @param fields 表的字段结构 + */ + public Table(@NonNull String tableName, @NonNull List fields) { + this.recordStorage = RecordManager.fromFile(tableName); + this.tableName = tableName; + this.fields = fields; + this.next = -1; + this.path = tableName; + } + + /** + * 直接通过表名、字段创建表 + *

+ * 不检测是否与已存在表冲突,这个留给表管理器处检测 + *

+ * + * @param tableName 表名 + * @param fields 表的字段结构 + */ + public Table(@NonNull String tableName, @NonNull List fields, String path) { + this.recordStorage = RecordManager.fromFile(path); + this.tableName = tableName; + this.fields = fields; + this.next = -1; + this.path = path; + } + + + + /** + * 将当前表结构信息持久化到外存中 + */ + public void persist(TransactionContext context) { + + var byteOutStream = new ByteArrayOutputStream(); + var out = new JBBPBitOutputStream(byteOutStream); + + try { + out.writeString(tableName, JBBPByteOrder.BIG_ENDIAN); + out.writeString(status.toString().toUpperCase(Locale.ROOT), JBBPByteOrder.BIG_ENDIAN); + out.writeLong(next, JBBPByteOrder.BIG_ENDIAN); + out.writeInt(fields.size(), JBBPByteOrder.BIG_ENDIAN); + for (var f: fields) { + f.persist(byteOutStream); + } + } catch (IOException e) { + e.printStackTrace(); + } + + recordStorage.setMetadata(context, byteOutStream.toByteArray()); + } + + public static Table load(IRecordStorage recordStorage) { + + // fixme context + var context = TransactionContext.empty(); + var meta = recordStorage.getMetadata(context); + var stream = new ByteArrayInputStream(meta); + var in = new JBBPBitInputStream(stream); + try { + var name = in.readString(JBBPByteOrder.BIG_ENDIAN); + var status = in.readString(JBBPByteOrder.BIG_ENDIAN); + var next = in.readLong(JBBPByteOrder.BIG_ENDIAN); + var fieldNum = in.readInt(JBBPByteOrder.BIG_ENDIAN); + var fieldList = new ArrayList(); + var table = new Table(name, fieldList); + for (int i = 0; i < fieldNum; i++) { + var f = BaseField.load(stream, table); + if (f != null) { + fieldList.add(f); + } + } + table.next = next; + table.status = TableStatus.valueOf(status); + return table; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 删除元组 + * + * @param context 事务context + * @param uuid 元组的uuid + */ + public void delete(TransactionContext context, long uuid) throws RecordNotFoundException { + recordStorage.delete(context, uuid); + } + + /** + * 更新元组 + * + * @param context 事务context + * @param uuid 元组的uuid + * @param entry 新的行的字符串值列表 + */ + public void update(TransactionContext context, long uuid, List entry) throws TableConflictException, TableExistenceException, RecordNotFoundException { + + if(!checkStringEntry(entry)) { + throw new TableConflictException(3); + } + + var raw = stringEntryToBytes(entry); + + recordStorage.delete(context, uuid); + var newUuid = recordStorage.insert(context, raw); + + var l = entry.size(); + for (int i = 0; i < l; i++) { + var field = fields.get(i); + if (field.indexed()) { + field.insertIndex(entry.get(i), newUuid); + } + } + } + + + /** + * 更新元组 + * + * @param context 事务context + * @param uuid 元组的uuid + * @param entry 新的行的值列表 + */ + public void updateObjs(TransactionContext context, long uuid, List entry) throws TableConflictException, TableExistenceException, RecordNotFoundException { + + var raw = entryToBytes(entry); + + recordStorage.delete(context, uuid); + var newUuid = recordStorage.insert(context, raw); + + var l = entry.size(); + for (int i = 0; i < l; i++) { + var field = fields.get(i); + if (field.indexed()) { + field.insertIndex(entry.get(i), newUuid); + } + } + } + + + + + /** + * 检查一个entry是否满足当前表的约束 + * + * @param entry 待检查entry + * @return 满足情况 + */ + public boolean checkEntry(List entry) { + if (fields.size() != entry.size()) { + return false; + } + + var len = fields.size(); + + for (int i = 0; i < len; i++) { + if (!fields.get(i).checkObject(entry.get(i))) { + return false; + } + } + + return true; + } + + + /** + * 检查一个entry是否满足当前表的约束 + * + * @param entry 待检查entry + * @return 满足情况 + */ + public boolean checkStringEntry(List entry) { + if (fields.size() != entry.size()) { + return false; + } + + var len = fields.size(); + + for (int i = 0; i < len; i++) { + if (!fields.get(i).checkStr(entry.get(i))) { + return false; + } + } + + return true; + } + + /** + * 获取一个元组内容 + * + * @param context 事务context + * @param uuid 元组的uuid + * @return 元组 + * @throws TableExistenceException 要查询的表不存在 + * @throws TableConflictException 查询到的entry和当前表冲突 + */ + public Optional> read(TransactionContext context, long uuid) throws TableExistenceException, TableConflictException, RecordNotFoundException { + + var bytes = recordStorage + .queryOptional(context, uuid); + + if (bytes.isPresent()) { + try { + return Optional.of(parseEntry(bytes.get())); + } catch (IOException e) { + // fixme 不该出现这样的事情(吧) + // 查询到的entry和当前表冲突 + throw new TableConflictException(3); } + } else { + return Optional.empty(); + } + + } + + /** + * 读取表所有记录 + * + * @param context 事务context + * @return 所有记录 + */ + public List> readAll(TransactionContext context) throws TableExistenceException, TableConflictException, ArgumentException, RecordNotFoundException { + var field = getFirstIndexedField(); + if (field == null) { + throw new ArgumentException(2); + } + + var rows = new ArrayList>(); + var iter = searchAll(field.getName()); + while(iter.hasNext()) { + var uuid = iter.next().getUuid(); + read(context, uuid).ifPresent(rows::add); + } + return rows; + } + + /** + * 向表插入元组 + * + * @param context 事务context + * @param entry 新的元组 + * @throws TableConflictException 插入的元组不满足表约束 + */ + public void insert(TransactionContext context, List entry) throws TableConflictException, TableExistenceException, ArgumentException { + + var bytes = stringEntryToBytes(entry); + + var uuid = recordStorage.insert(context, bytes); + + var l = entry.size(); + boolean ok = false; + for (int i = 0; i < l; i++) { + var field = fields.get(i); + if (field.indexed()) { + field.insertIndex(entry.get(i), uuid); + ok = true; + } + } + + if (!ok) { + throw new ArgumentException(2); + } + } + + /** + * @param fieldName 字段名 + * @param fieldValue 字段值 + * @return 查询到的uuid列表 + * @throws TableExistenceException 要查询的表不存在 + */ + public List search(String fieldName, String fieldValue) throws TableExistenceException, TableConflictException { + BaseField field = null; + + for (var f: fields) { + if (f.getName().equals(fieldName)) { + field = f; + } + } + + if (field == null) { + throw new TableExistenceException(2); + } + + return field.queryIndex(fieldValue); + } + + /** + * @param fieldName 字段名 + * @param fieldValue 字段值 + * @return 查询到的uuid列表 + * @throws TableExistenceException 要查询的表不存在 + */ + public List search(String fieldName, Object fieldValue) throws TableExistenceException, TableConflictException { + BaseField field = null; + + for (var f: fields) { + if (f.getName().equals(fieldName)) { + field = f; + } + } + + if (field == null) { + throw new TableExistenceException(2); + } + + return field.queryIndex(fieldValue); + } + + + + /** + * 以指定的索引查询全部记录 + *

+ * 返回第一个记录的迭代器,以(字段值, 记录uuid)的方式返回 + *

+ * 返回的uuid不保证可见 + * + * @param fieldName 字段名 + * @return (字段值, 记录uuid)形式的第一个迭代器 + * @throws TableExistenceException 字段不存在(2), 索引不存在(6) + */ + public Iterator searchAll(String fieldName) throws TableExistenceException { + BaseField field = null; + + for (var f: fields) { + if (f.getName().equals(fieldName)) { + field = f; + } + } + + if (field == null) { + throw new TableExistenceException(2); + } + + return field.queryFirst(); + } + + + /** + * 查询第一个满足字段值的迭代器 + *

+ * 返回第一个记录的迭代器,以(字段值, 记录uuid)的方式返回 + *

+ * 返回的uuid不保证可见 + * + * @param fieldName 字段名 + * @param key 字段值 + * @return (字段值, 记录uuid)形式的第一个迭代器 + * @throws TableExistenceException 字段不存在(2), 索引不存在(6) + * @throws TableConflictException 字段类型不匹配 + */ + public Iterator searchFirst(String fieldName, String key) throws TableExistenceException, TableConflictException { + BaseField field = null; + + for (var f: fields) { + if (f.getName().equals(fieldName)) { + field = f; + } + } + + if (field == null) { + throw new TableExistenceException(2); + } + + return field.queryFirstMeet(key); + } + + /** + * 查询第一个满足值且与查询键不相等的迭代器 + *

+ * 返回第一个记录的迭代器,以(字段值, 记录uuid)的方式返回 + *

+ * 返回的uuid不保证可见 + * + * @param fieldName 字段名 + * @return (字段值, 记录uuid)形式的第一个迭代器 + * @throws TableExistenceException 字段不存在(2), 索引不存在(6) + */ + public Iterator searchFirstNotEqual(String fieldName, String key) throws TableExistenceException, TableConflictException { + BaseField field = null; + + for (var f: fields) { + if (f.getName().equals(fieldName)) { + field = f; + } + } + + if (field == null) { + throw new TableExistenceException(2); + } + + return field.queryFirstMeetNotEqual(key); + } + + /** + * 将entry转换成字节数组 + * + * @param entry 元组 + * @return 字节数组 + */ + public byte[] stringEntryToBytes(List entry) throws TableConflictException { + var stream = new ByteArrayOutputStream(); + + if (!checkStringEntry(entry)) { + throw new TableConflictException(3); + } + + var len = fields.size(); + + for (int i = 0; i < len; i++) { + fields.get(i).serialize(stream, entry.get(i)); + } + + return stream.toByteArray(); + } + + + /** + * 将entry转换成字节数组 + * + * @param entry 元组 + * @return 字节数组 + */ + public byte[] entryToBytes(List entry) throws TableConflictException { + var stream = new ByteArrayOutputStream(); + + if (!checkEntry(entry)) { + throw new TableConflictException(3); + } + + var len = fields.size(); + + for (int i = 0; i < len; i++) { + fields.get(i).serialize(stream, entry.get(i)); + } + + return stream.toByteArray(); + } + + + + /** + * 将字节数组转换成entry + * + * @param raw 字节数组 + * @return 元组 + */ + public List parseEntry(byte[] raw) throws TableConflictException, IOException { + var stream = new ByteArrayInputStream(raw); + var list = new ArrayList<>(); + + for (var field : fields) { + list.add(field.deserialize(stream)); + } + + if (stream.available() == 0) { + return list; + } else { + throw new TableConflictException(2); + } + } + + /** + * 返回第一个建立了索引的列 + * + * @return 列 + */ + public BaseField getFirstIndexedField() { + for (var f: fields) { + if (f.indexed()) { + return f; + } + } + return null; + } + + + public Optional getField(String fieldName) { + for (var f : fields) { + if (f.getName().equals(fieldName)) { + return Optional.of(f); + } + } + + return Optional.empty(); + } +} \ No newline at end of file diff --git a/src/main/java/net/kaaass/rumbase/table/TableManager.java b/src/main/java/net/kaaass/rumbase/table/TableManager.java new file mode 100644 index 0000000..8ecdc38 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/TableManager.java @@ -0,0 +1,176 @@ +package net.kaaass.rumbase.table; + +import com.igormaznitsa.jbbp.io.JBBPBitInputStream; +import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; +import com.igormaznitsa.jbbp.io.JBBPByteOrder; +import lombok.Getter; +import net.kaaass.rumbase.record.IRecordStorage; +import net.kaaass.rumbase.record.RecordManager; +import net.kaaass.rumbase.table.field.BaseField; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; + +/** + * 表管理器 + *

+ * ITableManager用于管理表结构, 已经为上层模块提供更加高级和抽象的接口. + *

+ * ITableManager会依赖index模块进行索引, 依赖record模块进行表单数据查找. + *

+ * + * @author @KveinAxel + */ + +public class TableManager { + + private final IRecordStorage metaRecord = RecordManager.fromFile("metadata.db"); + + /** + * 对所有的表结构的缓存 + *

+ * 如果不在这里,说明就没有这张表 + *

+ * 同样地,创建表、删除表需要维护这个结构 + */ + private final Map tableCache = new HashMap<>(); + + @Getter + private final List recordPaths = new ArrayList<>(); + + + /** + * 提交一个事务 + * + * @param context 事务context + */ + public void commit(TransactionContext context) { + context.commit(); + } + + /** + * 终止一个事务 + * + * @param context 事务context + */ + public void abort(TransactionContext context) { + context.rollback(); + } + + public TableManager() { + load(); + } + + public void load() { + var context = TransactionContext.empty(); + var meta = metaRecord.getMetadata(context); + + var byteInStream = new ByteArrayInputStream(meta); + var stream = new JBBPBitInputStream(byteInStream); + + int num; + try { + num = stream.readInt(JBBPByteOrder.BIG_ENDIAN); + } catch (IOException e) { + return; + } + for (int i = 0; i < num; i++) { + try { + var key = stream.readString(JBBPByteOrder.BIG_ENDIAN); + var val = stream.readString(JBBPByteOrder.BIG_ENDIAN); + // 加载表 + if (key.startsWith("tablePath$")) { + var tableName = key.split("\\$")[1]; + var record = RecordManager.fromFile(val); + recordPaths.add(val); + var table = Table.load(record); + tableCache.put(tableName, table); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + + /** + * 显示所有的表名 + * + * @return 表名的列表 + */ + public List showTables() { + List list = new ArrayList<>(); + + tableCache.forEach((k, v) -> list.add(k)); + + return list; + } + + /** + * 创建一个表 + * + * @param context 事务context + * @param tableName 表名 + * @param baseFields 表的字段 + * @throws TableExistenceException 该表已存在 + */ + public void createTable( + TransactionContext context, + String tableName, + List baseFields, + String path + ) throws TableExistenceException { + if (tableCache.containsKey(tableName)) { + throw new TableExistenceException(1); + } + + var table = new Table(tableName, baseFields, path); + + for (var f: baseFields) { + f.setParentTable(table); + } + + table.persist(context); + + var meta = metaRecord.getMetadata(TransactionContext.empty()); + + var in = new ByteArrayInputStream(meta); + var inStream = new JBBPBitInputStream(in); + + int cnt; + try { + cnt = inStream.readInt(JBBPByteOrder.BIG_ENDIAN); + + } catch (IOException e) { + cnt = 0; + } + + var byteOutStream = new ByteArrayOutputStream(); + var stream = new JBBPBitOutputStream(byteOutStream); + try { + stream.writeInt(cnt + 1, JBBPByteOrder.BIG_ENDIAN); + stream.write(in.readAllBytes()); + stream.writeString("tablePath$" + tableName, JBBPByteOrder.BIG_ENDIAN); + stream.writeString(path, JBBPByteOrder.BIG_ENDIAN); + } catch (IOException e) { + e.printStackTrace(); + } + + tableCache.put(tableName, table); + } + + public Table getTable(String tableName) throws TableExistenceException { + var table = tableCache.get(tableName); + + if (table == null) { + throw new TableExistenceException(1); + } else { + return table; + } + } + +} diff --git a/src/main/java/net/kaaass/rumbase/table/TableStatus.java b/src/main/java/net/kaaass/rumbase/table/TableStatus.java new file mode 100644 index 0000000..cdf5441 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/TableStatus.java @@ -0,0 +1,17 @@ +package net.kaaass.rumbase.table; + +/** + * 表状态的枚举 + * + * @author @KveinAxel + */ +public enum TableStatus { + /** + * 正常状态 + */ + NORMAL, + /** + * 表已经被删除(待定) + */ + DELETED, +} \ No newline at end of file diff --git a/src/main/java/net/kaaass/rumbase/table/exception/TableConflictException.java b/src/main/java/net/kaaass/rumbase/table/exception/TableConflictException.java new file mode 100644 index 0000000..5688d83 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/exception/TableConflictException.java @@ -0,0 +1,33 @@ +package net.kaaass.rumbase.table.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E3002 类型不匹配异常 + *

+ * E3002-1 字段类型不匹配 + *

+ * E3002-2 Entry类型不匹配 + *

+ * E3002-3 字段不满足约束 + * @author @KveinAxel + */ +public class TableConflictException extends RumbaseException { + public static final Map REASONS = new HashMap<>(){{ + put(1, "字段类型不匹配"); + put(2, "Entry不匹配"); + put(3, "字段不满足约束"); + }}; + + /** + * 类型不匹配异常 + * + * @param subId 子错误号 + */ + public TableConflictException(int subId) { + super(3002, subId, REASONS.get(subId)); + } +} diff --git a/src/main/java/net/kaaass/rumbase/table/exception/TableExistenceException.java b/src/main/java/net/kaaass/rumbase/table/exception/TableExistenceException.java new file mode 100644 index 0000000..1dcaced --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/exception/TableExistenceException.java @@ -0,0 +1,44 @@ +package net.kaaass.rumbase.table.exception; + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E3001 关系不存在异常 + *

+ * E3001-1 关系不存在 + *

+ * E3001-2 字段不存在 + *

+ * E3001-3 视图不存在 + *

+ * E3001-4 元组不存在 + *

+ * E3001-5 关系已存在 + *

+ * E3001-6 索引不存在 + * + * + * @author @KveinAxel + */ +public class TableExistenceException extends RumbaseException { + public static final Map REASONS = new HashMap<>(){{ + put(1, "关系不存在"); + put(2, "字段不存在"); + put(3, "视图不存在"); + put(4, "元组不存在"); + put(5, "关系已存在"); + put(6, "索引不存在"); + }}; + + /** + * 关系不存在异常 + * + * @param subId 子错误号 + */ + public TableExistenceException(int subId) { + super(3001, subId, REASONS.get(subId)); + } +} diff --git a/src/main/java/net/kaaass/rumbase/table/field/BaseField.java b/src/main/java/net/kaaass/rumbase/table/field/BaseField.java new file mode 100644 index 0000000..40dc924 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/field/BaseField.java @@ -0,0 +1,319 @@ +package net.kaaass.rumbase.table.field; + +import com.igormaznitsa.jbbp.io.JBBPBitInputStream; +import com.igormaznitsa.jbbp.io.JBBPByteOrder; +import lombok.*; +import net.kaaass.rumbase.index.Index; +import net.kaaass.rumbase.index.Pair; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.List; + + +/** + * 字段结构 + *

+ * 提供字段解析服务 + *

+ * 提供字段的索引处理 + * + * @author @KveinAxel + */ +@RequiredArgsConstructor +public abstract class BaseField{ + + /** + * 字段名 + */ + @Getter + @NonNull + protected final String name; + + /** + * 字段类型 + */ + @Getter + @NonNull + private final FieldType type; + + /** + * 字段是否可空 + */ + @Getter + private final boolean nullable; + + /** + * 索引 + *

+ * 如果未建立索引则为空 + */ + protected Index index = null; + + /** + * 索引名 + */ + @Getter + @Setter + protected String indexName; + + /** + * 当前列所属的表 + */ + @NonNull + @Getter + @Setter + private Table parentTable; + + /** + * 向输出流中写入当前字段格式信息 + *

+ * 格式为: + *

+ * 列名 + *

+ * 列类型 + *

+ * 是否可空 + *

+ * 是否建立索引 + *

+ * 索引名(如果建立索引) + *

+ * 列参数(可选) + *

+ * + * @param stream 输出流 + */ + public abstract void persist(OutputStream stream); + + /** + * 从输入流中读入当前字段格式信息,并构造、返回当前字段 + * + * @param stream 输入流 + * @return 字段 + */ + public static BaseField load(InputStream stream, Table table) { + var in = new JBBPBitInputStream(stream); + + try { + var name = in.readString(JBBPByteOrder.BIG_ENDIAN); + var type = FieldType.valueOf(in.readString(JBBPByteOrder.BIG_ENDIAN)); + var flag = in.readByte(); + var nullable = (flag & 1) == 1; + var indexed = (flag & 2) == 2; + String indexName = null; + if (indexed) { + indexName = in.readString(JBBPByteOrder.BIG_ENDIAN); + } + // todo 重建索引 + + BaseField field; + switch (type) { + case INT: + field = new IntField(name, nullable, table); + field.setIndexName(indexName); + return field; + case FLOAT: + field = new FloatField(name, nullable, table); + field.setIndexName(indexName); + return field; + default: + field = new VarcharField(name, in.readInt(JBBPByteOrder.BIG_ENDIAN), nullable, table); + field.setIndexName(indexName); + return field; + } + } catch (IOException e) { + // todo + e.printStackTrace(); + } + return null; + } + + /** + * 判断字符串是否能够转成符合当前字段约束的值 + * @param valStr 待检查字符串 + * @return 满足情况 + */ + public abstract boolean checkStr(String valStr); + + /** + * 将字符串转成哈希 + * @param str 待转换字符串 + * @throws TableConflictException 字段类型不匹配 + * @return 哈希 + */ + public abstract long strToHash(String str) throws TableConflictException; + + + /** + * 将值对象转成哈希 + * @param val 值对象 + * @throws TableConflictException 字段类型不匹配 + * @return 哈希 + */ + public abstract long toHash(Object val) throws TableConflictException; + + /** + * 从输入流中反序列化出一个满足当前字段约束的值对象 + * @param inputStream 输入流 + * @throws TableConflictException 输入流中读出对象与字段类型不匹配 + * @return 值对象 + */ + public abstract Object deserialize(InputStream inputStream) throws TableConflictException; + + /** + * 从输入流中反序列化一个满足当前类型的对象,并判断是否满足约束 + * @param inputStream 输入流 + * @return 满足情况 + */ + public abstract boolean checkInputStream(InputStream inputStream); + + /** + * 将值对象序列化到输出流中 + *

+ * 先序列化一位isNull,1 -> null ; 0 -> 非null + *

+ * 如果非null则继续序列化他的值 + * + * @param outputStream 输出流 + * @param strVal 值对象 + * @throws TableConflictException 类型不匹配 + */ + public abstract void serialize(OutputStream outputStream, String strVal) throws TableConflictException; + + + /** + * 将值对象序列化到输出流中 + *

+ * 序列化方法同上 + * + * @param outputStream 输出流 + * @param val 值对象 + * @throws TableConflictException 类型不匹配 + */ + public abstract void serialize(OutputStream outputStream, Object val) throws TableConflictException; + + + + /** + * 创建索引 + * + * @throws IndexAlreadyExistException 索引已存在 + */ + public void createIndex() throws IndexAlreadyExistException { + var delimiter = "$"; + var indexName = parentTable.getPath() + delimiter + name; + + index = Index.createEmptyIndex(indexName); + } + + /** + * 向索引插入一个键值对 + * @param value 值对象 + * @param uuid uuid + * @throws TableConflictException 字段类型不匹配 + * @throws TableExistenceException 索引不存在 + */ + public abstract void insertIndex(String value, long uuid) throws TableConflictException, TableExistenceException; + + + /** + * 向索引插入一个键值对 + * @param value 值对象 + * @param uuid uuid + * @throws TableConflictException 字段类型不匹配 + * @throws TableExistenceException 索引不存在 + */ + public abstract void insertIndex(Object value, long uuid) throws TableConflictException, TableExistenceException; + + + + /** + * 通过当前字段的索引树查询记录 + * + * @param key 字段值 + * @throws TableConflictException 字段类型不匹配 + * @throws TableExistenceException 索引不存在 + * @return 记录的uuid + */ + public abstract List queryIndex(String key) throws TableExistenceException, TableConflictException; + + /** + * 通过当前字段的索引树查询记录 + * + * @param key 字段值 + * @throws TableConflictException 字段类型不匹配 + * @throws TableExistenceException 索引不存在 + * @return 记录的uuid + */ + public abstract List queryIndex(Object key) throws TableExistenceException, TableConflictException; + + /** + * 查询当前索引的第一个迭代器 + * + * @throws TableExistenceException 索引不存在 + * @return 迭代器 + */ + public abstract Iterator queryFirst() throws TableExistenceException; + + /** + * 查询第一个满足字段值的迭代器 + * + * @param key 查询键 + * @throws TableExistenceException 索引不存在 + * @throws TableConflictException 字段类型不匹配 + * @return 迭代器 + */ + public abstract Iterator queryFirstMeet(String key) throws TableExistenceException, TableConflictException; + + /** + * 查询第一个满足值且与查询键不相等的迭代器 + * + * @param key 查询键 + * @throws TableExistenceException 索引不存在 + * @throws TableConflictException 字段类型不匹配 + * @return 迭代器 + */ + public abstract Iterator queryFirstMeetNotEqual(String key) throws TableExistenceException, TableConflictException; + + /** + * 是否建立索引 + * @return true -> 已建立索引; false -> 未建立索引 + */ + public boolean indexed() { + return index != null; + } + + /** + * 将str转成值 + * + * @param str 字符串 + * @return 值 + * @throws TableConflictException 字段类型不匹配 + */ + public abstract Object strToValue(String str) throws TableConflictException; + + /** + * 检测值是否满足约束 + * + * @param val 值 + * @return true -> 满足约束; false -> 不满足约束 + */ + public abstract boolean checkObject(Object val); + + /** + * 比较两个字段大小 + * @param a 第一个字段 + * @param b 第二个字段 + * @return 比较结果 + * @throws TableConflictException 字段类型不匹配 + */ + public abstract int compare(Object a, Object b) throws TableConflictException; +} \ No newline at end of file diff --git a/src/main/java/net/kaaass/rumbase/table/field/FieldType.java b/src/main/java/net/kaaass/rumbase/table/field/FieldType.java new file mode 100644 index 0000000..b150126 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/field/FieldType.java @@ -0,0 +1,36 @@ +package net.kaaass.rumbase.table.field; + +/** + * 字段的类型枚举 + * + * @author @KveinAxel + */ +public enum FieldType { + /** + * 4字节整型 + */ + INT, + /** + * 4字节浮点类型 + */ + FLOAT, + /** + * 带最大长度的可变长字符串 + */ + VARCHAR; + + @Override + public String toString() { + switch (this) { + case INT: + return "int"; + case FLOAT: + return "float"; + case VARCHAR: + return "varchar"; + default: + return ""; + } + } + +} diff --git a/src/main/java/net/kaaass/rumbase/table/field/FloatField.java b/src/main/java/net/kaaass/rumbase/table/field/FloatField.java new file mode 100644 index 0000000..d8539d2 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/field/FloatField.java @@ -0,0 +1,314 @@ +package net.kaaass.rumbase.table.field; + +import com.igormaznitsa.jbbp.io.JBBPBitInputStream; +import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; +import com.igormaznitsa.jbbp.io.JBBPByteOrder; +import lombok.NonNull; +import net.kaaass.rumbase.index.Pair; +import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + + +/** + * 单浮点数类型的字段 + * + * @author @KveinAxel + */ +public class FloatField extends BaseField { + + public FloatField(@NonNull String name, boolean nullable, @NonNull Table parentTable) { + super(name, FieldType.FLOAT, nullable, parentTable); + } + + + @Override + public void persist(OutputStream stream) { + var out = new JBBPBitOutputStream(stream); + + try { + out.writeString(getName(), JBBPByteOrder.BIG_ENDIAN); + out.writeString(getType().toString().toUpperCase(Locale.ROOT), JBBPByteOrder.BIG_ENDIAN); + var flags = new byte[]{0}; + flags[0] |= indexed() ? 1 : 0; + if (indexed()) { + flags[0] |= 2; + out.writeBytes(flags, 1, JBBPByteOrder.BIG_ENDIAN); + out.writeString(indexName, JBBPByteOrder.BIG_ENDIAN); + } else { + out.writeBytes(flags, 1, JBBPByteOrder.BIG_ENDIAN); + } + // todo (字段约束) + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public boolean checkStr(String valStr) { + if (valStr == null || valStr.isBlank()) { + return isNullable(); + } + try { + return checkVal(Float.parseFloat(valStr)); + } catch (NumberFormatException e) { + return false; + } + } + + /** + * 字段约束(待定) + * + * @param val 值 + * @return 是否满足约束 + */ + boolean checkVal(float val) { + return true; + } + + @Override + public long strToHash(String str) throws TableConflictException { + + // 空值的哈希固定为0 + if (str == null || str.isBlank()) { + return 0; + } + + try { + var val = Float.parseFloat(str); + if (checkVal(val)) { + return toHash(val); + } else { + throw new TableConflictException(3); + } + } catch (NumberFormatException e) { + throw new TableConflictException(1); + } + } + + @Override + public long toHash(Object val) throws TableConflictException { + + // 空值的哈希固定为0 + if (val == null) { + return 0; + } + + try { + var f = (float) val; + return toHash(f); + } catch (ClassCastException e) { + throw new TableConflictException(1); + } + } + + /** + * 将float转成hash + * + * @param f float + * @return hash + */ + long toHash(float f) { + return (long) (f * 1000); + } + + @Override + public Object deserialize(InputStream inputStream) throws TableConflictException { + + var stream = new JBBPBitInputStream(inputStream); + + try { + var flag = stream.readByte(); + var isNull = (flag & 1) == 1; + if (isNull) { + if (isNullable()) { + return null; + } else { + throw new TableConflictException(3); + } + } + + var val = stream.readFloat(JBBPByteOrder.BIG_ENDIAN); + if (checkVal(val)) { + return val; + } else { + throw new TableConflictException(3); + } + + } catch (IOException e) { + throw new TableConflictException(1); + } + + } + + @Override + public boolean checkInputStream(InputStream inputStream) { + + var stream = new JBBPBitInputStream(inputStream); + + try { + var flag = stream.readByte(); + var isNull = (flag & 1) == 1; + if (isNull) { + return isNullable(); + } + + return checkVal(stream.readFloat(JBBPByteOrder.BIG_ENDIAN)); + } catch (IOException e) { + return false; + } + } + + @Override + public void serialize(OutputStream outputStream, String strVal) throws TableConflictException { + + var stream = new JBBPBitOutputStream(outputStream); + + try { + if (strVal == null || strVal.isBlank()) { + stream.writeBytes(new byte[]{1}, 1, JBBPByteOrder.BIG_ENDIAN); + } else { + stream.writeBytes(new byte[]{0}, 1, JBBPByteOrder.BIG_ENDIAN); + var val = Float.parseFloat(strVal); + if (checkVal(val)) { + stream.writeFloat(val, JBBPByteOrder.BIG_ENDIAN); + } else { + throw new TableConflictException(3); + } + } + + } catch (IOException e) { + // fixme 这个给外面可能也不知道如何处理 + throw new RuntimeException(); + } catch (NumberFormatException e) { + throw new TableConflictException(1); + } + } + + @Override + public void serialize(OutputStream outputStream, Object objVal) throws TableConflictException { + var stream = new JBBPBitOutputStream(outputStream); + + try { + if (objVal == null) { + stream.writeBytes(new byte[]{1}, 1, JBBPByteOrder.BIG_ENDIAN); + } else { + stream.writeBytes(new byte[]{0}, 1, JBBPByteOrder.BIG_ENDIAN); + var val = (float) objVal; + if (checkVal(val)) { + stream.writeFloat(val, JBBPByteOrder.BIG_ENDIAN); + } else { + throw new TableConflictException(3); + } + } + + } catch (IOException e) { + // fixme 这个给外面可能也不知道如何处理 + throw new RuntimeException(); + } catch (NumberFormatException e) { + throw new TableConflictException(1); + } + } + + @Override + public void insertIndex(String value, long uuid) throws TableConflictException, TableExistenceException { + if (index == null) { + throw new TableExistenceException(6); + } + index.insert(strToHash(value), uuid); + } + + @Override + public void insertIndex(Object value, long uuid) throws TableConflictException, TableExistenceException { + if (index == null) { + throw new TableExistenceException(6); + } + index.insert(toHash(value), uuid); + } + + @Override + public List queryIndex(String value) throws TableExistenceException, TableConflictException { + if (index == null) { + throw new TableExistenceException(6); + } + + return index.query(strToHash(value)); + } + + @Override + public List queryIndex(Object key) throws TableExistenceException, TableConflictException { + if (index == null) { + throw new TableExistenceException(6); + } + + try { + return index.query(toHash(key)); + } catch (ClassCastException e) { + throw new TableConflictException(1); + } + } + + @Override + public Iterator queryFirst() throws TableExistenceException { + if (index == null) { + throw new TableExistenceException(6); + } + + return index.findFirst(); + } + + @Override + public Iterator queryFirstMeet(String key) throws TableExistenceException, TableConflictException { + if (index == null) { + throw new TableExistenceException(6); + } + + return index.findFirst(strToHash(key)); + } + + @Override + public Iterator queryFirstMeetNotEqual(String key) throws TableExistenceException, TableConflictException { + if (index == null) { + throw new TableExistenceException(6); + } + + return index.findUpperbound(strToHash(key)); + } + + @Override + public Object strToValue(String str) throws TableConflictException { + + try { + return Float.parseFloat(str); + } catch (NumberFormatException e) { + throw new TableConflictException(1); + } + } + + @Override + public boolean checkObject(Object val) { + try { + var res = (float) val; + return true; + } catch (NumberFormatException e) { + return false; + } + } + + @Override + public int compare(Object a, Object b) throws TableConflictException { + try { + return Float.compare((float) a, (float) b); + } catch (ClassCastException e) { + throw new TableConflictException(1); + } + } + +} diff --git a/src/main/java/net/kaaass/rumbase/table/field/IntField.java b/src/main/java/net/kaaass/rumbase/table/field/IntField.java new file mode 100644 index 0000000..110a879 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/field/IntField.java @@ -0,0 +1,312 @@ +package net.kaaass.rumbase.table.field; + +import com.igormaznitsa.jbbp.io.JBBPBitInputStream; +import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; +import com.igormaznitsa.jbbp.io.JBBPByteOrder; +import lombok.NonNull; +import net.kaaass.rumbase.index.Pair; +import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +/** + * 整型类型的字段 + * + * @author @KveinAxel + */ +public class IntField extends BaseField { + + public IntField(@NonNull String name, boolean nullable, @NonNull Table parentTable) { + super(name, FieldType.INT, nullable, parentTable); + } + + @Override + public void persist(OutputStream stream) { + var out = new JBBPBitOutputStream(stream); + + try { + out.writeString(getName(), JBBPByteOrder.BIG_ENDIAN); + out.writeString(getType().toString().toUpperCase(Locale.ROOT), JBBPByteOrder.BIG_ENDIAN); + var flags = new byte[]{0}; + flags[0] |= indexed() ? 1 : 0; + if (indexed()) { + flags[0] |= 2; + out.writeBytes(flags, 1, JBBPByteOrder.BIG_ENDIAN); + out.writeString(indexName, JBBPByteOrder.BIG_ENDIAN); + } else { + out.writeBytes(flags, 1, JBBPByteOrder.BIG_ENDIAN); + } + // todo (字段约束) + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public boolean checkStr(String valStr) { + if (valStr == null || valStr.isBlank()) { + return isNullable(); + } + try { + var val = Integer.parseInt(valStr); + return checkVal(val); + } catch (NumberFormatException e) { + return false; + } + } + + /** + * 字段约束(待定) + * + * @param val 值 + * @return 是否满足约束 + */ + boolean checkVal(int val) { + return true; + } + + @Override + public long strToHash(String str) throws TableConflictException { + + // 空值的哈希固定为0 + if (str == null || str.isBlank()) { + return 0; + } + + try { + var val = Integer.parseInt(str); + + if (!checkVal(val)) { + throw new TableConflictException(3); + } + return toHash(val); + } catch (NumberFormatException e) { + throw new TableConflictException(1); + } + } + + @Override + public long toHash(Object val) throws TableConflictException { + + // 空值的哈希固定为0 + if (val == null) { + return 0; + } + + try { + var i = (int) val; + return toHash(i); + } catch (ClassCastException e) { + throw new TableConflictException(1); + } + } + + /** + * 将int转成hash + * + * @param val int值 + * @return hash + */ + long toHash(int val) { + return val; + } + + @Override + public Object deserialize(InputStream inputStream) throws TableConflictException { + + var stream = new JBBPBitInputStream(inputStream); + + try { + var flag = stream.readByte(); + var isNull = (flag & 1) == 1; + if (isNull) { + if (isNullable()) { + return null; + } else { + throw new TableConflictException(3); + } + } + + var val = stream.readInt(JBBPByteOrder.BIG_ENDIAN); + if (checkVal(val)) { + return val; + } else { + throw new TableConflictException(3); + } + } catch (IOException e) { + throw new TableConflictException(1); + } + } + + @Override + public boolean checkInputStream(InputStream inputStream) { + + var stream = new JBBPBitInputStream(inputStream); + + try { + var flag = stream.readByte(); + var isNull = (flag & 1) == 1; + if (isNull) { + return isNullable(); + } + + return checkVal(stream.readInt(JBBPByteOrder.BIG_ENDIAN)); + } catch (IOException e) { + return false; + } + } + + @Override + public void serialize(OutputStream outputStream, String strVal) throws TableConflictException { + + var stream = new JBBPBitOutputStream(outputStream); + + try { + if (strVal == null || strVal.isBlank()) { + stream.writeBytes(new byte[]{1}, 1, JBBPByteOrder.BIG_ENDIAN); + } else { + stream.writeBytes(new byte[]{0}, 1, JBBPByteOrder.BIG_ENDIAN); + var val = Integer.parseInt(strVal); + if (checkVal(val)) { + stream.writeInt(val, JBBPByteOrder.BIG_ENDIAN); + } else { + throw new TableConflictException(3); + } + } + + + } catch (IOException e) { + // fixme 这个给外面可能也不知道如何处理 + throw new RuntimeException(); + } catch (NumberFormatException e) { + throw new TableConflictException(1); + } + } + + @Override + public void serialize(OutputStream outputStream, Object objVal) throws TableConflictException { + var stream = new JBBPBitOutputStream(outputStream); + + try { + if (objVal == null) { + stream.writeBytes(new byte[]{1}, 1, JBBPByteOrder.BIG_ENDIAN); + } else { + stream.writeBytes(new byte[]{0}, 1, JBBPByteOrder.BIG_ENDIAN); + var val = (int) objVal; + if (checkVal(val)) { + stream.writeInt(val, JBBPByteOrder.BIG_ENDIAN); + } else { + throw new TableConflictException(3); + } + } + + } catch (IOException e) { + // fixme 这个给外面可能也不知道如何处理 + throw new RuntimeException(); + } catch (NumberFormatException e) { + throw new TableConflictException(1); + } + } + + @Override + public void insertIndex(String value, long uuid) throws TableExistenceException, TableConflictException { + if (index == null) { + throw new TableExistenceException(6); + } + index.insert(strToHash(value), uuid); + } + + @Override + public void insertIndex(Object value, long uuid) throws TableConflictException, TableExistenceException { + if (index == null) { + throw new TableExistenceException(6); + } + index.insert(toHash(value), uuid); + } + + @Override + public List queryIndex(String value) throws TableExistenceException, TableConflictException { + if (index == null) { + throw new TableExistenceException(6); + } + + return index.query(strToHash(value)); + } + + @Override + public List queryIndex(Object key) throws TableExistenceException, TableConflictException { + if (index == null) { + throw new TableExistenceException(6); + } + + try { + return index.query(toHash(key)); + } catch (ClassCastException e) { + throw new TableConflictException(1); + } + } + + @Override + public Iterator queryFirst() throws TableExistenceException { + if (index == null) { + throw new TableExistenceException(6); + } + + return index.findFirst(); + } + + @Override + public Iterator queryFirstMeet(String key) throws TableExistenceException, TableConflictException { + if (index == null) { + throw new TableExistenceException(6); + } + + return index.findFirst(strToHash(key)); + } + + @Override + public Iterator queryFirstMeetNotEqual(String key) throws TableExistenceException, TableConflictException { + if (index == null) { + throw new TableExistenceException(6); + } + + return index.findUpperbound(strToHash(key)); + } + + @Override + public Object strToValue(String str) throws TableConflictException { + try { + return Integer.parseInt(str); + } catch (NumberFormatException e) { + throw new TableConflictException(1); + } + } + + @Override + public boolean checkObject(Object val) { + try { + var res = (int) val; + return true; + } catch (ClassCastException e) { + return false; + } + } + + @Override + public int compare(Object a, Object b) throws TableConflictException { + try { + return Integer.compare((int) a, (int) b); + } catch (ClassCastException e) { + throw new TableConflictException(1); + } + } + + +} diff --git a/src/main/java/net/kaaass/rumbase/table/field/VarcharField.java b/src/main/java/net/kaaass/rumbase/table/field/VarcharField.java new file mode 100644 index 0000000..93e96e9 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/table/field/VarcharField.java @@ -0,0 +1,290 @@ +package net.kaaass.rumbase.table.field; + +import com.igormaznitsa.jbbp.io.JBBPBitInputStream; +import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; +import com.igormaznitsa.jbbp.io.JBBPByteOrder; +import lombok.Getter; +import lombok.NonNull; +import net.kaaass.rumbase.index.Pair; +import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + + +/** + * 可变长字符串类型的字段 + * + * @author @KveinAxel + */ +public class VarcharField extends BaseField { + + /** + * 字符串长度的限制 + */ + @Getter + private final int limit; + + private static final String DELIMIT = "'"; + + public VarcharField(@NonNull String name, int limit, boolean nullable, @NonNull Table parentTable) { + super(name, FieldType.VARCHAR, nullable, parentTable); + this.limit = limit; + } + + @Override + public void persist(OutputStream stream) { + var out = new JBBPBitOutputStream(stream); + + try { + out.writeString(getName(), JBBPByteOrder.BIG_ENDIAN); + out.writeString(getType().toString().toUpperCase(Locale.ROOT), JBBPByteOrder.BIG_ENDIAN); + var flags = new byte[]{0}; + flags[0] |= indexed() ? 1 : 0; + if (indexed()) { + flags[0] |= 2; + out.writeBytes(flags, 1, JBBPByteOrder.BIG_ENDIAN); + out.writeString(indexName, JBBPByteOrder.BIG_ENDIAN); + } else { + out.writeBytes(flags, 1, JBBPByteOrder.BIG_ENDIAN); + } + out.writeInt(limit, JBBPByteOrder.BIG_ENDIAN); + // todo (字段约束) + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public boolean checkStr(String valStr) { + if (valStr == null || valStr.isBlank()) { + return isNullable(); + } + return valStr.startsWith(DELIMIT) && valStr.endsWith(DELIMIT) && valStr.length() <= this.limit + 2 * DELIMIT.length(); + } + + @Override + public long strToHash(String str) { + + // 空值的哈希固定为0 + if (str == null || str.isBlank()) { + return 0; + } + + return str.hashCode(); + } + + @Override + public long toHash(Object val) throws TableConflictException { + + // 空值的哈希固定为0 + if (val == null) { + return 0; + } + + try { + var str = (String) val; + return strToHash(str); + } catch (ClassCastException e) { + throw new TableConflictException(1); + } + } + + @Override + public Object deserialize(InputStream inputStream) throws TableConflictException { + + var stream = new JBBPBitInputStream(inputStream); + + try { + var flag = stream.readByte(); + var isNull = (flag & 1) == 1; + if (isNull) { + if (isNullable()) { + return null; + } else { + throw new TableConflictException(3); + } + } + + return stream.readString(JBBPByteOrder.BIG_ENDIAN); + } catch (IOException e) { + throw new TableConflictException(1); + } + } + + @Override + public boolean checkInputStream(InputStream inputStream) { + + var stream = new JBBPBitInputStream(inputStream); + + try { + var flag = stream.readByte(); + var isNull = (flag & 1) == 1; + if (isNull) { + return isNullable(); + } + + stream.readString(JBBPByteOrder.BIG_ENDIAN); + return true; + } catch (IOException e) { + return false; + } + } + + @Override + public void serialize(OutputStream outputStream, String strVal) throws TableConflictException { + + var stream = new JBBPBitOutputStream(outputStream); + + if (!checkStr(strVal)) { + throw new TableConflictException(3); + } + + try { + if (strVal == null || strVal.isBlank()) { + stream.writeBytes(new byte[]{1}, 1, JBBPByteOrder.BIG_ENDIAN); + } else { + if (strVal.startsWith(DELIMIT) && strVal.endsWith(DELIMIT)) { + var substr = strVal.substring(1, strVal.length() - 1); + if (substr.isBlank()) { + stream.writeBytes(new byte[]{1}, 1, JBBPByteOrder.BIG_ENDIAN); + } else { + stream.writeBytes(new byte[]{0}, 1, JBBPByteOrder.BIG_ENDIAN); + stream.writeString(substr, JBBPByteOrder.BIG_ENDIAN); + } + } else { + throw new TableConflictException(1); + } + } + + } catch (IOException e) { + // fixme 这个给外面可能也不知道如何处理 + throw new RuntimeException(e); + } + } + + @Override + public void serialize(OutputStream outputStream, Object val) throws TableConflictException { + + var stream = new JBBPBitOutputStream(outputStream); + + if (!checkObject(val)) { + throw new TableConflictException(3); + } + + try { + if (val == null) { + stream.writeBytes(new byte[]{1}, 1, JBBPByteOrder.BIG_ENDIAN); + } else { + stream.writeBytes(new byte[]{0}, 1, JBBPByteOrder.BIG_ENDIAN); + stream.writeString((String) val, JBBPByteOrder.BIG_ENDIAN); + } + + } catch (IOException e) { + // fixme 这个给外面可能也不知道如何处理 + throw new RuntimeException(e); + } catch (ClassCastException e){ + throw new TableConflictException(1); + } + } + + @Override + public void insertIndex(String value, long uuid) throws TableExistenceException { + if (index == null) { + throw new TableExistenceException(6); + } + index.insert(strToHash(value), uuid); + } + + @Override + public void insertIndex(Object value, long uuid) throws TableConflictException, TableExistenceException { + if (index == null) { + throw new TableExistenceException(6); + } + index.insert(toHash(value), uuid); + } + + @Override + public List queryIndex(String value) throws TableExistenceException { + if (index == null) { + throw new TableExistenceException(6); + } + + return index.query(strToHash(value)); + } + + @Override + public List queryIndex(Object key) throws TableExistenceException, TableConflictException { + if (index == null) { + throw new TableExistenceException(6); + } + + try { + return index.query(toHash(key)); + } catch (ClassCastException e) { + throw new TableConflictException(1); + } + } + + @Override + public Iterator queryFirst() throws TableExistenceException { + if (index == null) { + throw new TableExistenceException(6); + } + + return index.findFirst(); + } + + @Override + public Iterator queryFirstMeet(String key) throws TableExistenceException { + if (index == null) { + throw new TableExistenceException(6); + } + + return index.findFirst(strToHash(key)); + } + + @Override + public Iterator queryFirstMeetNotEqual(String key) throws TableExistenceException { + if (index == null) { + throw new TableExistenceException(6); + } + + return index.findUpperbound(strToHash(key)); + } + + @Override + public Object strToValue(String str) { + if (str.startsWith(DELIMIT) && str.endsWith(DELIMIT)) { + return str.substring(1, str.length() - 1); + } else { + return str; + } + } + + @Override + public boolean checkObject(Object val) { + try { + var res = (String) val; + return true; + } catch (ClassCastException e) { + return false; + } + } + + @Override + public int compare(Object a, Object b) throws TableConflictException { + try { + return ((String) a).compareTo((String) b); + } catch (ClassCastException e) { + throw new TableConflictException(1); + } + } + +} diff --git a/src/test/java/net/kaaass/rumbase/query/CreateIndexExecutorTest.java b/src/test/java/net/kaaass/rumbase/query/CreateIndexExecutorTest.java new file mode 100644 index 0000000..861fe15 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/query/CreateIndexExecutorTest.java @@ -0,0 +1,101 @@ +package net.kaaass.rumbase.query; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.CreateIndexStatement; +import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.BaseField; +import net.kaaass.rumbase.table.field.IntField; +import net.kaaass.rumbase.table.field.VarcharField; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.io.File; +import java.util.ArrayList; + +@Slf4j +public class CreateIndexExecutorTest extends TestCase { + + private static final String PATH = "build/"; + + public void testParseSingle() throws SqlSyntaxException { + var sql = "CREATE INDEX PersonIndex ON testParseSingle$Person (LastName) ;"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof CreateIndexStatement); + // 准备预期结果 + var manager = new TableManager(); + var context = TransactionContext.empty(); + var fields = new ArrayList(); + var dummy = new Table("testParseSingle.__reserved__", fields); + fields.add(new VarcharField("LastName", 20, false, dummy)); + try { + manager.createTable(context, "testParseSingle$Person", fields, PATH + "testParseSingle.Person.db"); + } catch (TableExistenceException e) { + log.error("Exception expected: ", e); + fail(); + } + try { + var table = manager.getTable("testParseSingle$Person"); + var field = table.getField("LastName"); + assertTrue(field.isPresent()); + assertFalse(field.get().indexed()); + + var createExe = new CreateIndexExecutor((CreateIndexStatement) stmt, manager); + createExe.execute(); + + assertTrue(field.get().indexed()); + assertEquals("PersonIndex", field.get().getIndexName()); + } catch (TableExistenceException | IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + new File("metadata.db").deleteOnExit(); + } + + public void testParseMulti() throws SqlSyntaxException { + var sql = "CREATE INDEX PersonIndex ON testParseMulti$Person (LastName, ID) ;"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof CreateIndexStatement); + // 准备预期结果 + var manager = new TableManager(); + var context = TransactionContext.empty(); + var fields = new ArrayList(); + var dummy = new Table("testParseMulti.__reserved__", fields); + fields.add(new VarcharField("LastName", 20, false, dummy)); + fields.add(new IntField("ID", false, dummy)); + try { + manager.createTable(context, "testParseMulti$Person", fields, PATH + "testParseMulti.Person.db"); + } catch (TableExistenceException e) { + log.error("Exception expected: ", e); + fail(); + } + try { + var table = manager.getTable("testParseMulti$Person"); + var field1 = table.getField("LastName"); + var field2 = table.getField("ID"); + assertTrue(field1.isPresent()); + assertFalse(field1.get().indexed()); + assertTrue(field2.isPresent()); + assertFalse(field2.get().indexed()); + + var createExe = new CreateIndexExecutor((CreateIndexStatement) stmt, manager); + createExe.execute(); + + assertTrue(field1.get().indexed()); + assertFalse(field2.get().indexed()); + assertEquals("PersonIndex", field1.get().getIndexName()); + } catch (TableExistenceException | IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + new File("metadata.db").deleteOnExit(); + } +} diff --git a/src/test/java/net/kaaass/rumbase/query/CreateTableExecutorTest.java b/src/test/java/net/kaaass/rumbase/query/CreateTableExecutorTest.java new file mode 100644 index 0000000..59733fe --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/query/CreateTableExecutorTest.java @@ -0,0 +1,72 @@ +package net.kaaass.rumbase.query; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.CreateTableStatement; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.FieldType; +import net.kaaass.rumbase.table.field.VarcharField; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.io.File; + +@Slf4j +public class CreateTableExecutorTest extends TestCase { + + public void testCreate() throws SqlSyntaxException { + var sql = "CREATE TABLE testCreate$Persons\n" + + "(\n" + + "Id_P int not null,\n" + + "LastName varchar(255),\n" + + "FirstName varchar(255) NOT NULL\n" + + ")"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof CreateTableStatement); + // 执行 + var manager = new TableManager(); + var context = TransactionContext.empty(); + var exe = new CreateTableExecutor((CreateTableStatement) stmt, manager, context); + try { + exe.execute(); + } catch (TableExistenceException | TableConflictException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + // 确认结果 + Table table = null; + try { + table = manager.getTable("testCreate$Persons"); + } catch (TableExistenceException e) { + log.error("Exception expected: ", e); + fail(); + } + assertNotNull(table); + var fields = table.getFields(); + assertEquals(3, fields.size()); + + assertEquals("Id_P", fields.get(0).getName()); + assertEquals(FieldType.INT, fields.get(0).getType()); + assertFalse(fields.get(0).isNullable()); + + + assertEquals("LastName", fields.get(1).getName()); + assertEquals(FieldType.VARCHAR, fields.get(1).getType()); + assertEquals(255, ((VarcharField)fields.get(1)).getLimit()); + assertTrue(fields.get(1).isNullable()); + + + assertEquals("FirstName", fields.get(2).getName()); + assertEquals(FieldType.VARCHAR, fields.get(2).getType()); + assertEquals(255, ((VarcharField)fields.get(1)).getLimit()); + assertFalse(fields.get(2).isNullable()); + + new File("metadata.db").deleteOnExit(); + } +} diff --git a/src/test/java/net/kaaass/rumbase/query/DeleteExecutorTest.java b/src/test/java/net/kaaass/rumbase/query/DeleteExecutorTest.java new file mode 100644 index 0000000..322761a --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/query/DeleteExecutorTest.java @@ -0,0 +1,191 @@ +package net.kaaass.rumbase.query; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.DeleteStatement; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.BaseField; +import net.kaaass.rumbase.table.field.IntField; +import net.kaaass.rumbase.table.field.VarcharField; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.io.File; +import java.util.ArrayList; + +@Slf4j +public class DeleteExecutorTest extends TestCase { + + private static final String PATH = "build/"; + + public void testDelete() throws SqlSyntaxException { + var sql = "DELETE FROM testDelete$Person WHERE LastName = 'KevinAxel'"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof DeleteStatement); + + var manager = new TableManager(); + var context = TransactionContext.empty(); + var fields = new ArrayList(); + var dummy = new Table("testDelete.__reserved__", fields); + var id = new IntField("ID", false, dummy); + fields.add(new VarcharField("LastName", 20, false, dummy)); + fields.add(id); + Table table = null; + try { + manager.createTable(context, "testDelete$Person", fields, PATH + "testDelete.Person.db"); + id.createIndex(); + table = manager.getTable("testDelete$Person"); + } catch (TableExistenceException | IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + assertNotNull(table); + try { + table.insert(context, new ArrayList<>() {{ + add(0, "'KevinAxel'"); + add(1, "1"); + }}); + table.insert(context, new ArrayList<>() {{ + add(0, "'KAAAsS'"); + add(1, "2"); + }}); + + } catch (TableConflictException | TableExistenceException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 测试插入结果 + try { + var data = table.readAll(context); + assertEquals(2, data.size()); + + assertEquals(2, data.get(0).size()); + assertEquals("KevinAxel", (String) data.get(0).get(0)); + assertEquals(1, (int) data.get(0).get(1)); + + assertEquals(2, data.get(1).size()); + assertEquals("KAAAsS", (String) data.get(1).get(0)); + assertEquals(2, (int) data.get(1).get(1)); + + } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 执行 + var exe = new DeleteExecutor((DeleteStatement) stmt, manager, context); + try { + exe.execute(); + } catch (TableExistenceException | ArgumentException | IndexAlreadyExistException | TableConflictException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 测试执行结果 + try { + var data = table.readAll(context); + assertEquals(1, data.size()); + + assertEquals(2, data.get(0).size()); + assertEquals("KAAAsS", (String) data.get(0).get(0)); + assertEquals(2, (int) data.get(0).get(1)); + + } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + new File("metadata.db").deleteOnExit(); + + } + + public void testDeleteAll() throws SqlSyntaxException { + var sql = "DELETE FROM testDeleteAll$Person "; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof DeleteStatement); + + var manager = new TableManager(); + var context = TransactionContext.empty(); + var fields = new ArrayList(); + var dummy = new Table("testDeleteAll.__reserved__", fields); + var id = new IntField("ID", false, dummy); + fields.add(new VarcharField("LastName", 20, false, dummy)); + fields.add(id); + Table table = null; + try { + manager.createTable(context, "testDeleteAll$Person", fields, PATH + "testDeleteAll.Person.db"); + id.createIndex(); + table = manager.getTable("testDeleteAll$Person"); + } catch (TableExistenceException | IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + assertNotNull(table); + try { + table.insert(context, new ArrayList<>() {{ + add(0, "'KevinAxel'"); + add(1, "1"); + }}); + table.insert(context, new ArrayList<>() {{ + add(0, "'KAAAsS'"); + add(1, "2"); + }}); + + } catch (TableConflictException | TableExistenceException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 测试插入结果 + try { + var data = table.readAll(context); + assertEquals(2, data.size()); + + assertEquals(2, data.get(0).size()); + assertEquals("KevinAxel", (String) data.get(0).get(0)); + assertEquals(1, (int) data.get(0).get(1)); + + assertEquals(2, data.get(1).size()); + assertEquals("KAAAsS", (String) data.get(1).get(0)); + assertEquals(2, (int) data.get(1).get(1)); + + } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 执行 + var exe = new DeleteExecutor((DeleteStatement) stmt, manager, context); + try { + exe.execute(); + } catch (TableExistenceException | ArgumentException | IndexAlreadyExistException | TableConflictException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 测试执行结果 + try { + var data = table.readAll(context); + assertEquals(0, data.size()); + + } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + new File("metadata.db").deleteOnExit(); + + } +} diff --git a/src/test/java/net/kaaass/rumbase/query/InsertExecutorTest.java b/src/test/java/net/kaaass/rumbase/query/InsertExecutorTest.java new file mode 100644 index 0000000..45d2624 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/query/InsertExecutorTest.java @@ -0,0 +1,104 @@ +package net.kaaass.rumbase.query; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.InsertStatement; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.BaseField; +import net.kaaass.rumbase.table.field.IntField; +import net.kaaass.rumbase.table.field.VarcharField; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.io.File; +import java.util.ArrayList; + +@Slf4j +public class InsertExecutorTest extends TestCase { + + private static final String PATH = "build/"; + + public void testInsertColumnValue() throws SqlSyntaxException { + var sql = "INSERT INTO Persons (Persons.LastName, Address) VALUES ('Wilson', 'Champs-Elysees')"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof InsertStatement); + + var manager = new TableManager(); + var context = TransactionContext.empty(); + var fields = new ArrayList(); + var dummy = new Table("testInsertColumnValue.__reserved__", fields); + var lastName = new VarcharField("LastName", 20, false, dummy); + fields.add(lastName); + fields.add(new VarcharField("Address", 255, false, dummy)); + Table table = null; + try { + manager.createTable(context, "Persons", fields, PATH + "testInsertColumnValue.Persons.db"); + lastName.createIndex(); + table = manager.getTable("Persons"); + } catch (TableExistenceException | IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + assertNotNull(table); + + // 执行 + var exe = new InsertExecutor((InsertStatement) stmt, manager, context); + try { + exe.execute(); + } catch (TableExistenceException | TableConflictException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 确认结果 + try { + var data = table.readAll(context); + assertEquals(1, data.size()); + assertEquals("Wilson", data.get(0).get(0)); + assertEquals("Champs-Elysees", data.get(0).get(1)); + } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + new File("metadata.db").deleteOnExit(); + + } + + public void testInsertValue() throws SqlSyntaxException { + var sql = "INSERT INTO stu VALUES (20200101, 'KAAAsS', true, 3.9)"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof InsertStatement); + + var manager = new TableManager(); + var context = TransactionContext.empty(); + var fields = new ArrayList(); + var dummy = new Table("testInsertValue.__reserved__", fields); + var id = new IntField("ID", false, dummy); + fields.add(new VarcharField("LastName", 20, false, dummy)); + fields.add(id); + Table table = null; + try { + manager.createTable(context, "Person", fields, PATH + "testInsertValue.Person.db"); + id.createIndex(); + table = manager.getTable("Person"); + } catch (TableExistenceException | IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + assertNotNull(table); + new File("metadata.db").deleteOnExit(); + + } +} diff --git a/src/test/java/net/kaaass/rumbase/query/SelectExecutorTest.java b/src/test/java/net/kaaass/rumbase/query/SelectExecutorTest.java new file mode 100644 index 0000000..8e86c7a --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/query/SelectExecutorTest.java @@ -0,0 +1,201 @@ +package net.kaaass.rumbase.query; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.SelectStatement; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.BaseField; +import net.kaaass.rumbase.table.field.FloatField; +import net.kaaass.rumbase.table.field.IntField; +import net.kaaass.rumbase.table.field.VarcharField; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.io.File; +import java.util.ArrayList; + +@Slf4j +public class SelectExecutorTest extends TestCase { + + private static final String PATH = "build/"; + + public void testSelect() throws SqlSyntaxException { + var sql = "SELECT distinct name, testSelect$account.ID, testSelect$account.balance \n" + + "from testSelect$account join testSelect$payment on testSelect$account.ID = testSelect$payment.ID\n" + + "WHERE testSelect$account.ID > 1 and (testSelect$payment.type = 'N' or testSelect$payment.type = 'T') \n" + + "order by testSelect$account.ID desc;"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + + // 创建测试表 + var manager = new TableManager(); + var context = TransactionContext.empty(); + + // 创建account表 + var accountFields = new ArrayList(); + var accountDummy = new Table("testSelect.__reserved__", accountFields); + var id = new IntField("ID", false, accountDummy); + accountFields.add(id); + accountFields.add(new VarcharField("name", 20, false, accountDummy)); + accountFields.add(new FloatField("balance", false, accountDummy)); + Table account = null; + try { + manager.createTable(context, "testSelect$account", accountFields, PATH + "testSelect.account.db"); + id.createIndex(); + account = manager.getTable("testSelect$account"); + } catch (TableExistenceException | IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + assertNotNull(account); + try { + account.insert(context, new ArrayList<>() {{ + add(0, "1"); + add(1, "'KevinAxel'"); + add(2, "5000"); + }}); + account.insert(context, new ArrayList<>() {{ + add(0, "2"); + add(1, "'KAAAsS'"); + add(2, "8000"); + }}); + account.insert(context, new ArrayList<>() {{ + add(0, "3"); + add(1, "'kkk'"); + add(2, "8000"); + }}); + + } catch (TableConflictException | TableExistenceException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 测试插入结果 + try { + var data = account.readAll(context); + assertEquals(3, data.size()); + + assertEquals(3, data.get(0).size()); + assertEquals(1, (int) data.get(0).get(0)); + assertEquals("KevinAxel", (String) data.get(0).get(1)); + assertEquals(5000f, data.get(0).get(2)); + + assertEquals(3, data.get(1).size()); + assertEquals(2, (int) data.get(1).get(0)); + assertEquals("KAAAsS", (String) data.get(1).get(1)); + assertEquals(8000f, data.get(1).get(2)); + + assertEquals(3, data.get(2).size()); + assertEquals(3, (int) data.get(2).get(0)); + assertEquals("kkk", (String) data.get(2).get(1)); + assertEquals(8000f, data.get(2).get(2)); + + + } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 创建payment表 + var paymentFields = new ArrayList(); + var paymentDummy = new Table("testSelect.__reserved__", paymentFields); + var paymentId = new IntField("ID", false, paymentDummy); + paymentFields.add(paymentId); + paymentFields.add(new VarcharField("type", 1, false, paymentDummy)); + Table payment = null; + try { + manager.createTable(context, "testSelect$payment", paymentFields, PATH + "testSelect.payment.db"); + paymentId.createIndex(); + payment = manager.getTable("testSelect$payment"); + } catch (TableExistenceException | IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + assertNotNull(payment); + try { + payment.insert(context, new ArrayList<>() {{ + add(0, "1"); + add(1, "'N'"); + }}); + payment.insert(context, new ArrayList<>() {{ + add(0, "2"); + add(1, "'T'"); + }}); + payment.insert(context, new ArrayList<>() {{ + add(0, "3"); + add(1, "'T'"); + }}); + + } catch (TableConflictException | TableExistenceException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 测试插入结果 + try { + var data = payment.readAll(context); + assertEquals(3, data.size()); + + assertEquals(2, data.get(0).size()); + assertEquals(1, (int) data.get(0).get(0)); + assertEquals("N", (String) data.get(0).get(1)); + + assertEquals(2, data.get(1).size()); + assertEquals(2, (int) data.get(1).get(0)); + assertEquals("T", (String) data.get(1).get(1)); + + assertEquals(2, data.get(2).size()); + assertEquals(3, (int) data.get(2).get(0)); + assertEquals("T", (String) data.get(2).get(1)); + + + } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 执行语句 + var exe = new SelectExecutor((SelectStatement) stmt, manager, context); + try { + exe.execute(); + } catch (TableConflictException | ArgumentException | TableExistenceException | IndexAlreadyExistException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + var cols = exe.getResultTable(); + var data = exe.getResultData(); + + assertEquals(3, cols.size()); + assertEquals("testSelect$account", cols.get(0).getTableName()); + assertEquals("name", cols.get(0).getFieldName()); + assertEquals("testSelect$account", cols.get(1).getTableName()); + assertEquals("ID", cols.get(1).getFieldName()); + assertEquals("testSelect$account", cols.get(2).getTableName()); + assertEquals("balance", cols.get(2).getFieldName()); + + assertEquals(2, data.size()); + + assertEquals(3, data.get(0).size()); + assertEquals(3, data.get(0).get(0)); + assertEquals("kkk", data.get(0).get(1)); + assertEquals(8000f, data.get(0).get(2)); + + assertEquals(3, data.get(0).size()); + assertEquals(2, data.get(1).get(0)); + assertEquals("KAAAsS", data.get(1).get(1)); + assertEquals(8000f, data.get(1).get(2)); + + + new File("metadata.db").deleteOnExit(); + + } +} diff --git a/src/test/java/net/kaaass/rumbase/query/UpdateExecutorTest.java b/src/test/java/net/kaaass/rumbase/query/UpdateExecutorTest.java new file mode 100644 index 0000000..c2f594d --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/query/UpdateExecutorTest.java @@ -0,0 +1,215 @@ +package net.kaaass.rumbase.query; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.UpdateStatement; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.BaseField; +import net.kaaass.rumbase.table.field.VarcharField; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.io.File; +import java.util.ArrayList; + +@Slf4j +public class UpdateExecutorTest extends TestCase { + + private static final String PATH = "build/"; + + public void testUpdateWithCondition() throws SqlSyntaxException { + var sql = "UPDATE testUpdateWithCondition$Person SET Address = 'Zhongshan 23', City = 'Nanjing'\n" + + "WHERE LastName = 'Wilson'"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + assertTrue(stmt instanceof UpdateStatement); + + var manager = new TableManager(); + var context = TransactionContext.empty(); + var fields = new ArrayList(); + var dummy = new Table("testUpdateWithCondition.__reserved__", fields); + var lastName = new VarcharField("LastName", 20, false, dummy); + fields.add(lastName); + fields.add(new VarcharField("City", 20, false, dummy)); + fields.add(new VarcharField("Address", 20, false, dummy)); + Table table = null; + try { + manager.createTable(context, "testUpdateWithCondition$Person", fields, PATH + "testUpdateWithCondition.Person.db"); + lastName.createIndex(); + table = manager.getTable("testUpdateWithCondition$Person"); + } catch (TableExistenceException | IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + assertNotNull(table); + try { + table.insert(context, new ArrayList<>() {{ + add(0, "'Wilson'"); + add(1, "'JiaXing'"); + add(2, "'Zhongshan 45'"); + }}); + table.insert(context, new ArrayList<>() {{ + add(0, "'KAAAsS'"); + add(1, "'WenZhou'"); + add(2, "'Zhongshan 78'"); + }}); + + } catch (TableConflictException | TableExistenceException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 测试插入结果 + try { + var data = table.readAll(context); + assertEquals(2, data.size()); + + assertEquals(3, data.get(0).size()); + assertEquals("Wilson", (String) data.get(0).get(0)); + assertEquals("JiaXing", (String) data.get(0).get(1)); + assertEquals("Zhongshan 45", (String) data.get(0).get(2)); + + assertEquals(3, data.get(1).size()); + assertEquals("KAAAsS", (String) data.get(1).get(0)); + assertEquals("WenZhou", (String) data.get(1).get(1)); + assertEquals("Zhongshan 78", (String) data.get(1).get(2)); + + } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 执行 + var exe = new UpdateExecutor((UpdateStatement) stmt, manager, context); + try { + exe.execute(); + } catch (TableExistenceException | ArgumentException | IndexAlreadyExistException | TableConflictException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 检查执行结果 + try { + var data = table.readAll(context); + assertEquals(2, data.size()); + + assertEquals(3, data.get(0).size()); + assertEquals("Wilson", (String) data.get(0).get(0)); + assertEquals("Nanjing", (String) data.get(0).get(1)); + assertEquals("Zhongshan 23", (String) data.get(0).get(2)); + + assertEquals(3, data.get(1).size()); + assertEquals("KAAAsS", (String) data.get(1).get(0)); + assertEquals("WenZhou", (String) data.get(1).get(1)); + assertEquals("Zhongshan 78", (String) data.get(1).get(2)); + + } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + new File("metadata.db").deleteOnExit(); + + } + + public void testUpdateWithoutCondition() throws SqlSyntaxException { + var sql = "UPDATE testUpdateWithoutCondition$Person SET Address = 'Zhongshan 23', City = 'Nanjing'"; + // 解析 + var stmt = SqlParser.parseStatement(sql); + + + var manager = new TableManager(); + var context = TransactionContext.empty(); + var fields = new ArrayList(); + var dummy = new Table("testUpdateWithoutCondition.__reserved__", fields); + var lastName = new VarcharField("LastName", 20, false, dummy); + fields.add(lastName); + fields.add(new VarcharField("City", 20, false, dummy)); + fields.add(new VarcharField("Address", 20, false, dummy)); + Table table = null; + try { + manager.createTable(context, "testUpdateWithoutCondition$Person", fields, PATH + "testUpdateWithoutCondition.Person.db"); + lastName.createIndex(); + table = manager.getTable("testUpdateWithoutCondition$Person"); + } catch (TableExistenceException | IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + assertNotNull(table); + try { + table.insert(context, new ArrayList<>() {{ + add(0, "'Wilson'"); + add(1, "'JiaXing'"); + add(2, "'Zhongshan 45'"); + }}); + table.insert(context, new ArrayList<>() {{ + add(0, "'KAAAsS'"); + add(1, "'WenZhou'"); + add(2, "'Zhongshan 78'"); + }}); + + } catch (TableConflictException | TableExistenceException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 测试插入结果 + try { + var data = table.readAll(context); + assertEquals(2, data.size()); + + assertEquals(3, data.get(0).size()); + assertEquals("Wilson", (String) data.get(0).get(0)); + assertEquals("JiaXing", (String) data.get(0).get(1)); + assertEquals("Zhongshan 45", (String) data.get(0).get(2)); + + assertEquals(3, data.get(1).size()); + assertEquals("KAAAsS", (String) data.get(1).get(0)); + assertEquals("WenZhou", (String) data.get(1).get(1)); + assertEquals("Zhongshan 78", (String) data.get(1).get(2)); + + } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 执行 + var exe = new UpdateExecutor((UpdateStatement) stmt, manager, context); + try { + exe.execute(); + } catch (TableExistenceException | ArgumentException | IndexAlreadyExistException | TableConflictException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 检查执行结果 + try { + var data = table.readAll(context); + assertEquals(2, data.size()); + + assertEquals(3, data.get(0).size()); + assertEquals("KAAAsS", (String) data.get(0).get(0)); + assertEquals("Nanjing", (String) data.get(0).get(1)); + assertEquals("Zhongshan 23", (String) data.get(0).get(2)); + + assertEquals(3, data.get(1).size()); + assertEquals("Wilson", (String) data.get(1).get(0)); + assertEquals("Nanjing", (String) data.get(1).get(1)); + assertEquals("Zhongshan 23", (String) data.get(1).get(2)); + + } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + new File("metadata.db").deleteOnExit(); + + } +} diff --git a/src/test/java/net/kaaass/rumbase/table/BaseFieldTest.java b/src/test/java/net/kaaass/rumbase/table/BaseFieldTest.java new file mode 100644 index 0000000..19cfde1 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/table/BaseFieldTest.java @@ -0,0 +1,547 @@ +package net.kaaass.rumbase.table; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; + +import static org.junit.Assert.assertArrayEquals; + +/** + * 字段结构测试 + * + * @author @KveinAxel + * @see BaseField + */ +@Slf4j +public class BaseFieldTest extends TestCase { + + private static final String PATH = "build/"; + + public void testCheckStr() { + + var dummy = new Table(PATH + "testCheckStrTable", new ArrayList<>()); + + // test int + var intField = new IntField("testCheckStrInt", false, dummy); + assertTrue(intField.checkStr("1")); + assertFalse(intField.checkStr("1.2")); + assertFalse(intField.checkStr("1aa")); + + // test float + var floatField = new FloatField("testCheckStrFloat", false, dummy); + assertTrue(floatField.checkStr("1")); + assertTrue(floatField.checkStr("1.2")); + assertFalse(floatField.checkStr("1aa")); + + // test varchar + var varcharField = new VarcharField("testCheckStrVarchar", 20, false, dummy); + assertTrue(varcharField.checkStr("'aaaa'")); + assertFalse(varcharField.checkStr("'aaaa aaaa aaaa aaaa aaaa'")); + + } + + public void testDeserialize() { + + var dummy = new Table(PATH + "testDeserializeTable", new ArrayList<>()); + + // 测试数据 + var bytes = new byte[]{ + 0, + 0, 0, 0, 33, // int 33 + 0, + 63, -103, -103, -102, // float 1.2 + 0, + 12, + 116, 101, 115, 116, + 32, 118, 97, 114, + 99, 104, 97, 114 // varchar test varchar + }; + var inputStream = new ByteArrayInputStream(bytes); + + var intField = new IntField("testDeserializeInt", false, dummy); + try { + var intRes = (int) intField.deserialize(inputStream); + assertEquals(33, intRes); + } catch (TableConflictException e) { + log.error("Exception expected: ", e); + fail("proper format should not fail to parse"); + } + + var floatField = new FloatField("testDeserializeFloat", false, dummy); + try { + var floatRes = (float) floatField.deserialize(inputStream); + assertEquals(1.2f, floatRes); + } catch (TableConflictException e) { + log.error("Exception expected: ", e); + fail("proper format should not fail to parse"); + } + + var varcharField = new VarcharField("testDeserializeVarchar", 20, false, dummy); + try { + var varcharRes = (String) varcharField.deserialize(inputStream); + assertEquals("test varchar", varcharRes); + } catch (TableConflictException e) { + fail("proper format should not fail to parse"); + } + + assertEquals(0, inputStream.available()); + + } + + public void testCheckInputStream() { + + var dummy = new Table(PATH + "testCheckInputStreamTable", new ArrayList<>()); + + // 测试数据 + var bytes = new byte[]{ + 0, + 0, 0, 0, 33, // int 33 + 0, + 63, -103, -103, -102, // float 1.2 + 0, + 12, + 116, 101, 115, 116, + 32, 118, 97, 114, + 99, 104, 97, 114 // varchar test varchar + }; + var inputStream = new ByteArrayInputStream(bytes); + + var intField = new IntField("testCheckInputStreamInt", false, dummy); + assertTrue(intField.checkInputStream(inputStream)); + + var floatField = new FloatField("testCheckInputStreamFloat", false, dummy); + assertTrue(floatField.checkInputStream(inputStream)); + + var varcharField = new VarcharField("testCheckInputStreamVarchar", 20, false, dummy); + assertTrue(varcharField.checkInputStream(inputStream)); + + assertEquals(0, inputStream.available()); + } + + public void testSerialize() { + var dummy = new Table(PATH + "testSerialize", new ArrayList<>()); + + + var intField = new IntField("testSerializeInt", false, dummy); + var intBos1 = new ByteArrayOutputStream(); + var intBos2 = new ByteArrayOutputStream(); + + try { + intField.serialize(intBos1, "33"); + var expected = new byte[]{ + 0, + 0, 0, 0, 33, // int 33 + }; + assertArrayEquals(expected, intBos1.toByteArray()); + } catch (TableConflictException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + intField.serialize(intBos2, "'xx'"); + fail(); + } catch (TableConflictException e) { + log.error("Exception expected: ", e); + } + + var floatField = new FloatField("testSerializeFloat", false, dummy); + var floatBos1 = new ByteArrayOutputStream(); + var floatBos2 = new ByteArrayOutputStream(); + + try { + floatField.serialize(floatBos1, "1.2"); + var expected = new byte[]{ + 0, + 63, -103, -103, -102, // float 1.2 + }; + assertArrayEquals(expected, floatBos1.toByteArray()); + } catch (TableConflictException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + floatField.serialize(floatBos2, "'xx'"); + fail(); + } catch (TableConflictException e) { + log.error("Exception expected: ", e); + } + + var varcharField = new VarcharField("testSerializeVarchar", 20, false, dummy); + var varcharBos1 = new ByteArrayOutputStream(); + var varcharBos2 = new ByteArrayOutputStream(); + + try { + varcharField.serialize(varcharBos1, "'test varchar'"); + var expected = new byte[]{ + 0, + 12, + 116, 101, 115, 116, + 32, 118, 97, 114, + 99, 104, 97, 114 // varchar test varchar + }; + assertArrayEquals(expected, varcharBos1.toByteArray()); + } catch (TableConflictException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + varcharField.serialize(varcharBos2, "'test varchar too looooooooooooong'"); + fail(); + } catch (TableConflictException e) { + log.error("Exception expected: ", e); + } + + } + + public void testDoubleCreateIndex() { + var dummy = new Table(PATH + "testCreateIndexTable", new ArrayList<>()); + + BaseField field = new IntField("testCreateIndexField", false, dummy); + try { + field.createIndex(); + } catch (IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + field.createIndex(); + fail(); + } catch (IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + } + } + + public void testInsertIndex() { + var dummy = new Table(PATH + "testInsertIndexTable", new ArrayList<>()); + + var intField = new IntField("testInsertIndexInt", false, dummy); + + try { + intField.insertIndex("1", 1); + fail(); + } catch (TableExistenceException | TableConflictException e) { + log.error("Exception expected: ", e); + } + + try { + intField.createIndex(); + } catch (IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + intField.insertIndex("1", 1); + intField.insertIndex("1", 1); + } catch (TableExistenceException | TableConflictException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + intField.insertIndex("1xx", 1); + fail(); + } catch (TableExistenceException | TableConflictException e) { + log.error("Exception expected: ", e); + } + + var floatField = new FloatField("testInsertIndexFloat", false, dummy); + + try { + floatField.insertIndex("1.2", 1); + fail(); + } catch (TableExistenceException | TableConflictException e) { + log.error("Exception expected: ", e); + } + + try { + floatField.createIndex(); + } catch (IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + floatField.insertIndex("1.2", 1); + floatField.insertIndex("1.2", 1); + } catch (TableExistenceException | TableConflictException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + floatField.insertIndex("1xx", 1); + fail(); + } catch (TableExistenceException | TableConflictException e) { + log.error("Exception expected: ", e); + } + + + var varcharField = new VarcharField("testInsertIndexVarchar", 20, false, dummy); + + try { + varcharField.insertIndex("xxx", 1); + fail(); + } catch (TableExistenceException e) { + log.error("Exception expected: ", e); + } + + try { + varcharField.createIndex(); + } catch (IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + varcharField.insertIndex("xxx", 1); + varcharField.insertIndex("xxx", 1); + } catch (TableExistenceException e) { + log.error("Exception expected: ", e); + fail(); + } + + } + + public void testQueryIndex() { + var dummy = new Table(PATH + "testQueryIndexTable", new ArrayList<>()); + + var intField = new IntField("testQueryIndexInt", false, dummy); + + try { + intField.createIndex(); + } catch (IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + intField.insertIndex("1", 1); + intField.insertIndex("1", 1); + } catch (TableExistenceException | TableConflictException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + var uuid = intField.queryIndex("1"); + assertEquals(1, uuid.get(0).longValue()); + assertEquals(1, uuid.get(1).longValue()); + } catch (TableExistenceException | TableConflictException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + var uuid = intField.queryIndex("2"); + assertTrue(uuid.isEmpty()); + } catch (TableExistenceException | TableConflictException e) { + log.error("Exception expected: ", e); + fail(); + } + + var floatField = new FloatField("testQueryIndexFloat", false, dummy); + + try { + floatField.createIndex(); + } catch (IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + floatField.insertIndex("1.2", 1); + floatField.insertIndex("1.2", 1); + } catch (TableExistenceException | TableConflictException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + var uuid = floatField.queryIndex("1.2"); + assertEquals(1, uuid.get(0).longValue()); + assertEquals(1, uuid.get(1).longValue()); + } catch (TableExistenceException | TableConflictException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + var uuid = floatField.queryIndex("2.2"); + assertTrue(uuid.isEmpty()); + } catch (TableExistenceException | TableConflictException e) { + log.error("Exception expected: ", e); + fail(); + } + + var varcharField = new VarcharField("testQueryIndexVarchar", 20, false, dummy); + + try { + varcharField.createIndex(); + } catch (IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + varcharField.insertIndex("xxx", 1); + varcharField.insertIndex("xxx", 1); + } catch (TableExistenceException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + var uuid = varcharField.queryIndex("xxx"); + assertEquals(1, uuid.get(0).longValue()); + assertEquals(1, uuid.get(1).longValue()); + } catch (TableExistenceException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + var uuid = varcharField.queryIndex("x"); + assertTrue(uuid.isEmpty()); + } catch (TableExistenceException e) { + log.error("Exception expected: ", e); + fail(); + } + } + + public void testLoad() { + var bytes = new byte[]{ + // testLoadInt + 11, + 116, 101, 115, 116, + 76, 111, 97, 100, + 73, 110, 116, + + // INT + 3, + 73, 78, 84, + + // 00 not nullable and no index + 0, + + // testLoadFloat + 13, + 116, 101, 115, 116, + 76, 111, 97, 100, + 70, 108, 111, 97, 116, + + // FLOAT + 5, + 70, 76, 79, 65, 84, + + 0, + + // testLoadVarchar + 15, + 116, 101, 115, 116, + 76, 111, 97, 100, + 86, 97, 114, 99, + 104, 97, 114, + + // VARCHAR + 7, + 86, 65, 82, 67, + 72, 65, 82, + + 0, + + // 12 + 0, 0, 0, 12 + + }; + var stream = new ByteArrayInputStream(bytes); + var dummy = new Table(PATH + "testLoadTable", new ArrayList<>()); + + var intField = BaseField.load(stream, dummy); + assertNotNull(intField); + assertEquals("testLoadInt", intField.getName()); + assertEquals(FieldType.INT, intField.getType()); + + var floatField = BaseField.load(stream, dummy); + assertNotNull(floatField); + assertEquals("testLoadFloat", floatField.getName()); + assertEquals(FieldType.FLOAT, floatField.getType()); + + var varcharField = BaseField.load(stream, dummy); + assertNotNull(varcharField); + assertEquals("testLoadVarchar", varcharField.getName()); + assertEquals(FieldType.VARCHAR, varcharField.getType()); + assertEquals(12, ((VarcharField) varcharField).getLimit()); + + } + + public void testPersist() { + var dummy = new Table(PATH + "testLoadTable", new ArrayList<>()); + var out = new ByteArrayOutputStream(); + + var intField = new IntField("testPersistInt", false, dummy); + var floatField = new FloatField("testPersistFloat", false, dummy); + var varcharField = new VarcharField("testPersistVarchar", 12, false, dummy); + + intField.persist(out); + floatField.persist(out); + varcharField.persist(out); + + var expected = new byte[]{ + // testPersistInt + 14, + 116, 101, 115, 116, + 80, 101, 114, 115, + 105, 115, 116, 73, 110, 116, + + // INT + 3, + 73, 78, 84, + + 0, + + // testPersistFloat + 16, + 116, 101, 115, 116, + 80, 101, 114, 115, + 105, 115, 116, 70, + 108, 111, 97, 116, + + // FLOAT + 5, + 70, 76, 79, 65, 84, + + 0, + + // testPersistVarchar + 18, + 116, 101, 115, 116, + 80, 101, 114, 115, + 105, 115, 116, 86, + 97, 114, 99, 104, 97, 114, + + // VARCHAR + 7, + 86, 65, 82, 67, + 72, 65, 82, + + 0, + + // 12 + 0, 0, 0, 12 + }; + + assertArrayEquals(expected, out.toByteArray()); + } +} diff --git a/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java b/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java new file mode 100644 index 0000000..3031a0b --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java @@ -0,0 +1,124 @@ +package net.kaaass.rumbase.table; + +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.BaseField; +import net.kaaass.rumbase.table.field.FloatField; +import net.kaaass.rumbase.table.field.IntField; +import net.kaaass.rumbase.table.field.VarcharField; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.io.File; +import java.util.ArrayList; + +/** + * 表管理器的测试 + * + * @author @KveinAxel + * @see net.kaaass.rumbase.table.TableManager + */ +@Slf4j +public class TableManagerTest extends TestCase { + + private static final String PATH = "build/"; + + public void testShowTables() { + var prefix = PATH + "testShowTables"; + + var tbm = new TableManager(); + + var fieldList = new ArrayList(); + var table = new Table(prefix + "Table", fieldList); + + // 增加测试表字段 + var intField = new IntField(prefix + "age", false, table); + var floatField = new FloatField(prefix + "balance", false, table); + var varcharField = new VarcharField(prefix + "name", 20, false, table); + fieldList.add(intField); + fieldList.add(floatField); + fieldList.add(varcharField); + + try { + tbm.createTable(TransactionContext.empty(), prefix + "Table", fieldList, prefix + ".db"); + } catch (TableExistenceException e) { + e.printStackTrace(); + fail(); + } + + var tables = tbm.showTables(); + assertEquals(1, tables.size()); + assertEquals(prefix + "Table", tables.get(0)); + + new File("metadata.db").deleteOnExit(); + } + + public void testCreateTable() { + var prefix = PATH + "testCreateTable"; + + var tbm = new TableManager(); + + var fieldList = new ArrayList(); + var table = new Table(prefix + "Table", fieldList); + + // 增加测试表字段 + var intField = new IntField(prefix + "age", false, table); + var floatField = new FloatField(prefix + "balance", false, table); + var varcharField = new VarcharField(prefix + "name", 20, false, table); + fieldList.add(intField); + fieldList.add(floatField); + fieldList.add(varcharField); + + try { + tbm.createTable(TransactionContext.empty(), prefix + "Table", fieldList, prefix + ".db"); + } catch (TableExistenceException e) { + e.printStackTrace(); + fail(); + } + + new File("metadata.db").deleteOnExit(); + + } + + public void testGetTable() { + var prefix = PATH + "testGetTable"; + + var tbm = new TableManager(); + + var fieldList = new ArrayList(); + var table = new Table(prefix + "Table", fieldList); + + // 增加测试表字段 + var intField = new IntField(prefix + "age", false, table); + var floatField = new FloatField(prefix + "balance", false, table); + var varcharField = new VarcharField(prefix + "name", 20, false, table); + fieldList.add(intField); + fieldList.add(floatField); + fieldList.add(varcharField); + + try { + tbm.createTable(TransactionContext.empty(), prefix + "Table", fieldList, prefix + ".db"); + } catch (TableExistenceException e) { + e.printStackTrace(); + fail(); + } + + try { + var t = tbm.getTable(prefix + "Table"); + assertEquals(prefix + "Table", t.tableName); + assertEquals(intField.getName(), t.fields.get(0).getName()); + assertEquals(intField.getType(), t.fields.get(0).getType()); + assertEquals(floatField.getName(), t.fields.get(1).getName()); + assertEquals(floatField.getType(), t.fields.get(1).getType()); + assertEquals(varcharField.getName(), t.fields.get(2).getName()); + assertEquals(varcharField.getType(), t.fields.get(2).getType()); + assertEquals(varcharField.getLimit(), ((VarcharField) t.fields.get(2)).getLimit()); + + } catch (TableExistenceException e) { + fail(); + } + + new File("metadata.db").deleteOnExit(); + } + +} diff --git a/src/test/java/net/kaaass/rumbase/table/TableTest.java b/src/test/java/net/kaaass/rumbase/table/TableTest.java new file mode 100644 index 0000000..575d799 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/table/TableTest.java @@ -0,0 +1,604 @@ +package net.kaaass.rumbase.table; + +import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; +import com.igormaznitsa.jbbp.io.JBBPByteOrder; +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.RecordManager; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.*; +import net.kaaass.rumbase.transaction.TransactionContext; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertArrayEquals; + +/** + * 表结构测试 + * + * @author @KveinAxel + * @see Table + */ +@Slf4j +public class TableTest extends TestCase { + + private static final String PATH = "build/"; + + public void testLoad() { + var prefix = PATH + "testLoad"; + + var byteOS = new ByteArrayOutputStream(); + var out = new JBBPBitOutputStream(byteOS); + try { + out.writeString("testLoadTable", JBBPByteOrder.BIG_ENDIAN); + out.writeString("NORMAL", JBBPByteOrder.BIG_ENDIAN); + out.writeLong(-1, JBBPByteOrder.BIG_ENDIAN); + out.writeInt(3, JBBPByteOrder.BIG_ENDIAN); + out.writeString("testLoadInt", JBBPByteOrder.BIG_ENDIAN); + out.writeString("INT", JBBPByteOrder.BIG_ENDIAN); + out.writeBytes(new byte[]{0}, 1, JBBPByteOrder.BIG_ENDIAN); + out.writeString("testLoadFloat", JBBPByteOrder.BIG_ENDIAN); + out.writeString("FLOAT", JBBPByteOrder.BIG_ENDIAN); + out.writeBytes(new byte[]{0}, 1, JBBPByteOrder.BIG_ENDIAN); + out.writeString("testLoadVarchar", JBBPByteOrder.BIG_ENDIAN); + out.writeString("VARCHAR", JBBPByteOrder.BIG_ENDIAN); + out.writeBytes(new byte[]{0}, 1, JBBPByteOrder.BIG_ENDIAN); + out.writeInt(12, JBBPByteOrder.BIG_ENDIAN); + } catch (IOException e) { + log.error("Exception expected: ", e); + fail(); + } + + var storage = RecordManager.fromFile(prefix + "Table"); + storage.setMetadata(TransactionContext.empty(), byteOS.toByteArray()); + + var table = Table.load(RecordManager.fromFile(prefix + "Table")); + + assertNotNull(table); + assertEquals("testLoadTable", table.getTableName()); + assertEquals(-1L, table.getNext()); + assertEquals(TableStatus.NORMAL, table.getStatus()); + + var fields = table.getFields(); + assertEquals("testLoadInt", fields.get(0).getName()); + assertEquals(FieldType.INT, fields.get(0).getType()); + assertEquals("testLoadFloat", fields.get(1).getName()); + assertEquals(FieldType.FLOAT, fields.get(1).getType()); + assertEquals("testLoadVarchar", fields.get(2).getName()); + assertEquals(FieldType.VARCHAR, fields.get(2).getType()); + assertEquals(12, ((VarcharField) fields.get(2)).getLimit()); + } + + public void testPersist() { + + var fieldList = new ArrayList(); + var table = new Table("testPersistTable", fieldList); + var context = TransactionContext.empty(); + + fieldList.add(new IntField("testPersistInt", false, table)); + fieldList.add(new FloatField("testPersistFloat", false, table)); + fieldList.add(new VarcharField("testPersistVarchar", 12, false, table)); + + var byteOS = new ByteArrayOutputStream(); + var out = new JBBPBitOutputStream(byteOS); + try { + out.writeString("testPersistTable", JBBPByteOrder.BIG_ENDIAN); + out.writeString("NORMAL", JBBPByteOrder.BIG_ENDIAN); + out.writeLong(-1, JBBPByteOrder.BIG_ENDIAN); + out.writeInt(3, JBBPByteOrder.BIG_ENDIAN); + out.writeString("testPersistInt", JBBPByteOrder.BIG_ENDIAN); + out.writeString("INT", JBBPByteOrder.BIG_ENDIAN); + out.writeBytes(new byte[]{0}, 1, JBBPByteOrder.BIG_ENDIAN); + out.writeString("testPersistFloat", JBBPByteOrder.BIG_ENDIAN); + out.writeString("FLOAT", JBBPByteOrder.BIG_ENDIAN); + out.writeBytes(new byte[]{0}, 1, JBBPByteOrder.BIG_ENDIAN); + out.writeString("testPersistVarchar", JBBPByteOrder.BIG_ENDIAN); + out.writeString("VARCHAR", JBBPByteOrder.BIG_ENDIAN); + out.writeBytes(new byte[]{0}, 1, JBBPByteOrder.BIG_ENDIAN); + out.writeInt(12, JBBPByteOrder.BIG_ENDIAN); + } catch (IOException e) { + log.error("Exception expected: ", e); + fail(); + } + + assertArrayEquals(new byte[0], table.getRecordStorage().getMetadata(context)); + table.persist(TransactionContext.empty()); + assertArrayEquals(byteOS.toByteArray(), table.getRecordStorage().getMetadata(context)); + + } + + Table createTestTable(String prefix) { + var fieldList = new ArrayList(); + var table = new Table(PATH + prefix + "Table", fieldList); + + // 增加测试表字段 + var intField = new IntField(prefix + "age", false, table); + var floatField = new FloatField(prefix + "balance", false, table); + var varcharField = new VarcharField(prefix + "name", 20, false, table); + fieldList.add(intField); + fieldList.add(floatField); + fieldList.add(varcharField); + + // 创建字段索引 + try { + intField.createIndex(); + floatField.createIndex(); + } catch (IndexAlreadyExistException e) { + log.error("Exception expected: ", e); + fail(); + } + + return table; + } + + public void testCURD() { + + var prefix = "testCURD"; + + // 创建待测试表 + var context = TransactionContext.empty(); + var table = createTestTable(prefix); + + // 添加记录 + var data = new ArrayList(); + data.add("33"); + data.add("1.2"); + data.add("'test varchar'"); + + var data2 = new ArrayList(); + data2.add("1"); + data2.add("-0.4"); + data2.add("'ya test varchar'"); + + // 插入记录 + try { + table.insert(context, data); + table.insert(context, data2); + } catch (TableConflictException | TableExistenceException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + // 查询记录,测试插入情况与查询情况 + var iter = table.searchFirst(prefix + "age", "0"); + assertTrue(iter.hasNext()); + var pair1 = iter.next(); + assertTrue(iter.hasNext()); + var pair2 = iter.next(); + assertFalse(iter.hasNext()); + assertEquals(1L, pair1.getKey()); + assertEquals(33L, pair2.getKey()); + + var res1 = table.read(context, pair1.getUuid()); + var res2 = table.read(context, pair2.getUuid()); + + assertTrue(res1.isPresent()); + assertTrue(res2.isPresent()); + + assertEquals(1, (int) res1.get().get(0)); + assertEquals(-0.4f, res1.get().get(1)); + assertEquals("ya test varchar", (String) res1.get().get(2)); + + assertEquals(33, (int) res2.get().get(0)); + assertEquals(1.2f, res2.get().get(1)); + assertEquals("test varchar", (String) res2.get().get(2)); + + // 测试删除记录 + table.delete(context, pair2.getUuid()); + + var iter2 = table.searchFirst(prefix + "age", "33"); + assertTrue(iter2.hasNext()); + var pair3 = iter2.next(); + assertEquals(33L, pair3.getKey()); + + var res3 = table.read(context, pair3.getUuid()); + assertTrue(res3.isEmpty()); // 记录不存在 + + // 测试更新记录 + table.update(context, pair1.getUuid(), data); + + var iter3 = table.searchFirst(prefix + "age", "33"); + assertTrue(iter3.hasNext()); + var pair4 = iter3.next(); + assertTrue(iter3.hasNext()); + var pair5 = iter3.next(); + + // 有效的被更新记录 + assertEquals(33L, pair4.getKey()); + var res4 = table.read(context, pair4.getUuid()); + assertTrue(res4.isPresent()); + + // 四记录 + assertEquals(33L, pair5.getKey()); + var res5 = table.read(context, pair5.getUuid()); + assertTrue(res5.isEmpty()); + + // 测试记录是否被更新 + assertEquals(33, (int) res4.get().get(0)); + assertEquals(1.2f, res4.get().get(1)); + assertEquals("test varchar", (String) res4.get().get(2)); + + + } catch (TableExistenceException | TableConflictException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + } + + void addTestData(TransactionContext context, Table table) throws TableConflictException, TableExistenceException, ArgumentException { + table.insert(context, new ArrayList<>() {{ + add("1"); + add("1.2"); + add("'test varchar'"); + }}); + table.insert(context, new ArrayList<>() {{ + add("2"); + add("1.2"); + add("'test varchar'"); + }}); + table.insert(context, new ArrayList<>() {{ + add("3"); + add("1.2"); + add("'test varchar'"); + }}); + table.insert(context, new ArrayList<>() {{ + add("3"); + add("1.2"); + add("'test varchar'"); + }}); + table.insert(context, new ArrayList<>() {{ + add("4"); + add("1.2"); + add("'test varchar'"); + }}); + table.insert(context, new ArrayList<>() {{ + add("5"); + add("1.2"); + add("'test varchar'"); + }}); + table.insert(context, new ArrayList<>() {{ + add("6"); + add("1.2"); + add("'test varchar'"); + }}); + table.insert(context, new ArrayList<>() {{ + add("7"); + add("1.2"); + add("'test varchar'"); + }}); + table.insert(context, new ArrayList<>() {{ + add("7"); + add("1.2"); + add("'test varchar'"); + }}); + table.insert(context, new ArrayList<>() {{ + add("8"); + add("1.2"); + add("'test varchar'"); + }}); + } + + public void testSearch() { + + var prefix = "testSearch"; + + // 创建待测试表 + var context = TransactionContext.empty(); + var table = createTestTable(prefix); + + // 添加记录 + try { + addTestData(context, table); + } catch (TableConflictException | TableExistenceException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 查询记录 + // 测试search + try { + var uuids = table.search(prefix + "age", "7"); + var resList = new ArrayList>(); + for (var uuid : uuids) { + var res = table.read(context, uuid); + res.ifPresent(resList::add); + } + assertEquals(7, (int) resList.get(0).get(0)); + assertEquals(1.2f, resList.get(0).get(1)); + assertEquals("test varchar", (String) resList.get(0).get(2)); + + assertEquals(7, (int) resList.get(1).get(0)); + assertEquals(1.2f, resList.get(1).get(1)); + assertEquals("test varchar", (String) resList.get(1).get(2)); + + } catch (TableExistenceException | TableConflictException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + + } + + public void testSearchAll() { + + var prefix = "testSearchAll"; + + // 创建待测试表 + var context = TransactionContext.empty(); + var table = createTestTable(prefix); + + // 添加记录 + try { + addTestData(context, table); + } catch (TableConflictException | TableExistenceException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + + + // 测试searchAll + try { + var uuids = table.searchAll(prefix + "age"); + var resList = new ArrayList>(); + while (uuids.hasNext()) { + var uuid = uuids.next(); + table.read(context, uuid.getUuid()).ifPresent(resList::add); + } + + assertEquals(1, (int) resList.get(0).get(0)); + assertEquals(1.2f, resList.get(0).get(1)); + assertEquals("test varchar", (String) resList.get(0).get(2)); + + assertEquals(2, (int) resList.get(1).get(0)); + assertEquals(1.2f, resList.get(1).get(1)); + assertEquals("test varchar", (String) resList.get(1).get(2)); + + assertEquals(3, (int) resList.get(2).get(0)); + assertEquals(1.2f, resList.get(2).get(1)); + assertEquals("test varchar", (String) resList.get(2).get(2)); + + assertEquals(3, (int) resList.get(3).get(0)); + assertEquals(1.2f, resList.get(3).get(1)); + assertEquals("test varchar", (String) resList.get(3).get(2)); + + assertEquals(4, (int) resList.get(4).get(0)); + assertEquals(1.2f, resList.get(4).get(1)); + assertEquals("test varchar", (String) resList.get(4).get(2)); + + assertEquals(5, (int) resList.get(5).get(0)); + assertEquals(1.2f, resList.get(5).get(1)); + assertEquals("test varchar", (String) resList.get(5).get(2)); + + assertEquals(6, (int) resList.get(6).get(0)); + assertEquals(1.2f, resList.get(6).get(1)); + assertEquals("test varchar", (String) resList.get(6).get(2)); + + assertEquals(7, (int) resList.get(7).get(0)); + assertEquals(1.2f, resList.get(7).get(1)); + assertEquals("test varchar", (String) resList.get(7).get(2)); + + assertEquals(7, (int) resList.get(8).get(0)); + assertEquals(1.2f, resList.get(8).get(1)); + assertEquals("test varchar", (String) resList.get(8).get(2)); + + assertEquals(8, (int) resList.get(9).get(0)); + assertEquals(1.2f, resList.get(9).get(1)); + assertEquals("test varchar", (String) resList.get(9).get(2)); + + + } catch (TableExistenceException | TableConflictException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + } + + public void testSearchFirst() { + var prefix = "testSearchFirst"; + + // 创建待测试表 + var context = TransactionContext.empty(); + var table = createTestTable(prefix); + + // 添加记录 + try { + addTestData(context, table); + } catch (TableConflictException | TableExistenceException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + // 测试searchFirst + try { + var uuids = table.searchFirst(prefix + "age", "3"); + var resList = new ArrayList>(); + while (uuids.hasNext()) { + var uuid = uuids.next(); + table.read(context, uuid.getUuid()).ifPresent(resList::add); + } + + assertEquals(3, (int) resList.get(0).get(0)); + assertEquals(1.2f, resList.get(0).get(1)); + assertEquals("test varchar", (String) resList.get(0).get(2)); + + assertEquals(3, (int) resList.get(1).get(0)); + assertEquals(1.2f, resList.get(1).get(1)); + assertEquals("test varchar", (String) resList.get(1).get(2)); + + assertEquals(4, (int) resList.get(2).get(0)); + assertEquals(1.2f, resList.get(2).get(1)); + assertEquals("test varchar", (String) resList.get(2).get(2)); + + } catch (TableExistenceException | TableConflictException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + } + + public void testSearchFirstNotEqual() { + var prefix = "testSearchFirstNotEqual"; + + // 创建待测试表 + var context = TransactionContext.empty(); + var table = createTestTable(prefix); + + // 添加记录 + try { + addTestData(context, table); + } catch (TableConflictException | TableExistenceException | ArgumentException e) { + log.error("Exception expected: ", e); + fail(); + } + + // 测试searchFirstNotEqual + try { + var uuids = table.searchFirstNotEqual(prefix + "age", "3"); + var resList = new ArrayList>(); + while (uuids.hasNext()) { + var uuid = uuids.next(); + table.read(context, uuid.getUuid()).ifPresent(resList::add); + } + + assertEquals(4, (int) resList.get(0).get(0)); + assertEquals(1.2f, resList.get(0).get(1)); + assertEquals("test varchar", (String) resList.get(0).get(2)); + + } catch (TableExistenceException | TableConflictException | RecordNotFoundException e) { + log.error("Exception expected: ", e); + fail(); + } + } + + public void testCheckStringEntry() { + var passEntry = new ArrayList(); + passEntry.add("33"); + passEntry.add("1.2"); + passEntry.add("'test varchar'"); + + var failEntry = new ArrayList(); + failEntry.add("33"); + failEntry.add("'test varchar'"); + failEntry.add("1.2"); + + var fieldList = new ArrayList(); + var table = new Table("testCheckStringEntryTable", fieldList); + var intField = new IntField("testCheckStringEntryInt", false, table); + var floatField = new FloatField("testCheckStringEntryFloat", false, table); + var varcharField = new VarcharField("testCheckStringEntryVarchar", 20, false, table); + fieldList.add(intField); + fieldList.add(floatField); + fieldList.add(varcharField); + + assertTrue(table.checkStringEntry(passEntry)); + assertFalse(table.checkStringEntry(failEntry)); + + } + + public void testStringEntryToBytes() { + var passEntry = new ArrayList(); + passEntry.add("33"); + passEntry.add("1.2"); + passEntry.add("'test varchar'"); + + var failEntry = new ArrayList(); + failEntry.add("33"); + failEntry.add("'test varchar'"); + failEntry.add("1.2"); + + var fieldList = new ArrayList(); + var table = new Table("testStringEntryToBytesTable", fieldList); + var intField = new IntField("testStringEntryToBytesInt", false, table); + var floatField = new FloatField("testStringEntryToBytesFloat", false, table); + var varcharField = new VarcharField("testStringEntryToBytesVarchar", 20, false, table); + fieldList.add(intField); + fieldList.add(floatField); + fieldList.add(varcharField); + + try { + var bytes = table.stringEntryToBytes(passEntry); + var expected = new byte[]{ + 0, + 0, 0, 0, 33, // int 33 + 0, + 63, -103, -103, -102, // float 1.2 + 0, + 12, + 116, 101, 115, 116, + 32, 118, 97, 114, + 99, 104, 97, 114 // varchar test varchar + }; + assertArrayEquals(expected, bytes); + } catch (TableConflictException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + table.stringEntryToBytes(failEntry); + fail(""); + } catch (TableConflictException e) { + log.error("Exception expected: ", e); + } + } + + public void testParseEntry() { + var passEntry = new byte[]{ + 0, + 0, 0, 0, 33, // int 33 + 0, + 63, -103, -103, -102, // float 1.2 + 0, + 12, + 116, 101, 115, 116, + 32, 118, 97, 114, + 99, 104, 97, 114 // varchar test varchar + }; + + var failEntry = new byte[]{ + 0, + 0, 0, 0, 33, // int 33 + 0, + 12, + 116, 101, 115, 116, + 32, 118, 97, 114, + 99, 104, 97, 114, // varchar test varchar + 0, + 63, -103, -103, -102, // float 1.2 + }; + + var fieldList = new ArrayList(); + var table = new Table("testParseEntryTable", fieldList); + var intField = new IntField("testParseEntryInt", false, table); + var floatField = new FloatField("testParseEntryFloat", false, table); + var varcharField = new VarcharField("testParseEntryVarchar", 20, false, table); + fieldList.add(intField); + fieldList.add(floatField); + fieldList.add(varcharField); + + try { + var list = table.parseEntry(passEntry); + assertEquals(33, (int) list.get(0)); + assertEquals(1.2f, list.get(1)); + assertEquals("test varchar", (String) list.get(2)); + } catch (TableConflictException | IOException e) { + log.error("Exception expected: ", e); + fail(); + } + + try { + table.parseEntry(failEntry); + fail(); + } catch (TableConflictException | IOException e) { + log.error("Exception expected: ", e); + } + + } + + +} From 20d80390ba99287806c0a80b0e814bf3755b9155 Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Sun, 17 Jan 2021 13:31:24 +0800 Subject: [PATCH 16/18] =?UTF-8?q?[+]=E5=AE=9E=E7=8E=B0=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E6=A8=A1=E5=9D=97=20(#19)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 将接口、mock改成普通类 * 将接口、mock改成普通类 * 使用访问者模式替代原先的switch匹配 * 去除visitor, 重构表管理 * 去除visitor, 重构表管理 * 重写jbbp解析方法,增加测试 * 增加表配置信息操作 * 增加表加载测试 * 增加系统表和测试 * 完成Select语句的语法树定义 * 完成Insert语句的语法树定义 * 完成Update语句的语法树定义 * 完成Delete语句的语法树定义 * 完成Create table语句的语法树定义 * 完成Create index语句的语法树定义 * 格式化代码 * 增加空字段,增加field的方法 * 增加query模块 * 更新测试用例的序列化格式 * 修复空值Condition * 增加创建索引测试 * 更改测试文件路径 * 完成服务器程序基本框架 * 字符串解析增加单引号 * 完成echo服务器 * 增加增删改、创建表的执行测试 * 更改测试路径 * 实现访问者模式处理sql语句 * 改进select语句解析 * 完善语句解析的返回值,使用泛型 * 完成语句执行逻辑 * 增加select测试 * 修复delete测试 * 实现指令解析,完成当前所有指令运行的测试 * 将mock换为RecordManager * 完成自动提交 * 完成todo/fixme * 增加获取所有文件路径的api * 修正测试文件路径 * 增加field加载时的索引重建 * 修复未抛出的异常 * 修正执行的系列错误 * 修正自动提交事务上下文更改的问题 * 修正ci重复进行测试 * 修复投影获得空列表的bug * 修复列未空的插入请求 * 修正select输出格式 * 修复metadata写入bug * unpin遗漏 * 增加CreateIndex时的表结构序列化 * 修复物理记录查找问题 * 修复错误@NonNull的问题 * 更新dummy * 修正一系列select解析问题 * 修正between计算问题 * 增加插入列检查 * record提供路径名 * 更改文件目录 * 创建数据文件夹 * 增加flush、exec语句 * 修正null值错误处理 * 通过记录来保存数据字典 * flush错误处理 * 修正系列问题,增加recordapi * 修正测试用例编译 * 修复metadata加载问题 * 修复metadata加载问题 * 修正一处forEach * 修复列persist标志位 * metadata加载的空指针问题 * 修改测试用例 * 修正若干错误测试用例 * 修正表模块单元测试路径 * 统一单元测试文件路径 Co-authored-by: Kevin Axel Manjaro --- .travis.yml | 1 - src/main/java/net/kaaass/rumbase/Main.java | 14 +- .../kaaass/rumbase/dataitem/ItemStorage.java | 39 +- .../java/net/kaaass/rumbase/page/RumPage.java | 2 + .../rumbase/parse/ConditionExpression.java | 11 +- .../kaaass/rumbase/parse/ISqlStatement.java | 2 + .../rumbase/parse/ISqlStatementVisitor.java | 37 ++ .../net/kaaass/rumbase/parse/SqlParser.java | 27 +- .../parse/exception/SqlSyntaxException.java | 6 + .../parse/parser/CommandStatementParser.java | 47 ++ .../parser/command/ExecStatementParser.java | 22 + .../CreateIndexStatementParser.java | 3 +- .../CreateTableStatementParser.java | 9 +- .../{ => jsqlp}/DeleteStatementParser.java | 3 +- .../{ => jsqlp}/InsertStatementParser.java | 4 +- .../parse/parser/{ => jsqlp}/ParserUtil.java | 2 +- .../{ => jsqlp}/SelectStatementParser.java | 64 ++- .../{ => jsqlp}/UpdateStatementParser.java | 3 +- .../rumbase/parse/stmt/CommitStatement.java | 16 + .../parse/stmt/CreateIndexStatement.java | 6 + .../parse/stmt/CreateTableStatement.java | 6 + .../rumbase/parse/stmt/DeleteStatement.java | 6 + .../rumbase/parse/stmt/ExecStatement.java | 23 + .../rumbase/parse/stmt/ExitStatement.java | 16 + .../rumbase/parse/stmt/FlushStatement.java | 16 + .../rumbase/parse/stmt/InsertStatement.java | 6 + .../rumbase/parse/stmt/RollbackStatement.java | 16 + .../rumbase/parse/stmt/SelectStatement.java | 6 + .../rumbase/parse/stmt/ShutdownStatement.java | 16 + .../parse/stmt/StartTransactionStatement.java | 16 + .../rumbase/parse/stmt/UpdateStatement.java | 6 + .../rumbase/query/CreateIndexExecutor.java | 5 + .../rumbase/query/CreateTableExecutor.java | 13 +- .../kaaass/rumbase/query/InsertExecutor.java | 17 +- .../kaaass/rumbase/query/SelectExecutor.java | 2 +- .../kaaass/rumbase/record/IRecordStorage.java | 2 + .../rumbase/record/MvccRecordStorage.java | 2 + .../kaaass/rumbase/record/RecordManager.java | 8 + .../record/mock/MockRecordStorage.java | 5 + .../net/kaaass/rumbase/server/Server.java | 139 +++++ .../net/kaaass/rumbase/server/Session.java | 477 ++++++++++++++++++ .../java/net/kaaass/rumbase/table/Table.java | 24 +- .../kaaass/rumbase/table/TableManager.java | 142 ++++-- .../kaaass/rumbase/table/field/BaseField.java | 55 +- .../rumbase/table/field/FloatField.java | 11 +- .../kaaass/rumbase/table/field/IntField.java | 11 +- .../rumbase/table/field/VarcharField.java | 12 +- .../transaction/TransactionManagerImpl.java | 2 +- .../java/net/kaaass/rumbase/FileUtil.java | 55 ++ .../rumbase/dataitem/IItemStorageTest.java | 29 +- .../net/kaaass/rumbase/index/BTreeTest.java | 59 ++- .../rumbase/index/ConcurrentIndexTest.java | 25 +- .../net/kaaass/rumbase/index/IndexTest.java | 39 +- .../kaaass/rumbase/page/PageStorageTest.java | 25 +- .../net/kaaass/rumbase/page/PageTest.java | 23 +- .../query/CreateIndexExecutorTest.java | 82 +-- .../query/CreateTableExecutorTest.java | 53 +- .../rumbase/query/DeleteExecutorTest.java | 110 ++-- .../rumbase/query/InsertExecutorTest.java | 65 +-- .../rumbase/query/SelectExecutorTest.java | 144 +++--- .../rumbase/query/UpdateExecutorTest.java | 160 +++--- .../rumbase/record/IRecordStorageTest.java | 32 +- .../rumbase/record/MvccReadCommitTest.java | 93 ++-- .../record/MvccReadRepeatableTest.java | 107 ++-- .../kaaass/rumbase/table/BaseFieldTest.java | 216 ++++---- .../rumbase/table/TableManagerTest.java | 98 ++-- .../net/kaaass/rumbase/table/TableTest.java | 253 ++++++---- .../transaction/TransactionContextTest.java | 38 +- 68 files changed, 2218 insertions(+), 866 deletions(-) create mode 100644 src/main/java/net/kaaass/rumbase/parse/ISqlStatementVisitor.java create mode 100644 src/main/java/net/kaaass/rumbase/parse/parser/CommandStatementParser.java create mode 100644 src/main/java/net/kaaass/rumbase/parse/parser/command/ExecStatementParser.java rename src/main/java/net/kaaass/rumbase/parse/parser/{ => jsqlp}/CreateIndexStatementParser.java (91%) rename src/main/java/net/kaaass/rumbase/parse/parser/{ => jsqlp}/CreateTableStatementParser.java (82%) rename src/main/java/net/kaaass/rumbase/parse/parser/{ => jsqlp}/DeleteStatementParser.java (89%) rename src/main/java/net/kaaass/rumbase/parse/parser/{ => jsqlp}/InsertStatementParser.java (90%) rename src/main/java/net/kaaass/rumbase/parse/parser/{ => jsqlp}/ParserUtil.java (96%) rename src/main/java/net/kaaass/rumbase/parse/parser/{ => jsqlp}/SelectStatementParser.java (59%) rename src/main/java/net/kaaass/rumbase/parse/parser/{ => jsqlp}/UpdateStatementParser.java (93%) create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/CommitStatement.java create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/ExecStatement.java create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/ExitStatement.java create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/FlushStatement.java create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/RollbackStatement.java create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/ShutdownStatement.java create mode 100644 src/main/java/net/kaaass/rumbase/parse/stmt/StartTransactionStatement.java create mode 100644 src/main/java/net/kaaass/rumbase/server/Server.java create mode 100644 src/main/java/net/kaaass/rumbase/server/Session.java create mode 100644 src/test/java/net/kaaass/rumbase/FileUtil.java diff --git a/.travis.yml b/.travis.yml index 115da55..336a154 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,4 @@ dist: trusty group: edge script: - - ./gradlew check - ./gradlew build --scan -s diff --git a/src/main/java/net/kaaass/rumbase/Main.java b/src/main/java/net/kaaass/rumbase/Main.java index 69e9221..25d9bc6 100644 --- a/src/main/java/net/kaaass/rumbase/Main.java +++ b/src/main/java/net/kaaass/rumbase/Main.java @@ -1,6 +1,7 @@ package net.kaaass.rumbase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.server.Server; /** * 入口类 @@ -11,6 +12,17 @@ public class Main { public static void main(String[] args) { - log.debug("Rumbase"); + log.info("Rumbase DBMS"); + // 启动 + log.info("Start preparing..."); + Server.getInstance().prepare(); + // 注册程序退出事件 + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + log.info("Shutdown..."); + Server.getInstance().shutdown(); + }, "Shutdown-thread")); + // 运行 + log.info("Starting server..."); + Server.getInstance().run(); } } diff --git a/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java b/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java index 7cb3217..7e054bb 100644 --- a/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java +++ b/src/main/java/net/kaaass/rumbase/dataitem/ItemStorage.java @@ -98,27 +98,28 @@ public static IItemStorage ofFile(String fileName) throws FileException, PageExc var pageStorage = PageManager.fromFile(fileName); var header = pageStorage.get(0); header.pin(); - if (checkTableHeader(header)) { - // 如果表头标志存在,就解析对应表头信息 - var h = parseHeader(header); - header.unpin(); - return new ItemStorage(fileName, h.tempFreePage, h.headerUuid, pageStorage); - } else { - // 若表头标志不存在,就初始化对应的表信息。 - // 只初始化headerFlag和tempFreePage,表头信息位置统一由setMetadata来实现 - byte[] bytes; - try { - bytes = JBBPOut.BeginBin(). - Byte(1, 2, 3, 4). - Int(1). - End().toByteArray(); - } catch (IOException e) { - header.unpin(); - throw new PageCorruptedException(1, e); + try { + if (checkTableHeader(header)) { + // 如果表头标志存在,就解析对应表头信息 + var h = parseHeader(header); + return new ItemStorage(fileName, h.tempFreePage, h.headerUuid, pageStorage); + } else { + // 若表头标志不存在,就初始化对应的表信息。 + // 只初始化headerFlag和tempFreePage,表头信息位置统一由setMetadata来实现 + byte[] bytes; + try { + bytes = JBBPOut.BeginBin(). + Byte(1, 2, 3, 4). + Int(1). + End().toByteArray(); + } catch (IOException e) { + throw new PageCorruptedException(1, e); + } + header.patchData(0, bytes); + return new ItemStorage(fileName, 1, 0, pageStorage); } - header.patchData(0, bytes); + } finally { header.unpin(); - return new ItemStorage(fileName, 1, 0, pageStorage); } } diff --git a/src/main/java/net/kaaass/rumbase/page/RumPage.java b/src/main/java/net/kaaass/rumbase/page/RumPage.java index f7bc3f7..ffa25a6 100644 --- a/src/main/java/net/kaaass/rumbase/page/RumPage.java +++ b/src/main/java/net/kaaass/rumbase/page/RumPage.java @@ -76,6 +76,8 @@ public void flush() throws FileException { throw new FileException(2); } out.close(); + } catch (FileException e) { + throw e; } catch (Exception e) { e.printStackTrace(); } diff --git a/src/main/java/net/kaaass/rumbase/parse/ConditionExpression.java b/src/main/java/net/kaaass/rumbase/parse/ConditionExpression.java index 60ea8a4..5aa10b7 100644 --- a/src/main/java/net/kaaass/rumbase/parse/ConditionExpression.java +++ b/src/main/java/net/kaaass/rumbase/parse/ConditionExpression.java @@ -3,7 +3,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import net.kaaass.rumbase.parse.exception.EvaluationException; -import net.kaaass.rumbase.parse.parser.ParserUtil; +import net.kaaass.rumbase.parse.parser.jsqlp.ParserUtil; import net.sf.jsqlparser.expression.*; import net.sf.jsqlparser.expression.operators.arithmetic.*; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; @@ -24,7 +24,6 @@ public class ConditionExpression { public static double PRECISION = 0.00001; - @NonNull private final Expression expression; @NonNull @@ -38,6 +37,9 @@ public class ConditionExpression { * @param paramMap 参数列表,其中参数必须是原生类型的装箱对象,如Integer、String */ public boolean evaluate(Map paramMap) { + if (expression == null) { + return true; + } updateParam(); var parser = new DeParser(paramMap); expression.accept(parser); @@ -58,6 +60,9 @@ public boolean evaluate(Map paramMap) { * 获得表达式求值需要的参数 */ public List getParams() { + if (expression == null) { + return List.of(); + } updateParam(); return List.copyOf(paramColumn.values()); } @@ -205,7 +210,7 @@ public void visit(Between between) { var b = stack.pop(); var a = stack.pop(); Comparator cmp = Comparator.naturalOrder(); - stack.push(cmp.compare(a, b) <= 0 && cmp.compare(b, c) < 0); + stack.push(cmp.compare(b, a) <= 0 && cmp.compare(a, c) < 0); } @Override diff --git a/src/main/java/net/kaaass/rumbase/parse/ISqlStatement.java b/src/main/java/net/kaaass/rumbase/parse/ISqlStatement.java index 90236c0..5d58b37 100644 --- a/src/main/java/net/kaaass/rumbase/parse/ISqlStatement.java +++ b/src/main/java/net/kaaass/rumbase/parse/ISqlStatement.java @@ -6,4 +6,6 @@ * @author kaaass */ public interface ISqlStatement { + + T accept(ISqlStatementVisitor visitor); } diff --git a/src/main/java/net/kaaass/rumbase/parse/ISqlStatementVisitor.java b/src/main/java/net/kaaass/rumbase/parse/ISqlStatementVisitor.java new file mode 100644 index 0000000..833849f --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/ISqlStatementVisitor.java @@ -0,0 +1,37 @@ +package net.kaaass.rumbase.parse; + +import net.kaaass.rumbase.parse.stmt.*; + +/** + * SQL语句访问者,用于处理对应SQL语句 + * + * @author kaaass + */ +public interface ISqlStatementVisitor { + + T visit(SelectStatement statement); + + T visit(InsertStatement statement); + + T visit(UpdateStatement statement); + + T visit(DeleteStatement statement); + + T visit(CreateIndexStatement statement); + + T visit(CreateTableStatement statement); + + T visit(StartTransactionStatement statement); + + T visit(CommitStatement statement); + + T visit(RollbackStatement statement); + + T visit(ExitStatement statement); + + T visit(ShutdownStatement statement); + + T visit(FlushStatement statement); + + T visit(ExecStatement statement); +} diff --git a/src/main/java/net/kaaass/rumbase/parse/SqlParser.java b/src/main/java/net/kaaass/rumbase/parse/SqlParser.java index 2e6417e..62e803e 100644 --- a/src/main/java/net/kaaass/rumbase/parse/SqlParser.java +++ b/src/main/java/net/kaaass/rumbase/parse/SqlParser.java @@ -1,7 +1,11 @@ package net.kaaass.rumbase.parse; import net.kaaass.rumbase.parse.exception.SqlSyntaxException; -import net.kaaass.rumbase.parse.parser.*; +import net.kaaass.rumbase.parse.parser.CommandStatementParser; +import net.kaaass.rumbase.parse.parser.JsqlpStatementParser; +import net.kaaass.rumbase.parse.parser.command.ExecStatementParser; +import net.kaaass.rumbase.parse.parser.jsqlp.*; +import net.kaaass.rumbase.parse.stmt.*; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; @@ -16,7 +20,17 @@ */ public class SqlParser { - private static List jsqlpStatementParsers = new ArrayList<>() {{ + private static final List> STRING_STATEMENT_PARSERS = new ArrayList<>() {{ + add(new CommandStatementParser(StartTransactionStatement.class, "start transaction")); + add(new CommandStatementParser(CommitStatement.class, "commit")); + add(new CommandStatementParser(RollbackStatement.class, "rollback")); + add(new CommandStatementParser(ExitStatement.class, "exit")); + add(new CommandStatementParser(ShutdownStatement.class, "shutdown")); + add(new CommandStatementParser(FlushStatement.class, "flush")); + add(new ExecStatementParser()); + }}; + + private static final List JSQLP_STATEMENT_PARSERS = new ArrayList<>() {{ add(new SelectStatementParser()); add(new InsertStatementParser()); add(new UpdateStatementParser()); @@ -29,7 +43,12 @@ public class SqlParser { * 将语句解析为SQL语法树 */ public static ISqlStatement parseStatement(String sql) throws SqlSyntaxException { - // TODO + // 尝试字符串解析器解析 + for (var parser : STRING_STATEMENT_PARSERS) { + if (parser.checkStatement(sql)) { + return parser.parse(sql); + } + } // 尝试 JSqlParser 解析 Statement stmt; try { @@ -37,7 +56,7 @@ public static ISqlStatement parseStatement(String sql) throws SqlSyntaxException } catch (JSQLParserException e) { throw new SqlSyntaxException(1, e); } - for (var parser : jsqlpStatementParsers) { + for (var parser : JSQLP_STATEMENT_PARSERS) { if (parser.checkStatement(stmt)) { return parser.parse(stmt); } diff --git a/src/main/java/net/kaaass/rumbase/parse/exception/SqlSyntaxException.java b/src/main/java/net/kaaass/rumbase/parse/exception/SqlSyntaxException.java index a8b24ee..cc87add 100644 --- a/src/main/java/net/kaaass/rumbase/parse/exception/SqlSyntaxException.java +++ b/src/main/java/net/kaaass/rumbase/parse/exception/SqlSyntaxException.java @@ -10,6 +10,7 @@ *

* E1001-1 SQL语句语法错误 * E1001-2 不支持的SQL语句 + * E1001-3 语句生成错误,请检查服务器日志 * * @author kaaass */ @@ -18,12 +19,17 @@ public class SqlSyntaxException extends RumbaseException { public static final Map REASONS = new HashMap<>() {{ put(1, "SQL语句语法错误"); put(2, "不支持的SQL语句"); + put(3, "语句生成错误,请检查服务器日志"); }}; public SqlSyntaxException(int subId) { super(5001, subId, REASONS.get(subId)); } + public SqlSyntaxException(int subId, String subReason) { + super(5001, subId, REASONS.get(subId) + ":" + subReason); + } + public SqlSyntaxException(int subId, Throwable e) { super(5001, subId, REASONS.get(subId), e); } diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/CommandStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/CommandStatementParser.java new file mode 100644 index 0000000..341be90 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/parser/CommandStatementParser.java @@ -0,0 +1,47 @@ +package net.kaaass.rumbase.parse.parser; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; + +import java.lang.reflect.InvocationTargetException; + +/** + * 字符串指令解析器 + * @author kaaass + */ +@Slf4j +@RequiredArgsConstructor +public class CommandStatementParser implements SqlParser.StatementParser { + + /** + * 语句解析结果的类 + */ + private final Class stmtClazz; + + /** + * 指令格式 + */ + private final String command; + + @Override + public ISqlStatement parse(String input) throws SqlSyntaxException { + try { + return stmtClazz.getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + log.error("无法生成指令对象 {}", input, e); + throw new SqlSyntaxException(3, e); + } + } + + @Override + public boolean checkStatement(String input) { + // 忽略逗号 + if (input.charAt(input.length() - 1) == ';') { + input = input.substring(0, input.length() - 1); + } + return command.compareToIgnoreCase(input) == 0; + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/command/ExecStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/command/ExecStatementParser.java new file mode 100644 index 0000000..0a373ef --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/parser/command/ExecStatementParser.java @@ -0,0 +1,22 @@ +package net.kaaass.rumbase.parse.parser.command; + +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.stmt.ExecStatement; + +/** + * 将执行SQL文件语句解释为对应语法树 + * + * @author kaaass + */ +public class ExecStatementParser implements SqlParser.StatementParser { + @Override + public ISqlStatement parse(String input) { + return new ExecStatement(input.substring(5).trim()); + } + + @Override + public boolean checkStatement(String input) { + return input.startsWith("exec ") || input.startsWith("EXEC "); + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/CreateIndexStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/CreateIndexStatementParser.java similarity index 91% rename from src/main/java/net/kaaass/rumbase/parse/parser/CreateIndexStatementParser.java rename to src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/CreateIndexStatementParser.java index fcb614c..1245ab4 100644 --- a/src/main/java/net/kaaass/rumbase/parse/parser/CreateIndexStatementParser.java +++ b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/CreateIndexStatementParser.java @@ -1,7 +1,8 @@ -package net.kaaass.rumbase.parse.parser; +package net.kaaass.rumbase.parse.parser.jsqlp; import net.kaaass.rumbase.parse.ColumnIdentifier; import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.parser.JsqlpStatementParser; import net.kaaass.rumbase.parse.stmt.CreateIndexStatement; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.create.index.CreateIndex; diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/CreateTableStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/CreateTableStatementParser.java similarity index 82% rename from src/main/java/net/kaaass/rumbase/parse/parser/CreateTableStatementParser.java rename to src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/CreateTableStatementParser.java index 9bfb26a..7b3cbcb 100644 --- a/src/main/java/net/kaaass/rumbase/parse/parser/CreateTableStatementParser.java +++ b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/CreateTableStatementParser.java @@ -1,6 +1,8 @@ -package net.kaaass.rumbase.parse.parser; +package net.kaaass.rumbase.parse.parser.jsqlp; import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.parser.JsqlpStatementParser; import net.kaaass.rumbase.parse.stmt.CreateTableStatement; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.create.table.ColDataType; @@ -16,11 +18,14 @@ */ public class CreateTableStatementParser implements JsqlpStatementParser { @Override - public ISqlStatement parse(Statement input) { + public ISqlStatement parse(Statement input) throws SqlSyntaxException { var stmt = (CreateTable) input; // 解析表名 var tableName = stmt.getTable().getName(); // 解析字段定义 + if (stmt.getColumnDefinitions() == null) { + throw new SqlSyntaxException(1, "表字段不能为空"); + } var columnDefs = stmt.getColumnDefinitions().stream() .map(def -> new CreateTableStatement.ColumnDefinition( mapColType(def.getColDataType()), diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/DeleteStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/DeleteStatementParser.java similarity index 89% rename from src/main/java/net/kaaass/rumbase/parse/parser/DeleteStatementParser.java rename to src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/DeleteStatementParser.java index c70d7d1..328579c 100644 --- a/src/main/java/net/kaaass/rumbase/parse/parser/DeleteStatementParser.java +++ b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/DeleteStatementParser.java @@ -1,7 +1,8 @@ -package net.kaaass.rumbase.parse.parser; +package net.kaaass.rumbase.parse.parser.jsqlp; import net.kaaass.rumbase.parse.ConditionExpression; import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.parser.JsqlpStatementParser; import net.kaaass.rumbase.parse.stmt.DeleteStatement; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.delete.Delete; diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/InsertStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/InsertStatementParser.java similarity index 90% rename from src/main/java/net/kaaass/rumbase/parse/parser/InsertStatementParser.java rename to src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/InsertStatementParser.java index a82ffea..65c12b4 100644 --- a/src/main/java/net/kaaass/rumbase/parse/parser/InsertStatementParser.java +++ b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/InsertStatementParser.java @@ -1,7 +1,8 @@ -package net.kaaass.rumbase.parse.parser; +package net.kaaass.rumbase.parse.parser.jsqlp; import net.kaaass.rumbase.parse.ColumnIdentifier; import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.parser.JsqlpStatementParser; import net.kaaass.rumbase.parse.stmt.InsertStatement; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.ItemsListVisitorAdapter; @@ -37,6 +38,7 @@ public ISqlStatement parse(Statement input) { public void visit(ExpressionList expressionList) { expressionList.getExpressions().stream() .map(Objects::toString) + .map(s -> "NULL".equals(s) ? null : s) .forEach(values::add); } }); diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/ParserUtil.java b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/ParserUtil.java similarity index 96% rename from src/main/java/net/kaaass/rumbase/parse/parser/ParserUtil.java rename to src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/ParserUtil.java index caa97ab..5ee4447 100644 --- a/src/main/java/net/kaaass/rumbase/parse/parser/ParserUtil.java +++ b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/ParserUtil.java @@ -1,4 +1,4 @@ -package net.kaaass.rumbase.parse.parser; +package net.kaaass.rumbase.parse.parser.jsqlp; import net.kaaass.rumbase.parse.ColumnIdentifier; import net.sf.jsqlparser.schema.Column; diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/SelectStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/SelectStatementParser.java similarity index 59% rename from src/main/java/net/kaaass/rumbase/parse/parser/SelectStatementParser.java rename to src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/SelectStatementParser.java index dafbdd5..e9e4569 100644 --- a/src/main/java/net/kaaass/rumbase/parse/parser/SelectStatementParser.java +++ b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/SelectStatementParser.java @@ -1,9 +1,11 @@ -package net.kaaass.rumbase.parse.parser; +package net.kaaass.rumbase.parse.parser.jsqlp; import lombok.extern.slf4j.Slf4j; import net.kaaass.rumbase.parse.ColumnIdentifier; import net.kaaass.rumbase.parse.ConditionExpression; import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.parser.JsqlpStatementParser; import net.kaaass.rumbase.parse.stmt.SelectStatement; import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; import net.sf.jsqlparser.schema.Column; @@ -23,7 +25,7 @@ @Slf4j public class SelectStatementParser implements JsqlpStatementParser { @Override - public ISqlStatement parse(Statement input) { + public ISqlStatement parse(Statement input) throws SqlSyntaxException { final PlainSelect[] selectStmtArr = {null}; ((Select) input).getSelectBody().accept(new SelectVisitorAdapter() { @Override @@ -35,6 +37,9 @@ public void visit(PlainSelect plainSelect) { // 解析distinct var distinct = stmt.getDistinct() != null; // 解析表名 + if (stmt.getFromItem() == null) { + throw new SqlSyntaxException(1, "选择的目标表不能为空"); + } var tableName = getTableFromItem(stmt.getFromItem()); // 解析列 List columns = new ArrayList<>(); @@ -58,28 +63,28 @@ public void visit(SelectExpressionItem item) { } })); // 解析join - var joins = stmt.getJoins().stream() - .map(join -> { - ConditionExpression joinOn = null; - if (join.getOnExpression() != null) { - joinOn = new ConditionExpression(join.getOnExpression(), tableName); - } - var table = getTableFromItem(join.getRightItem()); - var result = new SelectStatement.JoinTable(table, joinOn); - result.setOuter(join.isOuter()); - result.setRight(join.isRight()); - result.setLeft(join.isLeft()); - result.setNatural(join.isNatural()); - result.setFull(join.isFull()); - result.setInner(join.isInner()); - result.setSimple(join.isSimple()); - return result; - }) - .collect(Collectors.toList()); + List joins = null; + if (stmt.getJoins() != null) { + joins = stmt.getJoins().stream() + .map(join -> { + ConditionExpression joinOn = joinOn = new ConditionExpression(join.getOnExpression(), tableName); + var table = getTableFromItem(join.getRightItem()); + var result = new SelectStatement.JoinTable(table, joinOn); + result.setOuter(join.isOuter()); + result.setRight(join.isRight()); + result.setLeft(join.isLeft()); + result.setNatural(join.isNatural()); + result.setFull(join.isFull()); + result.setInner(join.isInner()); + result.setSimple(join.isSimple()); + return result; + }) + .collect(Collectors.toList()); + } // 解析where ConditionExpression where = null; if (stmt.getWhere() != null) { - where = new ConditionExpression(stmt.getWhere(), ""); + where = new ConditionExpression(stmt.getWhere(), tableName); } // 解析orderBy ColumnIdentifier[] columnIdentifiers = new ColumnIdentifier[1]; @@ -87,15 +92,18 @@ public void visit(SelectExpressionItem item) { @Override public void visit(Column column) { - columnIdentifiers[0] = new ColumnIdentifier(column.getTable().getName(), column.getColumnName()); + columnIdentifiers[0] = ParserUtil.mapColumn(column, tableName); } }; - var orderBys = stmt.getOrderByElements().stream() - .map(orderByElement -> { - orderByElement.getExpression().accept(orderVisitor); - return new SelectStatement.OrderBy(columnIdentifiers[0], orderByElement.isAsc()); - }) - .collect(Collectors.toList()); + List orderBys = null; + if (stmt.getOrderByElements() != null) { + orderBys = stmt.getOrderByElements().stream() + .map(orderByElement -> { + orderByElement.getExpression().accept(orderVisitor); + return new SelectStatement.OrderBy(columnIdentifiers[0], orderByElement.isAsc()); + }) + .collect(Collectors.toList()); + } // 拼接结果 return new SelectStatement(distinct, columns, tableName, joins, where, orderBys); } diff --git a/src/main/java/net/kaaass/rumbase/parse/parser/UpdateStatementParser.java b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/UpdateStatementParser.java similarity index 93% rename from src/main/java/net/kaaass/rumbase/parse/parser/UpdateStatementParser.java rename to src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/UpdateStatementParser.java index f3e2383..110bb7e 100644 --- a/src/main/java/net/kaaass/rumbase/parse/parser/UpdateStatementParser.java +++ b/src/main/java/net/kaaass/rumbase/parse/parser/jsqlp/UpdateStatementParser.java @@ -1,8 +1,9 @@ -package net.kaaass.rumbase.parse.parser; +package net.kaaass.rumbase.parse.parser.jsqlp; import net.kaaass.rumbase.parse.ColumnIdentifier; import net.kaaass.rumbase.parse.ConditionExpression; import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.parser.JsqlpStatementParser; import net.kaaass.rumbase.parse.stmt.UpdateStatement; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.update.Update; diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/CommitStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/CommitStatement.java new file mode 100644 index 0000000..2f001d4 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/CommitStatement.java @@ -0,0 +1,16 @@ +package net.kaaass.rumbase.parse.stmt; + +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; + +/** + * SQL语法树:提交事务语句 + * + * @author kaaass + */ +public class CommitStatement implements ISqlStatement { + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/CreateIndexStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/CreateIndexStatement.java index 3f5e8fb..92a1527 100644 --- a/src/main/java/net/kaaass/rumbase/parse/stmt/CreateIndexStatement.java +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/CreateIndexStatement.java @@ -4,6 +4,7 @@ import lombok.Data; import net.kaaass.rumbase.parse.ColumnIdentifier; import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; import java.util.List; @@ -30,4 +31,9 @@ public class CreateIndexStatement implements ISqlStatement { * 索引的目标列 */ private List columns; + + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } } diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/CreateTableStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/CreateTableStatement.java index 5a0dd50..6c1b75c 100644 --- a/src/main/java/net/kaaass/rumbase/parse/stmt/CreateTableStatement.java +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/CreateTableStatement.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; import java.util.List; @@ -25,6 +26,11 @@ public class CreateTableStatement implements ISqlStatement { */ private List columnDefinitions; + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } + /** * SQL语法树:列定义 */ diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/DeleteStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/DeleteStatement.java index 23590d4..3f92fd2 100644 --- a/src/main/java/net/kaaass/rumbase/parse/stmt/DeleteStatement.java +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/DeleteStatement.java @@ -4,6 +4,7 @@ import lombok.Data; import net.kaaass.rumbase.parse.ConditionExpression; import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; /** * SQL语法树:删除语句 @@ -23,4 +24,9 @@ public class DeleteStatement implements ISqlStatement { * 删除的筛选条件,为null则代表清空表 */ private ConditionExpression where; + + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } } diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/ExecStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/ExecStatement.java new file mode 100644 index 0000000..43ec55b --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/ExecStatement.java @@ -0,0 +1,23 @@ +package net.kaaass.rumbase.parse.stmt; + +import lombok.Data; +import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; + +/** + * SQL语法树:执行SQL文件 + * + * @author kaaass + */ +@Data +@RequiredArgsConstructor +public class ExecStatement implements ISqlStatement { + + private final String filepath; + + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/ExitStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/ExitStatement.java new file mode 100644 index 0000000..c9f399a --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/ExitStatement.java @@ -0,0 +1,16 @@ +package net.kaaass.rumbase.parse.stmt; + +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; + +/** + * SQL语法树:退出会话语句 + * + * @author kaaass + */ +public class ExitStatement implements ISqlStatement { + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/FlushStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/FlushStatement.java new file mode 100644 index 0000000..4506312 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/FlushStatement.java @@ -0,0 +1,16 @@ +package net.kaaass.rumbase.parse.stmt; + +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; + +/** + * SQL语法树:刷新缓冲区 + * + * @author kaaass + */ +public class FlushStatement implements ISqlStatement { + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/InsertStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/InsertStatement.java index c9c3f87..1efce70 100644 --- a/src/main/java/net/kaaass/rumbase/parse/stmt/InsertStatement.java +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/InsertStatement.java @@ -4,6 +4,7 @@ import lombok.Data; import net.kaaass.rumbase.parse.ColumnIdentifier; import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; import java.util.List; @@ -30,4 +31,9 @@ public class InsertStatement implements ISqlStatement { * 插入的数据,以字符串表示 */ private List values; + + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } } diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/RollbackStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/RollbackStatement.java new file mode 100644 index 0000000..fb6cf4d --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/RollbackStatement.java @@ -0,0 +1,16 @@ +package net.kaaass.rumbase.parse.stmt; + +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; + +/** + * SQL语法树:回滚事务语句 + * + * @author kaaass + */ +public class RollbackStatement implements ISqlStatement { + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/SelectStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/SelectStatement.java index cf21d2a..8625b9a 100644 --- a/src/main/java/net/kaaass/rumbase/parse/stmt/SelectStatement.java +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/SelectStatement.java @@ -6,6 +6,7 @@ import net.kaaass.rumbase.parse.ColumnIdentifier; import net.kaaass.rumbase.parse.ConditionExpression; import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; import java.util.List; @@ -50,6 +51,11 @@ public class SelectStatement implements ISqlStatement { */ private List orderBys; + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } + /** * SQL语法树:Join表 */ diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/ShutdownStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/ShutdownStatement.java new file mode 100644 index 0000000..1f3d052 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/ShutdownStatement.java @@ -0,0 +1,16 @@ +package net.kaaass.rumbase.parse.stmt; + +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; + +/** + * SQL语法树:关闭数据库语句 + * + * @author kaaass + */ +public class ShutdownStatement implements ISqlStatement { + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/StartTransactionStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/StartTransactionStatement.java new file mode 100644 index 0000000..6d90030 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/StartTransactionStatement.java @@ -0,0 +1,16 @@ +package net.kaaass.rumbase.parse.stmt; + +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; + +/** + * SQL语法树:开启事务语句 + * + * @author kaaass + */ +public class StartTransactionStatement implements ISqlStatement { + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/src/main/java/net/kaaass/rumbase/parse/stmt/UpdateStatement.java b/src/main/java/net/kaaass/rumbase/parse/stmt/UpdateStatement.java index 280aef5..97f00b2 100644 --- a/src/main/java/net/kaaass/rumbase/parse/stmt/UpdateStatement.java +++ b/src/main/java/net/kaaass/rumbase/parse/stmt/UpdateStatement.java @@ -5,6 +5,7 @@ import net.kaaass.rumbase.parse.ColumnIdentifier; import net.kaaass.rumbase.parse.ConditionExpression; import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; import java.util.List; @@ -36,4 +37,9 @@ public class UpdateStatement implements ISqlStatement { * 更新行的条件 */ private ConditionExpression where; + + @Override + public T accept(ISqlStatementVisitor visitor) { + return visitor.visit(this); + } } diff --git a/src/main/java/net/kaaass/rumbase/query/CreateIndexExecutor.java b/src/main/java/net/kaaass/rumbase/query/CreateIndexExecutor.java index 804e756..bd507ca 100644 --- a/src/main/java/net/kaaass/rumbase/query/CreateIndexExecutor.java +++ b/src/main/java/net/kaaass/rumbase/query/CreateIndexExecutor.java @@ -7,6 +7,7 @@ import net.kaaass.rumbase.table.TableManager; import net.kaaass.rumbase.table.exception.TableExistenceException; import net.kaaass.rumbase.table.field.BaseField; +import net.kaaass.rumbase.transaction.TransactionContext; /** * @@ -22,6 +23,9 @@ public class CreateIndexExecutor implements Executable{ @NonNull private final TableManager manager; + @NonNull + private final TransactionContext context; + @Override public void execute() throws TableExistenceException, IndexAlreadyExistException { var table = manager.getTable(statement.getTableName()); @@ -45,6 +49,7 @@ public void execute() throws TableExistenceException, IndexAlreadyExistException if (field != null) { field.createIndex(); + table.persist(context); } else { throw new TableExistenceException(2); } diff --git a/src/main/java/net/kaaass/rumbase/query/CreateTableExecutor.java b/src/main/java/net/kaaass/rumbase/query/CreateTableExecutor.java index f146a0c..3560e27 100644 --- a/src/main/java/net/kaaass/rumbase/query/CreateTableExecutor.java +++ b/src/main/java/net/kaaass/rumbase/query/CreateTableExecutor.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import net.kaaass.rumbase.parse.stmt.CreateTableStatement; import net.kaaass.rumbase.query.exception.ArgumentException; -import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; import net.kaaass.rumbase.table.TableManager; import net.kaaass.rumbase.table.exception.TableConflictException; import net.kaaass.rumbase.table.exception.TableExistenceException; @@ -30,10 +30,9 @@ public class CreateTableExecutor implements Executable { private final TransactionContext context; @Override - public void execute() throws TableExistenceException, TableConflictException, ArgumentException { + public void execute() throws TableExistenceException, TableConflictException, ArgumentException, RecordNotFoundException { var tableName = statement.getTableName(); var baseFields = new ArrayList(); - var dummyTable = new Table(tableName, baseFields); boolean nullable; for (var def : statement.getColumnDefinitions()) { nullable = !def.isNotNull(); @@ -43,13 +42,13 @@ public void execute() throws TableExistenceException, TableConflictException, Ar try { switch (fieldType) { case INT: - baseFields.add(new IntField(fieldName, nullable, dummyTable)); + baseFields.add(new IntField(fieldName, nullable, null)); break; case FLOAT: - baseFields.add(new FloatField(fieldName, nullable, dummyTable)); + baseFields.add(new FloatField(fieldName, nullable, null)); break; case VARCHAR: - baseFields.add(new VarcharField(fieldName, Integer.parseInt(def.getColumnType().getArguments().get(0)), nullable, dummyTable)); + baseFields.add(new VarcharField(fieldName, Integer.parseInt(def.getColumnType().getArguments().get(0)), nullable, null)); break; default: throw new TableConflictException(1); @@ -60,6 +59,6 @@ public void execute() throws TableExistenceException, TableConflictException, Ar } - manager.createTable(context, tableName, baseFields, tableName + ".db"); + manager.createTable(context, tableName, baseFields, "data/table/" + tableName + ".db"); } } diff --git a/src/main/java/net/kaaass/rumbase/query/InsertExecutor.java b/src/main/java/net/kaaass/rumbase/query/InsertExecutor.java index b1cf71f..c760397 100644 --- a/src/main/java/net/kaaass/rumbase/query/InsertExecutor.java +++ b/src/main/java/net/kaaass/rumbase/query/InsertExecutor.java @@ -2,6 +2,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; +import net.kaaass.rumbase.parse.ColumnIdentifier; import net.kaaass.rumbase.parse.stmt.InsertStatement; import net.kaaass.rumbase.query.exception.ArgumentException; import net.kaaass.rumbase.table.TableManager; @@ -34,23 +35,29 @@ public void execute() throws TableExistenceException, TableConflictException, Ar var table = manager.getTable(statement.getTableName()); var columns = statement.getColumns(); + if (columns == null || columns.isEmpty()) { + if (columns == null) { + columns = new ArrayList<>(); + } + var finalColumns = columns; + table.getFields().forEach(f -> finalColumns.add(new ColumnIdentifier(table.getTableName(), f.getName()))); + } var len = columns.size(); var insertArray = new ArrayList(); boolean ok; - for (BaseField f : table.getFields()) { + for (int j = 0; j < len; j++) { + var insertField = columns.get(j); ok = false; - for (int j = 0; j < len; j++) { - var insertField = columns.get(j); + for (BaseField f : table.getFields()) { if (f.getName().equals(insertField.getFieldName())) { insertArray.add(statement.getValues().get(j)); ok = true; - } } if (!ok) { - insertArray.add(""); + throw new TableConflictException(1); } } diff --git a/src/main/java/net/kaaass/rumbase/query/SelectExecutor.java b/src/main/java/net/kaaass/rumbase/query/SelectExecutor.java index b079cae..3a49f16 100644 --- a/src/main/java/net/kaaass/rumbase/query/SelectExecutor.java +++ b/src/main/java/net/kaaass/rumbase/query/SelectExecutor.java @@ -71,7 +71,7 @@ public void execute() throws TableConflictException, ArgumentException, TableExi // 投影 var selectCols = statement.getSelectColumns(); - if (selectCols != null) { + if (selectCols != null && !selectCols.isEmpty()) { var projectExe = new ProjectExecutor(resultTable, selectCols, resultData); projectExe.execute(); resultData = projectExe.getProjectedResult(); diff --git a/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java index 66c562d..abe0919 100644 --- a/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java +++ b/src/main/java/net/kaaass/rumbase/record/IRecordStorage.java @@ -64,4 +64,6 @@ default byte[] query(TransactionContext txContext, long recordId) throws RecordN * @param metadata 元信息数据 */ void setMetadata(TransactionContext txContext, byte[] metadata); + + String getIdentifiedName(); } diff --git a/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java index 52807a5..29a6a0a 100644 --- a/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java +++ b/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java @@ -1,5 +1,6 @@ package net.kaaass.rumbase.record; +import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; @@ -29,6 +30,7 @@ public class MvccRecordStorage implements IRecordStorage { private final IItemStorage storage; @NonNull + @Getter private final String identifiedName; /** diff --git a/src/main/java/net/kaaass/rumbase/record/RecordManager.java b/src/main/java/net/kaaass/rumbase/record/RecordManager.java index b6ff7b4..e7a26bb 100644 --- a/src/main/java/net/kaaass/rumbase/record/RecordManager.java +++ b/src/main/java/net/kaaass/rumbase/record/RecordManager.java @@ -3,6 +3,9 @@ import lombok.SneakyThrows; import net.kaaass.rumbase.dataitem.ItemManager; +import java.io.File; +import java.nio.file.Paths; + /** * 记录管理类 * @@ -23,4 +26,9 @@ public static IRecordStorage fromFile(String filepath) { var identifier = "TBL_" + filepath; return new MvccRecordStorage(itemStorage, identifier); } + + @SneakyThrows + public static IRecordStorage fromFile(String directory, String filename) { + return fromFile(Paths.get(directory, filename).toString()); + } } diff --git a/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java index 3e11d23..031fb21 100644 --- a/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java +++ b/src/main/java/net/kaaass/rumbase/record/mock/MockRecordStorage.java @@ -73,6 +73,11 @@ public void setMetadata(TransactionContext txContext, byte[] metadata) { this.metadata = metadata; } + @Override + public String getIdentifiedName() { + return this.toString(); + } + public static MockRecordStorage ofFile(String filepath) { var mockId = "file" + filepath; if (MOCK_STORAGES.containsKey(mockId)) { diff --git a/src/main/java/net/kaaass/rumbase/server/Server.java b/src/main/java/net/kaaass/rumbase/server/Server.java new file mode 100644 index 0000000..d3783d9 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/server/Server.java @@ -0,0 +1,139 @@ +package net.kaaass.rumbase.server; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.page.PageManager; +import net.kaaass.rumbase.page.exception.FileException; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.transaction.TransactionManager; +import net.kaaass.rumbase.transaction.TransactionManagerImpl; + +import java.io.File; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.SocketTimeoutException; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 管理服务器运行过程中总体的运行状态,使用单例模式 + * + * @author kaaass + */ +@Slf4j +public class Server { + + @Getter + private TransactionManager transactionManager = null; + + private ExecutorService threadPool = null; + + @Getter + @Setter + private boolean acceptNewConnection = true; + + @Getter + private TableManager tableManager = null; + + private final AtomicLong sessionCounter = new AtomicLong(0); + + @Getter + private final ConcurrentSkipListSet activeSession = new ConcurrentSkipListSet<>(); + + /** + * 进行服务器开始前的准备工作 + */ + public void prepare() { + // 准备文件夹 + var tableFolder = new File("data/table/a"); + assert tableFolder.exists() || tableFolder.mkdirs(); + var indexFolder = new File("data/index/a"); + assert indexFolder.exists() || indexFolder.mkdirs(); + // 初始化事务管理器 + log.info("初始化事务管理器..."); + try { + transactionManager = new TransactionManagerImpl(); + } catch (IOException | FileException e) { + log.error("初始化事务管理器失败", e); + System.exit(1); + } + // 初始化表管理器 + log.info("初始化表管理器..."); + // TODO 先恢复metadata + try { + tableManager = new TableManager(); + } catch (TableExistenceException | TableConflictException | RecordNotFoundException | ArgumentException | IndexAlreadyExistException e) { + log.error("初始化表管理器失败", e); + System.exit(1); + } + // 初始化线程池 + log.info("初始化线程池..."); + var namedThreadFactory = Executors.defaultThreadFactory(); + threadPool = new ThreadPoolExecutor(5, 200, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); + } + + /** + * 运行服务器,监听客户端消息 + */ + public void run() { + // 监听端口 + try (ServerSocket server = new ServerSocket(8889)) { + log.info("开始监听 {}", server.getLocalSocketAddress()); + + server.setSoTimeout(1000); + while (acceptNewConnection) { + try { + var connection = server.accept(); + long sessionId = sessionCounter.incrementAndGet(); + var session = new Session(sessionId, connection, this); + threadPool.submit(session); + log.info("创建会话 {}", sessionId); + } catch (SocketTimeoutException ignore) { + } + } + } catch (IOException e) { + log.error("服务器启动失败", e); + } + } + + /** + * 服务器关闭 + */ + public void shutdown() { + // 关闭线程池 + log.info("正在关闭线程池..."); + if (threadPool != null) { + threadPool.shutdown(); + } + // 关闭所有活动会话 + log.info("正在关闭所有活动会话..."); + for (var session : activeSession) { + session.say("服务器正在关闭...\n"); + session.onClose(); + } + activeSession.clear(); + // 释放文件、写回文件 + log.info("正在写回文件..."); + PageManager.flush(); + } + + private static final Server INSTANCE = new Server(); + + private Server() { + } + + /** + * 获得Server单例 + */ + public static Server getInstance() { + return INSTANCE; + } +} diff --git a/src/main/java/net/kaaass/rumbase/server/Session.java b/src/main/java/net/kaaass/rumbase/server/Session.java new file mode 100644 index 0000000..bcb033d --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/server/Session.java @@ -0,0 +1,477 @@ +package net.kaaass.rumbase.server; + +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.exception.RumbaseException; +import net.kaaass.rumbase.exception.RumbaseRuntimeException; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.page.PageManager; +import net.kaaass.rumbase.parse.ISqlStatement; +import net.kaaass.rumbase.parse.ISqlStatementVisitor; +import net.kaaass.rumbase.parse.SqlParser; +import net.kaaass.rumbase.parse.exception.SqlSyntaxException; +import net.kaaass.rumbase.parse.stmt.*; +import net.kaaass.rumbase.query.*; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.exception.TableConflictException; +import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.transaction.TransactionContext; +import net.kaaass.rumbase.transaction.TransactionIsolation; + +import java.io.*; +import java.net.Socket; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Scanner; +import java.util.stream.Collectors; + +/** + * 存储、管理会话中的数据库状态 + * + * @author kaaass + */ +@Slf4j +@RequiredArgsConstructor +@EqualsAndHashCode(of = "sessionId") +public class Session implements Runnable, Comparable, ISqlStatementVisitor { + + private TransactionContext currentContext = null; + + private final long sessionId; + + private final Socket connection; + + private final Server server; + + private Scanner scanner = null; + + private Writer writer = null; + + private boolean autoCommit = false; + + /** + * 执行SQL语句并输出相关结果 + * @return 是否退出 + */ + public boolean executeSql(String sql) { + // 解析SQL语句 + ISqlStatement stmt; + try { + stmt = SqlParser.parseStatement(sql); + } catch (SqlSyntaxException e) { + log.debug("会话 {} 解析SQL语句失败,输入: {}", sessionId, sql, e); + say(e); + return false; + } catch (Exception e) { + log.warn("会话 {} 解析SQL语句出现异常错误,输入:{}", sessionId, sql, e); + say("解析异常,请检查服务器日志\n"); + return false; + } + log.debug("会话 {} 解析SQL语句: {}", sessionId, stmt); + // 执行SQL + try { + return stmt.accept(this); + // TODO 自动回滚事务、SQL日志 + } catch (RumbaseRuntimeException e) { + log.info("会话 {} 发生错误", sessionId, e); + say(e); + return false; + } catch (Exception e) { + log.warn("会话 {} 运行SQL语句出现未知异常,输入:{}", sessionId, sql, e); + say("发生未知异常,请检查服务器日志\n"); + return false; + } + } + + @Override + public void run() { + // 加载 + onInit(); + // 加载成功,加入活跃会话 + server.getActiveSession().add(this); + // 进入REPL模式 + var exit = false; + while (!exit && !connection.isClosed()) { + // 打印命令提示符 + say("\n> "); + try { + writer.flush(); + } catch (IOException ignore) { + } + try { + // 等待输入 + String input = scanner.nextLine(); + if (input.isBlank()) { + continue; + } + // 执行 + exit = executeSql(input); + } catch (NoSuchElementException e) { + log.info("会话 {} 被强制关闭", sessionId, e); + break; + } catch (Exception e) { + log.info("会话 {} 命令执行错误", sessionId, e); + say("未知错误,请检查服务端日志\n"); + } + } + // 退出 + onClose(); + } + + /** + * 当会话开始加载 + */ + public void onInit() { + // 准备读写 + try { + scanner = new Scanner(new BufferedInputStream(connection.getInputStream())); + writer = new OutputStreamWriter(new BufferedOutputStream(connection.getOutputStream())); + } catch (IOException e) { + log.info("无法处理IO,会话建立失败", e); + } + // 打印欢迎信息 + say("Welcome to Rumbase DMBS\n\n"); + } + + /** + * 当会话被关闭 + */ + public void onClose() { + log.info("正在关闭会话 {} ...", sessionId); + // 提交活动事务 + if (currentContext != null) { + say("正在提交当前事务...\n"); + try { + currentContext.commit(); + } catch (RumbaseRuntimeException e) { + log.warn("退出会话 {} 时提交事务失败", sessionId, e); + say(e); + } + } + // 删除活跃会话 + server.getActiveSession().remove(this); + // 退出成功 + say("Bye\n"); + try { + writer.close(); + scanner.close(); + } catch (IOException ignore) { + } + } + + public void say(CharSequence chars) { + try { + writer.append(chars); + } catch (IOException e) { + log.info("信息发送失败:{},会话:{}", chars, sessionId, e); + } + } + + private void say(RumbaseException e) { + say(e.getMessage() + "\n"); + } + + private void say(RumbaseRuntimeException e) { + say(e.getMessage() + "\n"); + } + + @Override + public int compareTo(Session o) { + return Long.compare(sessionId, o.sessionId); + } + + @Override + public Boolean visit(SelectStatement statement) { + checkAutoCommitBefore(); + // 执行语句 + try { + var executor = new SelectExecutor(statement, + server.getTableManager(), + currentContext); + executor.execute(); + saySelectResult(executor, statement.getFromTable()); + } catch (TableExistenceException | IndexAlreadyExistException | ArgumentException | TableConflictException | RecordNotFoundException e) { + log.debug("会话 {} 执行语句异常", sessionId, e); + say(e); + } catch (Exception e) { + // 发生任何错误都回滚 + checkAutoCommitAfter(true); + throw e; + } + checkAutoCommitAfter(false); + return false; + } + + /** + * 以表格格式输出选择语句的结果 + * @param executor 结果集 + */ + private void saySelectResult(SelectExecutor executor, String defaultTable) { + var columns = + executor.getResultTable().stream() + .map(column -> column.getTableName().equals(defaultTable) ? + column.getFieldName() : + column.getTableName() + "." + column.getFieldName()) + .collect(Collectors.toList()); + var rows = executor.getResultData(); + log.debug("查询结果 {}, {}", columns, rows); + // 格式化为表格 + int[] maxLengths = new int[columns.size()]; + for (int i = 0; i < columns.size(); i++) { + maxLengths[i] = columns.get(i).length(); + } + for (var row : rows) { + for (int i = 0; i < row.size(); i++) { + row.set(i, row.get(i) == null ? "NULL" : row.get(i).toString()); + maxLengths[i] = Math.max(maxLengths[i], ((String) row.get(i)).length()); + } + } + // 生成行格式 + StringBuilder formatBuilder = new StringBuilder("|"); + for (int maxLength : maxLengths) { + formatBuilder.append("%-").append(maxLength + 2).append("s |"); + } + String format = formatBuilder.toString(); + // 输出行首 + var result = new StringBuilder(); + result.append(String.format(format, columns.toArray(new Object[0]))).append("\n"); + for (int i = 0; i < columns.size(); i++) { + result.append(i == 0 ? '|' : '+'); + result.append("-".repeat(Math.max(0, maxLengths[i] + 3))); + } + result.append("|\n"); + // 输出内容 + for (var row : rows) { + result.append(String.format(format, row.toArray(new Object[0]))).append("\n"); + } + say(result); + say("共 " + rows.size() + " 条记录\n"); + } + + @Override + public Boolean visit(InsertStatement statement) { + checkAutoCommitBefore(); + // 执行语句 + try { + var executor = new InsertExecutor(statement, + server.getTableManager(), + currentContext); + executor.execute(); + say("语句执行成功\n"); + } catch (TableExistenceException | ArgumentException | TableConflictException e) { + log.debug("会话 {} 执行语句异常", sessionId, e); + say(e); + } catch (Exception e) { + // 发生任何错误都回滚 + checkAutoCommitAfter(true); + throw e; + } + checkAutoCommitAfter(false); + return false; + } + + @Override + public Boolean visit(UpdateStatement statement) { + checkAutoCommitBefore(); + // 执行语句 + try { + var executor = new UpdateExecutor(statement, + server.getTableManager(), + currentContext); + executor.execute(); + say("语句执行成功\n"); + } catch (TableExistenceException | IndexAlreadyExistException | ArgumentException | TableConflictException | RecordNotFoundException e) { + log.debug("会话 {} 执行语句异常", sessionId, e); + say(e); + } catch (Exception e) { + // 发生任何错误都回滚 + checkAutoCommitAfter(true); + throw e; + } + checkAutoCommitAfter(false); + return false; + } + + @Override + public Boolean visit(DeleteStatement statement) { + checkAutoCommitBefore(); + // 执行语句 + try { + var executor = new DeleteExecutor(statement, + server.getTableManager(), + currentContext); + executor.execute(); + say("语句执行成功\n"); + } catch (TableExistenceException | IndexAlreadyExistException | ArgumentException | TableConflictException | RecordNotFoundException e) { + log.debug("会话 {} 执行语句异常", sessionId, e); + say(e); + } catch (Exception e) { + // 发生任何错误都回滚 + checkAutoCommitAfter(true); + throw e; + } + checkAutoCommitAfter(false); + return false; + } + + @Override + public Boolean visit(CreateIndexStatement statement) { + checkAutoCommitBefore(); + // 执行语句 + try { + var executor = new CreateIndexExecutor(statement, + server.getTableManager(), currentContext); + executor.execute(); + say("成功创建索引\n"); + } catch (TableExistenceException | IndexAlreadyExistException e) { + log.debug("会话 {} 执行语句异常", sessionId, e); + say(e); + } catch (Exception e) { + // 发生任何错误都回滚 + checkAutoCommitAfter(true); + throw e; + } + checkAutoCommitAfter(false); + return false; + } + + @Override + public Boolean visit(CreateTableStatement statement) { + checkAutoCommitBefore(); + // 执行语句 + try { + var executor = new CreateTableExecutor(statement, + server.getTableManager(), + currentContext); + executor.execute(); + say("成功创建表\n"); + } catch (ArgumentException | TableConflictException | TableExistenceException | RecordNotFoundException e) { + log.debug("会话 {} 执行语句异常", sessionId, e); + say(e); + } catch (Exception e) { + // 发生任何错误都回滚 + checkAutoCommitAfter(true); + throw e; + } + checkAutoCommitAfter(false); + return false; + } + + /** + * 执行前尝试自动提交 + */ + private void checkAutoCommitBefore() { + assert !autoCommit; + if (currentContext == null) { + // 自动创建事务 + // TODO 默认使用可重复读隔离度,应该从配置读取 + currentContext = server.getTransactionManager().createTransactionContext(TransactionIsolation.REPEATABLE_READ); + // 设置自动提交 + autoCommit = true; + } + } + + /** + * 执行后尝试自动提交 + * @param rollback 是否需要回滚 + */ + private void checkAutoCommitAfter(boolean rollback) { + if (autoCommit) { + try { + assert currentContext != null; + if (rollback) { + currentContext.rollback(); + } else { + currentContext.commit(); + } + currentContext = null; + } finally { + // 完成自动提交 + autoCommit = false; + } + } + } + + @Override + public Boolean visit(StartTransactionStatement statement) { + if (currentContext != null) { + say("请先提交当前事务!\n"); + return false; + } + // 创建事务 + // TODO 默认使用可重复读隔离度,应该从配置读取 + currentContext = server.getTransactionManager().createTransactionContext(TransactionIsolation.REPEATABLE_READ); + say("成功创建事务" + currentContext.getXid() + "\n"); + return false; + } + + @Override + public Boolean visit(CommitStatement statement) { + if (currentContext == null) { + say("当前会话内无事务\n"); + return false; + } + // 提交事务 + currentContext.commit(); + say("成功提交事务" + currentContext.getXid() + "\n"); + currentContext = null; + return false; + } + + @Override + public Boolean visit(RollbackStatement statement) { + if (currentContext == null) { + say("当前会话内无事务\n"); + return false; + } + // 回滚事务 + currentContext.rollback(); + say("成功回滚事务" + currentContext.getXid() + "\n"); + currentContext = null; + return false; + } + + @Override + public Boolean visit(ExitStatement statement) { + say("正在关闭会话...\n"); + return true; + } + + @Override + public Boolean visit(ShutdownStatement statement) { + say("正在关闭服务器...\n"); + System.exit(0); + return true; + } + + @Override + public Boolean visit(FlushStatement statement) { + PageManager.flush(); + say("已刷新缓冲\n"); + return false; + } + + @Override + public Boolean visit(ExecStatement statement) { + // 读入文件 + List lines; + try { + lines = Files.readAllLines(Paths.get(statement.getFilepath())); + } catch (IOException e) { + say("文件读取失败,请检查文件是否存在\n"); + return false; + } + // 逐行解析 + for (var line : lines) { + if (line.isBlank() || line.startsWith("#")) { + continue; + } + this.executeSql(line); + } + return false; + } +} diff --git a/src/main/java/net/kaaass/rumbase/table/Table.java b/src/main/java/net/kaaass/rumbase/table/Table.java index 2f3110c..c31dd18 100644 --- a/src/main/java/net/kaaass/rumbase/table/Table.java +++ b/src/main/java/net/kaaass/rumbase/table/Table.java @@ -5,6 +5,7 @@ import com.igormaznitsa.jbbp.io.JBBPByteOrder; import lombok.*; import net.kaaass.rumbase.index.Pair; +import net.kaaass.rumbase.index.exception.IndexNotFoundException; import net.kaaass.rumbase.query.exception.ArgumentException; import net.kaaass.rumbase.record.IRecordStorage; import net.kaaass.rumbase.record.RecordManager; @@ -16,6 +17,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.util.*; @@ -84,7 +86,11 @@ public class Table { * @param fields 表的字段结构 */ public Table(@NonNull String tableName, @NonNull List fields) { - this.recordStorage = RecordManager.fromFile(tableName); + var file = new File("data/table/"); + if (!file.exists() && !file.isDirectory()) { + file.mkdirs(); + } + this.recordStorage = RecordManager.fromFile("data/table", tableName + ".db"); this.tableName = tableName; this.fields = fields; this.next = -1; @@ -135,7 +141,6 @@ public void persist(TransactionContext context) { public static Table load(IRecordStorage recordStorage) { - // fixme context var context = TransactionContext.empty(); var meta = recordStorage.getMetadata(context); var stream = new ByteArrayInputStream(meta); @@ -146,20 +151,17 @@ public static Table load(IRecordStorage recordStorage) { var next = in.readLong(JBBPByteOrder.BIG_ENDIAN); var fieldNum = in.readInt(JBBPByteOrder.BIG_ENDIAN); var fieldList = new ArrayList(); - var table = new Table(name, fieldList); + var table = new Table(name, fieldList, recordStorage.getIdentifiedName().substring(4)); for (int i = 0; i < fieldNum; i++) { var f = BaseField.load(stream, table); - if (f != null) { - fieldList.add(f); - } + fieldList.add(f); } table.next = next; table.status = TableStatus.valueOf(status); return table; - } catch (IOException e) { - e.printStackTrace(); + } catch (IOException | IndexNotFoundException e) { + throw new RuntimeException(e); } - return null; } /** @@ -289,9 +291,9 @@ public Optional> read(TransactionContext context, long uuid) throws try { return Optional.of(parseEntry(bytes.get())); } catch (IOException e) { - // fixme 不该出现这样的事情(吧) // 查询到的entry和当前表冲突 - throw new TableConflictException(3); } + throw new TableConflictException(3); + } } else { return Optional.empty(); } diff --git a/src/main/java/net/kaaass/rumbase/table/TableManager.java b/src/main/java/net/kaaass/rumbase/table/TableManager.java index 8ecdc38..ecb925a 100644 --- a/src/main/java/net/kaaass/rumbase/table/TableManager.java +++ b/src/main/java/net/kaaass/rumbase/table/TableManager.java @@ -1,18 +1,18 @@ package net.kaaass.rumbase.table; -import com.igormaznitsa.jbbp.io.JBBPBitInputStream; -import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; -import com.igormaznitsa.jbbp.io.JBBPByteOrder; import lombok.Getter; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.query.exception.ArgumentException; import net.kaaass.rumbase.record.IRecordStorage; import net.kaaass.rumbase.record.RecordManager; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.exception.TableConflictException; import net.kaaass.rumbase.table.field.BaseField; import net.kaaass.rumbase.table.exception.TableExistenceException; +import net.kaaass.rumbase.table.field.VarcharField; import net.kaaass.rumbase.transaction.TransactionContext; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; +import java.io.File; import java.util.*; /** @@ -28,7 +28,14 @@ public class TableManager { - private final IRecordStorage metaRecord = RecordManager.fromFile("metadata.db"); + static { + var dataDir = new File("data/"); + if (!dataDir.exists() && !dataDir.isDirectory()) { + dataDir.mkdirs(); + } + } + + private final IRecordStorage metaRecord = RecordManager.fromFile("data/metadata.db"); /** * 对所有的表结构的缓存 @@ -61,40 +68,63 @@ public void abort(TransactionContext context) { context.rollback(); } - public TableManager() { + public TableManager() throws TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException, IndexAlreadyExistException { load(); } - public void load() { + public void load() throws TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException, IndexAlreadyExistException { var context = TransactionContext.empty(); - var meta = metaRecord.getMetadata(context); + Table metaTable; + try { + metaTable = Table.load(metaRecord); + tableCache.put("metadata", metaTable); + } catch (RuntimeException e) { + // 新建表 + var fields = new ArrayList(); + var keyField = new VarcharField("key", 255, false, null); + var valueField = new VarcharField("value", 255, false, null); + fields.add(keyField); + fields.add(valueField); + + var dataDir = new File("data/"); + if (!dataDir.exists() && !dataDir.isDirectory()) { + dataDir.mkdirs(); + } - var byteInStream = new ByteArrayInputStream(meta); - var stream = new JBBPBitInputStream(byteInStream); + metaTable = new Table("metadata", fields, "data/metadata.db"); - int num; - try { - num = stream.readInt(JBBPByteOrder.BIG_ENDIAN); - } catch (IOException e) { - return; + for (var f: fields) { + f.setParentTable(metaTable); + } + + keyField.createIndex("data/"); + + metaTable.persist(context); + tableCache.put("metadata", metaTable); } - for (int i = 0; i < num; i++) { - try { - var key = stream.readString(JBBPByteOrder.BIG_ENDIAN); - var val = stream.readString(JBBPByteOrder.BIG_ENDIAN); - // 加载表 - if (key.startsWith("tablePath$")) { - var tableName = key.split("\\$")[1]; - var record = RecordManager.fromFile(val); - recordPaths.add(val); - var table = Table.load(record); - tableCache.put(tableName, table); - } - } catch (IOException e) { - e.printStackTrace(); + var data = metaTable.readAll(context); + var map = new HashMap(); + data.forEach(row -> map.put((String) row.get(0), (String) row.get(1))); + + if (!map.containsKey("table_num")) { + metaTable.insert(context, new ArrayList<>(){{ + add("'table_num'"); + add("'0'"); + }}); + map.put("table_num", "0"); + } + + for (var item: map.entrySet()) { + if (item.getKey().startsWith("tablePath$")) { + var tableName = item.getKey().split("\\$")[1]; + var record = RecordManager.fromFile(item.getValue()); + recordPaths.add(item.getValue()); + var table = Table.load(record); + tableCache.put(tableName, table); } } + } /** @@ -123,11 +153,16 @@ public void createTable( String tableName, List baseFields, String path - ) throws TableExistenceException { + ) throws TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { if (tableCache.containsKey(tableName)) { throw new TableExistenceException(1); } + var tableDir = new File("data/table/"); + if (!tableDir.exists() && !tableDir.isDirectory()) { + tableDir.mkdirs(); + } + var table = new Table(tableName, baseFields, path); for (var f: baseFields) { @@ -136,30 +171,37 @@ public void createTable( table.persist(context); - var meta = metaRecord.getMetadata(TransactionContext.empty()); + var metaTable = tableCache.get("metadata"); - var in = new ByteArrayInputStream(meta); - var inStream = new JBBPBitInputStream(in); - - int cnt; - try { - cnt = inStream.readInt(JBBPByteOrder.BIG_ENDIAN); + var uuids = metaTable.search("key", "table_num"); - } catch (IOException e) { - cnt = 0; + int cnt = -1; + long cntUuid = -1; + for (var uuid: uuids) { + var res = metaTable.read(TransactionContext.empty(), uuid); + if (res.isPresent()) { + cnt = Integer.parseInt((String) res.get().get(1)); + cntUuid = uuid; + break; + } } - var byteOutStream = new ByteArrayOutputStream(); - var stream = new JBBPBitOutputStream(byteOutStream); - try { - stream.writeInt(cnt + 1, JBBPByteOrder.BIG_ENDIAN); - stream.write(in.readAllBytes()); - stream.writeString("tablePath$" + tableName, JBBPByteOrder.BIG_ENDIAN); - stream.writeString(path, JBBPByteOrder.BIG_ENDIAN); - } catch (IOException e) { - e.printStackTrace(); + if (cnt == -1 || cntUuid == -1 ) { + throw new RuntimeException(); } + cnt = cnt + 1; + var newCntEntry = new ArrayList(); + newCntEntry.add("'table_num'"); + newCntEntry.add("'" + Integer.toString(cnt) + "'"); + metaTable.update(TransactionContext.empty(), cntUuid, newCntEntry); + + var newTableData = new ArrayList(); + newTableData.add("'tablePath$" + tableName + "'"); + newTableData.add("'" + path + "'"); + + metaTable.insert(TransactionContext.empty(), newTableData); + tableCache.put(tableName, table); } diff --git a/src/main/java/net/kaaass/rumbase/table/field/BaseField.java b/src/main/java/net/kaaass/rumbase/table/field/BaseField.java index 40dc924..a0264ed 100644 --- a/src/main/java/net/kaaass/rumbase/table/field/BaseField.java +++ b/src/main/java/net/kaaass/rumbase/table/field/BaseField.java @@ -6,10 +6,12 @@ import net.kaaass.rumbase.index.Index; import net.kaaass.rumbase.index.Pair; import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.index.exception.IndexNotFoundException; import net.kaaass.rumbase.table.Table; import net.kaaass.rumbase.table.exception.TableConflictException; import net.kaaass.rumbase.table.exception.TableExistenceException; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -66,11 +68,17 @@ public abstract class BaseField{ /** * 当前列所属的表 */ - @NonNull @Getter @Setter private Table parentTable; + public BaseField(@NonNull String name, @NonNull FieldType type, boolean nullable, Table parentTable) { + this.name = name; + this.type = type; + this.nullable = nullable; + this.parentTable = parentTable; + } + /** * 向输出流中写入当前字段格式信息 *

@@ -99,7 +107,7 @@ public abstract class BaseField{ * @param stream 输入流 * @return 字段 */ - public static BaseField load(InputStream stream, Table table) { + public static BaseField load(InputStream stream, Table table) throws IndexNotFoundException { var in = new JBBPBitInputStream(stream); try { @@ -112,28 +120,34 @@ public static BaseField load(InputStream stream, Table table) { if (indexed) { indexName = in.readString(JBBPByteOrder.BIG_ENDIAN); } - // todo 重建索引 BaseField field; switch (type) { case INT: field = new IntField(name, nullable, table); field.setIndexName(indexName); + if (indexed) { + field.index = Index.getIndex(indexName); + } return field; case FLOAT: field = new FloatField(name, nullable, table); field.setIndexName(indexName); + if (indexed) { + field.index = Index.getIndex(indexName); + } return field; default: field = new VarcharField(name, in.readInt(JBBPByteOrder.BIG_ENDIAN), nullable, table); field.setIndexName(indexName); + if (indexed) { + field.index = Index.getIndex(indexName); + } return field; } } catch (IOException e) { - // todo - e.printStackTrace(); + throw new RuntimeException(e); } - return null; } /** @@ -209,11 +223,38 @@ public static BaseField load(InputStream stream, Table table) { */ public void createIndex() throws IndexAlreadyExistException { var delimiter = "$"; - var indexName = parentTable.getPath() + delimiter + name; + + var indexDir = new File("data/index/"); + if (!indexDir.exists() && !indexDir.isDirectory()) { + indexDir.mkdirs(); + } + + indexName = "data/index/" + parentTable.getTableName() + delimiter + name; + + index = Index.createEmptyIndex(indexName); + } + + + /** + * 创建索引 + * + * @throws IndexAlreadyExistException 索引已存在 + */ + public void createIndex(String path) throws IndexAlreadyExistException { + var delimiter = "$"; + + var indexDir = new File(path); + if (!indexDir.exists() && !indexDir.isDirectory()) { + indexDir.mkdirs(); + } + + indexName = path + parentTable.getTableName() + delimiter + name; index = Index.createEmptyIndex(indexName); } + + /** * 向索引插入一个键值对 * @param value 值对象 diff --git a/src/main/java/net/kaaass/rumbase/table/field/FloatField.java b/src/main/java/net/kaaass/rumbase/table/field/FloatField.java index d8539d2..a8c09b5 100644 --- a/src/main/java/net/kaaass/rumbase/table/field/FloatField.java +++ b/src/main/java/net/kaaass/rumbase/table/field/FloatField.java @@ -24,7 +24,7 @@ */ public class FloatField extends BaseField { - public FloatField(@NonNull String name, boolean nullable, @NonNull Table parentTable) { + public FloatField(@NonNull String name, boolean nullable, Table parentTable) { super(name, FieldType.FLOAT, nullable, parentTable); } @@ -37,7 +37,7 @@ public void persist(OutputStream stream) { out.writeString(getName(), JBBPByteOrder.BIG_ENDIAN); out.writeString(getType().toString().toUpperCase(Locale.ROOT), JBBPByteOrder.BIG_ENDIAN); var flags = new byte[]{0}; - flags[0] |= indexed() ? 1 : 0; + flags[0] |= isNullable() ? 1 : 0; if (indexed()) { flags[0] |= 2; out.writeBytes(flags, 1, JBBPByteOrder.BIG_ENDIAN); @@ -45,7 +45,6 @@ public void persist(OutputStream stream) { } else { out.writeBytes(flags, 1, JBBPByteOrder.BIG_ENDIAN); } - // todo (字段约束) } catch (IOException e) { e.printStackTrace(); } @@ -185,8 +184,7 @@ public void serialize(OutputStream outputStream, String strVal) throws TableConf } } catch (IOException e) { - // fixme 这个给外面可能也不知道如何处理 - throw new RuntimeException(); + throw new RuntimeException(e); } catch (NumberFormatException e) { throw new TableConflictException(1); } @@ -210,8 +208,7 @@ public void serialize(OutputStream outputStream, Object objVal) throws TableConf } } catch (IOException e) { - // fixme 这个给外面可能也不知道如何处理 - throw new RuntimeException(); + throw new RuntimeException(e); } catch (NumberFormatException e) { throw new TableConflictException(1); } diff --git a/src/main/java/net/kaaass/rumbase/table/field/IntField.java b/src/main/java/net/kaaass/rumbase/table/field/IntField.java index 110a879..da204e6 100644 --- a/src/main/java/net/kaaass/rumbase/table/field/IntField.java +++ b/src/main/java/net/kaaass/rumbase/table/field/IntField.java @@ -23,7 +23,7 @@ */ public class IntField extends BaseField { - public IntField(@NonNull String name, boolean nullable, @NonNull Table parentTable) { + public IntField(@NonNull String name, boolean nullable, Table parentTable) { super(name, FieldType.INT, nullable, parentTable); } @@ -35,7 +35,7 @@ public void persist(OutputStream stream) { out.writeString(getName(), JBBPByteOrder.BIG_ENDIAN); out.writeString(getType().toString().toUpperCase(Locale.ROOT), JBBPByteOrder.BIG_ENDIAN); var flags = new byte[]{0}; - flags[0] |= indexed() ? 1 : 0; + flags[0] |= isNullable() ? 1 : 0; if (indexed()) { flags[0] |= 2; out.writeBytes(flags, 1, JBBPByteOrder.BIG_ENDIAN); @@ -43,7 +43,6 @@ public void persist(OutputStream stream) { } else { out.writeBytes(flags, 1, JBBPByteOrder.BIG_ENDIAN); } - // todo (字段约束) } catch (IOException e) { e.printStackTrace(); } @@ -183,8 +182,7 @@ public void serialize(OutputStream outputStream, String strVal) throws TableConf } catch (IOException e) { - // fixme 这个给外面可能也不知道如何处理 - throw new RuntimeException(); + throw new RuntimeException(e); } catch (NumberFormatException e) { throw new TableConflictException(1); } @@ -208,8 +206,7 @@ public void serialize(OutputStream outputStream, Object objVal) throws TableConf } } catch (IOException e) { - // fixme 这个给外面可能也不知道如何处理 - throw new RuntimeException(); + throw new RuntimeException(e); } catch (NumberFormatException e) { throw new TableConflictException(1); } diff --git a/src/main/java/net/kaaass/rumbase/table/field/VarcharField.java b/src/main/java/net/kaaass/rumbase/table/field/VarcharField.java index 93e96e9..24d57c4 100644 --- a/src/main/java/net/kaaass/rumbase/table/field/VarcharField.java +++ b/src/main/java/net/kaaass/rumbase/table/field/VarcharField.java @@ -33,7 +33,7 @@ public class VarcharField extends BaseField { private static final String DELIMIT = "'"; - public VarcharField(@NonNull String name, int limit, boolean nullable, @NonNull Table parentTable) { + public VarcharField(@NonNull String name, int limit, boolean nullable, Table parentTable) { super(name, FieldType.VARCHAR, nullable, parentTable); this.limit = limit; } @@ -46,7 +46,7 @@ public void persist(OutputStream stream) { out.writeString(getName(), JBBPByteOrder.BIG_ENDIAN); out.writeString(getType().toString().toUpperCase(Locale.ROOT), JBBPByteOrder.BIG_ENDIAN); var flags = new byte[]{0}; - flags[0] |= indexed() ? 1 : 0; + flags[0] |= isNullable() ? 1 : 0; if (indexed()) { flags[0] |= 2; out.writeBytes(flags, 1, JBBPByteOrder.BIG_ENDIAN); @@ -55,7 +55,6 @@ public void persist(OutputStream stream) { out.writeBytes(flags, 1, JBBPByteOrder.BIG_ENDIAN); } out.writeInt(limit, JBBPByteOrder.BIG_ENDIAN); - // todo (字段约束) } catch (IOException e) { e.printStackTrace(); } @@ -77,6 +76,11 @@ public long strToHash(String str) { return 0; } + if (str.startsWith(DELIMIT) && str.endsWith(DELIMIT)) { + var substr = str.substring(1, str.length() - 1); + return substr.hashCode(); + } + return str.hashCode(); } @@ -164,7 +168,6 @@ public void serialize(OutputStream outputStream, String strVal) throws TableConf } } catch (IOException e) { - // fixme 这个给外面可能也不知道如何处理 throw new RuntimeException(e); } } @@ -187,7 +190,6 @@ public void serialize(OutputStream outputStream, Object val) throws TableConflic } } catch (IOException e) { - // fixme 这个给外面可能也不知道如何处理 throw new RuntimeException(e); } catch (ClassCastException e){ throw new TableConflictException(1); diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionManagerImpl.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionManagerImpl.java index 2b6f1b5..e2d7a6e 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/TransactionManagerImpl.java +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionManagerImpl.java @@ -61,7 +61,7 @@ public class TransactionManagerImpl implements TransactionManager { * 事务管理器 */ public TransactionManagerImpl() throws IOException, FileException { - this("xid.log"); + this("data/xid.log"); } /** diff --git a/src/test/java/net/kaaass/rumbase/FileUtil.java b/src/test/java/net/kaaass/rumbase/FileUtil.java new file mode 100644 index 0000000..16a2be5 --- /dev/null +++ b/src/test/java/net/kaaass/rumbase/FileUtil.java @@ -0,0 +1,55 @@ +package net.kaaass.rumbase; + +import lombok.extern.slf4j.Slf4j; + +import java.io.File; + +/** + * 单元测试常见文件操作 + */ +@Slf4j +public class FileUtil { + public final static String DATA_PATH = "data/"; + public final static String TABLE_PATH = "data/table/"; + public static final String TEST_PATH = "test_gen_files/"; + + public static void prepare() { + log.info("创建测试文件夹..."); + FileUtil.createDir(FileUtil.TEST_PATH); + FileUtil.createDir(FileUtil.DATA_PATH); + FileUtil.createDir(FileUtil.TABLE_PATH); + } + + public static void clear() { + log.info("清除测试文件夹..."); + FileUtil.removeDir(FileUtil.TEST_PATH); + FileUtil.removeDir(FileUtil.DATA_PATH); + FileUtil.removeDir(FileUtil.TABLE_PATH); + } + + public static void createDir(String path) { + File dir = new File(path); + if (dir.exists()) { + FileUtil.removeDir(dir); + } + assert dir.mkdirs(); + } + + public static void removeDir(String path) { + removeDir(new File(path)); + } + + public static void removeDir(File dir) { + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + removeDir(file); + } else { + assert file.delete(); + } + } + } + dir.delete(); + } +} diff --git a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java index 3cdbb00..cd6f433 100644 --- a/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/dataitem/IItemStorageTest.java @@ -2,11 +2,16 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.dataitem.exception.PageCorruptedException; import net.kaaass.rumbase.dataitem.exception.UUIDException; import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.page.exception.PageException; import net.kaaass.rumbase.transaction.TransactionContext; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.IOException; import java.util.ArrayList; @@ -15,6 +20,7 @@ import java.util.Random; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.fail; /** * 对数据项管理部分进行测试 @@ -24,13 +30,24 @@ */ @Slf4j -public class IItemStorageTest extends TestCase { +public class IItemStorageTest { - private static final String PATH = "build/"; + @BeforeClass + public static void createDataFolder() { + FileUtil.prepare(); + } + + @AfterClass + public static void clearDataFolder() { + FileUtil.clear(); + } + + private static final String PATH = FileUtil.TEST_PATH; /** * 测试能否从已有文件中解析得到数据项管理器 */ + @Test public void testGetFromFile() throws FileException, IOException, PageException { String fileName = PATH + "testGetFromFile.db"; var itemStorage = ItemManager.fromFile(fileName); @@ -46,6 +63,7 @@ public void testGetFromFile() throws FileException, IOException, PageException { /** * 测试能否新建文件并得到数据项管理器 */ + @Test public void testCreateFile() throws IOException, FileException, PageException { TransactionContext txContext = TransactionContext.empty(); String fileName = PATH + "testCreateFile.db"; @@ -64,6 +82,7 @@ public void testCreateFile() throws IOException, FileException, PageException { /** * 进行插入的测试 */ + @Test public void testInsert() throws FileException, IOException, PageException, UUIDException, PageCorruptedException { String fileName = PATH + "testInsert.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); @@ -81,6 +100,7 @@ public void testInsert() throws FileException, IOException, PageException, UUIDE /** * 对插入一个已分配UUID的测试 */ + @Test public void testInsertWithUUID() throws FileException, IOException, PageException { String fileName = PATH + "testInsertWithUUID.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); @@ -105,6 +125,7 @@ public void testInsertWithUUID() throws FileException, IOException, PageExceptio /** * 对插入大量数据进行测试 */ + @Test public void testManyInsert() throws FileException, IOException, PageException, UUIDException, PageCorruptedException { String fileName = PATH + "testInsertMany.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); @@ -125,6 +146,7 @@ public void testManyInsert() throws FileException, IOException, PageException, U /** * 获取整个页的数据项进行测试 */ + @Test public void testQueryByPageID() throws FileException, IOException, PageException, PageCorruptedException { String fileName = PATH + "testQueryByPageID.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); @@ -188,6 +210,7 @@ public void run() { /** * 测试并发下插入是否有问题 */ + @Test public void testSynInsert() throws IOException, FileException, PageException { String fileName = PATH + "testInsert.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); @@ -203,6 +226,7 @@ public void testSynInsert() throws IOException, FileException, PageException { /** * 对更新进行测试 */ + @Test public void testUpdate() throws FileException, IOException, PageException, UUIDException, PageCorruptedException { String fileName = PATH + "testUpdate.db"; TransactionContext txContext = TransactionContext.empty(); @@ -230,6 +254,7 @@ public void testUpdate() throws FileException, IOException, PageException, UUIDE /** * 测试修改和获取表头信息 */ + @Test public void testMeta() throws FileException, IOException, PageException, UUIDException, PageCorruptedException { String fileName = PATH + "testMeta.db"; IItemStorage iItemStorage = ItemManager.fromFile(fileName); diff --git a/src/test/java/net/kaaass/rumbase/index/BTreeTest.java b/src/test/java/net/kaaass/rumbase/index/BTreeTest.java index e9ab737..6e8be60 100644 --- a/src/test/java/net/kaaass/rumbase/index/BTreeTest.java +++ b/src/test/java/net/kaaass/rumbase/index/BTreeTest.java @@ -2,8 +2,13 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; import net.kaaass.rumbase.index.exception.IndexNotFoundException; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; import java.util.Iterator; @@ -16,12 +21,23 @@ * @see net.kaaass.rumbase.index.BTreeTest */ @Slf4j -public class BTreeTest extends TestCase { - public static final String fileDir = "build/"; +public class BTreeTest { + public static final String fileDir = FileUtil.TEST_PATH; + + @BeforeClass + public static void createDataFolder() { + FileUtil.prepare(); + } + + @AfterClass + public static void clearDataFolder() { + FileUtil.clear(); + } /** * 测试索引的插入与第一个迭代器功能 */ + @Test public void testInsert() { Index testIndex = null; try { @@ -42,7 +58,7 @@ public void testInsert() { // 测试数据是否符合预期 int cnt = 0; for (var pair : testIndex) { - assertEquals(cnt / 2, + Assert.assertEquals(cnt / 2, pair.getKey()); // log.debug("{}", pair); cnt++; @@ -52,6 +68,7 @@ public void testInsert() { /** * 测试不按顺寻key插入的情况 */ + @Test public void testInsertRandomKey() { Index testIndex = null; try { @@ -72,7 +89,7 @@ public void testInsertRandomKey() { // 测试数据是否符合预期 int cnt = 0; for (var pair : testIndex) { - assertEquals(cnt / 2, + Assert.assertEquals(cnt / 2, pair.getKey()); // log.debug("{}", pair); cnt++; @@ -82,6 +99,7 @@ public void testInsertRandomKey() { /** * 测试索引的查询功能 */ + @Test public void testQuery() { Index testIndex = null; @@ -110,18 +128,18 @@ public void testQuery() { // 测试 findFirst 方法 // keyHash在内的迭代器 1122->334455 Iterator it1 = testIndex.findFirst(3); - assertTrue(it1.hasNext()); - assertEquals(3, it1.next().getKey()); - assertEquals(3, it1.next().getKey()); - assertEquals(4, it1.next().getKey()); + Assert.assertTrue(it1.hasNext()); + Assert.assertEquals(3, it1.next().getKey()); + Assert.assertEquals(3, it1.next().getKey()); + Assert.assertEquals(4, it1.next().getKey()); // 测试 findUpperbound 方法 // 不包括keyHash在内的迭代器 112233->4455 Iterator it2 = testIndex.findUpperbound(3); - assertTrue(it2.hasNext()); - assertEquals(4, it2.next().getKey()); - assertEquals(4, it2.next().getKey()); - assertEquals(5, it2.next().getKey()); + Assert.assertTrue(it2.hasNext()); + Assert.assertEquals(4, it2.next().getKey()); + Assert.assertEquals(4, it2.next().getKey()); + Assert.assertEquals(5, it2.next().getKey()); // 测试 query 方法 var results = testIndex.query(4); @@ -133,6 +151,7 @@ public void testQuery() { /** * 测试multiKey索引的查询功能 */ + @Test public void testMultiKeyQuery() { Index testIndex = null; @@ -166,18 +185,18 @@ public void testMultiKeyQuery() { // 测试 findFirst 方法 // keyHash在内的迭代器 1122->334455 Iterator it1 = testIndex.findFirst(3); - assertTrue(it1.hasNext()); - assertEquals(3, it1.next().getKey()); - assertEquals(3, it1.next().getKey()); - assertEquals(3, it1.next().getKey()); + Assert.assertTrue(it1.hasNext()); + Assert.assertEquals(3, it1.next().getKey()); + Assert.assertEquals(3, it1.next().getKey()); + Assert.assertEquals(3, it1.next().getKey()); // 测试 findUpperbound 方法 // 不包括keyHash在内的迭代器 112233->4455 Iterator it2 = testIndex.findUpperbound(3); - assertTrue(it2.hasNext()); - assertEquals(4, it2.next().getKey()); - assertEquals(4, it2.next().getKey()); - assertEquals(4, it2.next().getKey()); + Assert.assertTrue(it2.hasNext()); + Assert.assertEquals(4, it2.next().getKey()); + Assert.assertEquals(4, it2.next().getKey()); + Assert.assertEquals(4, it2.next().getKey()); for (int i = 0; i < 3; i++) { log.debug("{}", it2.next().getUuid()); } diff --git a/src/test/java/net/kaaass/rumbase/index/ConcurrentIndexTest.java b/src/test/java/net/kaaass/rumbase/index/ConcurrentIndexTest.java index c06af40..db7d77f 100644 --- a/src/test/java/net/kaaass/rumbase/index/ConcurrentIndexTest.java +++ b/src/test/java/net/kaaass/rumbase/index/ConcurrentIndexTest.java @@ -2,18 +2,34 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; import java.util.Random; @Slf4j -public class ConcurrentIndexTest extends TestCase { - public static final String fileDir = "build/"; +public class ConcurrentIndexTest { + public static final String fileDir = FileUtil.TEST_PATH; + + @BeforeClass + public static void createDataFolder() { + FileUtil.prepare(); + } + + @AfterClass + public static void clearDataFolder() { + FileUtil.clear(); + } /** * 测试索引的并发功能 */ + @Test public void test() { Index testIndex = null; try { @@ -50,7 +66,7 @@ public void test() { // 测试数据是否符合预期 int cnt = 0; for (var pair : testIndex) { - assertEquals(cnt / 2, + Assert.assertEquals(cnt / 2, pair.getKey()); //log.debug("{}", pair); cnt++; @@ -60,6 +76,7 @@ public void test() { /** * 测试索引的更复杂的并发功能 */ + @Test public void testComplex() { Index testIndex = null; try { @@ -108,7 +125,7 @@ public void testComplex() { // 测试数据是否符合预期 int cnt = 0; for (var pair : testIndex) { - assertEquals(cnt / 3, + Assert.assertEquals(cnt / 3, pair.getKey()); //log.debug("{}", pair); cnt++; diff --git a/src/test/java/net/kaaass/rumbase/index/IndexTest.java b/src/test/java/net/kaaass/rumbase/index/IndexTest.java index 597d7aa..425401f 100644 --- a/src/test/java/net/kaaass/rumbase/index/IndexTest.java +++ b/src/test/java/net/kaaass/rumbase/index/IndexTest.java @@ -2,8 +2,13 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; import net.kaaass.rumbase.index.exception.IndexNotFoundException; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; import java.util.ArrayList; @@ -18,12 +23,23 @@ * @see net.kaaass.rumbase.index.Index */ @Slf4j -public class IndexTest extends TestCase { - public static final String fileDir = "build/"; +public class IndexTest { + public static final String fileDir = FileUtil.TEST_PATH; + + @BeforeClass + public static void createDataFolder() { + FileUtil.prepare(); + } + + @AfterClass + public static void clearDataFolder() { + FileUtil.clear(); + } /** * 测试索引的插入与第一个迭代器功能 */ + @Test public void testInsert() { Index testIndex = null; var standardRand = new ArrayList(); @@ -45,7 +61,7 @@ public void testInsert() { // 测试数据是否符合预期 int cnt = 0; for (var pair : testIndex) { - assertEquals(standardRand.get(cnt).longValue(), + Assert.assertEquals(standardRand.get(cnt).longValue(), pair.getUuid()); cnt++; } @@ -54,6 +70,7 @@ public void testInsert() { /** * 测试索引的查询功能 */ + @Test public void testQuery() { Index testIndex = null; var standardRand = new ArrayList(); @@ -85,28 +102,28 @@ public void testQuery() { // 测试 findFirst 方法 // keyHash在内的迭代器 1122->334455 Iterator it1 = testIndex.findFirst(3); - assertTrue(it1.hasNext()); + Assert.assertTrue(it1.hasNext()); var expected = List.of( standardRand.get(2 * 2), standardRand.get(2 * 2 + 1) ); - assertTrue(expected.contains(it1.next().getUuid())); - assertTrue(expected.contains(it1.next().getUuid())); + Assert.assertTrue(expected.contains(it1.next().getUuid())); + Assert.assertTrue(expected.contains(it1.next().getUuid())); // 测试 findUpperbound 方法 // 不包括keyHash在内的迭代器 112233->4455 Iterator it2 = testIndex.findUpperbound(3); - assertTrue(it2.hasNext()); + Assert.assertTrue(it2.hasNext()); expected = List.of( standardRand.get(2), standardRand.get(2 + 1) ); - assertTrue(expected.contains(it2.next().getUuid())); - assertTrue(expected.contains(it2.next().getUuid())); + Assert.assertTrue(expected.contains(it2.next().getUuid())); + Assert.assertTrue(expected.contains(it2.next().getUuid())); // 测试 query 方法 var results = testIndex.query(4); - assertTrue(results.contains(standardRand.get(2))); - assertTrue(results.contains(standardRand.get(2 + 1))); + Assert.assertTrue(results.contains(standardRand.get(2))); + Assert.assertTrue(results.contains(standardRand.get(2 + 1))); } } diff --git a/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java b/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java index 15c1c8e..6a3d08a 100644 --- a/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/page/PageStorageTest.java @@ -1,8 +1,13 @@ package net.kaaass.rumbase.page; import junit.framework.TestCase; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.page.exception.PageException; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; import java.io.FileInputStream; @@ -17,9 +22,20 @@ * @author XuanLaoYee * @see net.kaaass.rumbase.page.PageStorage */ -public class PageStorageTest extends TestCase { - public static String filePath = "build/pageTest.db"; +public class PageStorageTest { + public static String filePath = FileUtil.TEST_PATH + "pageTest.db"; + @BeforeClass + public static void createDataFolder() { + FileUtil.prepare(); + } + + @AfterClass + public static void clearDataFolder() { + FileUtil.clear(); + } + + @Test public void testGet() throws IOException { PageStorage rps = null; try { @@ -49,7 +65,7 @@ public void testGet() throws IOException { in.skip((11 + PageManager.FILE_HEAD_SIZE) * PageManager.PAGE_SIZE); int readNumber = in.read(dataFromFile); p11.getData().read(dataFromPage); - assertEquals(readNumber, PageManager.PAGE_SIZE); + Assert.assertEquals(readNumber, PageManager.PAGE_SIZE); assertArrayEquals(dataFromPage, dataFromFile); } catch (Exception e) { throw e; @@ -59,6 +75,7 @@ public void testGet() throws IOException { } } + @Test public void testWriteToFile() throws FileException, PageException { PageStorage storage = PageManager.fromFile(filePath); int[] testPage = new int[]{1, 3, 5, 7, 10, 11}; @@ -85,6 +102,7 @@ public void testWriteToFile() throws FileException, PageException { } } + @Test public void testFlush() throws PageException { PageStorage rps = null; try { @@ -152,6 +170,7 @@ public void testFlush() throws PageException { assertArrayEquals(data4, dataFromFile4); } + @Test public void testFlushAll() throws FileException, PageException { PageStorage storage = PageManager.fromFile(filePath); int[] testPage = new int[]{1, 3, 5, 7, 10, 11}; diff --git a/src/test/java/net/kaaass/rumbase/page/PageTest.java b/src/test/java/net/kaaass/rumbase/page/PageTest.java index 1832c99..3f28b21 100644 --- a/src/test/java/net/kaaass/rumbase/page/PageTest.java +++ b/src/test/java/net/kaaass/rumbase/page/PageTest.java @@ -1,9 +1,13 @@ package net.kaaass.rumbase.page; import junit.framework.TestCase; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.page.exception.PageException; +import org.junit.AfterClass; import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; import java.io.FileInputStream; @@ -18,8 +22,20 @@ * @author XuanLaoYee * @see net.kaaass.rumbase.page.Page */ -public class PageTest extends TestCase { - public static String filePath = "build/pageTest.db"; +public class PageTest { + public static String filePath = FileUtil.TEST_PATH + "pageTest.db"; + + @BeforeClass + public static void createDataFolder() { + FileUtil.prepare(); + } + + @AfterClass + public static void clearDataFolder() { + FileUtil.clear(); + } + + @Test public void testPatchData() { int offset = 99; byte[] data = new byte[PageManager.PAGE_SIZE - offset]; @@ -41,6 +57,7 @@ public void testPatchData() { } } + @Test public void testPatchOffset() throws FileException, PageException { var storage = PageManager.fromFile(filePath); var rand = new Random(); @@ -62,7 +79,7 @@ public void testPatchOffset() throws FileException, PageException { // 检查写入效果 var pageData = page.getDataBytes(); for (int j = st; j < ed; j++) { - assertEquals((byte) st, pageData[j]); + Assert.assertEquals((byte) st, pageData[j]); } } } finally { diff --git a/src/test/java/net/kaaass/rumbase/query/CreateIndexExecutorTest.java b/src/test/java/net/kaaass/rumbase/query/CreateIndexExecutorTest.java index 861fe15..0a52345 100644 --- a/src/test/java/net/kaaass/rumbase/query/CreateIndexExecutorTest.java +++ b/src/test/java/net/kaaass/rumbase/query/CreateIndexExecutorTest.java @@ -2,100 +2,108 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; import net.kaaass.rumbase.parse.SqlParser; import net.kaaass.rumbase.parse.exception.SqlSyntaxException; import net.kaaass.rumbase.parse.stmt.CreateIndexStatement; -import net.kaaass.rumbase.table.Table; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; import net.kaaass.rumbase.table.TableManager; +import net.kaaass.rumbase.table.exception.TableConflictException; import net.kaaass.rumbase.table.exception.TableExistenceException; import net.kaaass.rumbase.table.field.BaseField; import net.kaaass.rumbase.table.field.IntField; import net.kaaass.rumbase.table.field.VarcharField; import net.kaaass.rumbase.transaction.TransactionContext; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; import java.util.ArrayList; @Slf4j -public class CreateIndexExecutorTest extends TestCase { +public class CreateIndexExecutorTest { - private static final String PATH = "build/"; + @BeforeClass + @AfterClass + public static void clearDataFolder() { + log.info("清除数据文件夹..."); + FileUtil.removeDir(FileUtil.DATA_PATH); + } - public void testParseSingle() throws SqlSyntaxException { + @Test + public void testParseSingle() throws SqlSyntaxException, IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { var sql = "CREATE INDEX PersonIndex ON testParseSingle$Person (LastName) ;"; // 解析 var stmt = SqlParser.parseStatement(sql); - assertTrue(stmt instanceof CreateIndexStatement); + Assert.assertTrue(stmt instanceof CreateIndexStatement); // 准备预期结果 var manager = new TableManager(); var context = TransactionContext.empty(); var fields = new ArrayList(); - var dummy = new Table("testParseSingle.__reserved__", fields); - fields.add(new VarcharField("LastName", 20, false, dummy)); + fields.add(new VarcharField("LastName", 20, false, null)); try { - manager.createTable(context, "testParseSingle$Person", fields, PATH + "testParseSingle.Person.db"); - } catch (TableExistenceException e) { + manager.createTable(context, "testParseSingle$Person", fields, FileUtil.TABLE_PATH + "testParseSingle.Person.db"); + } catch (TableExistenceException | RecordNotFoundException | ArgumentException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { var table = manager.getTable("testParseSingle$Person"); var field = table.getField("LastName"); - assertTrue(field.isPresent()); - assertFalse(field.get().indexed()); + Assert.assertTrue(field.isPresent()); + Assert.assertFalse(field.get().indexed()); - var createExe = new CreateIndexExecutor((CreateIndexStatement) stmt, manager); + var createExe = new CreateIndexExecutor((CreateIndexStatement) stmt, manager, context); createExe.execute(); - assertTrue(field.get().indexed()); - assertEquals("PersonIndex", field.get().getIndexName()); + Assert.assertTrue(field.get().indexed()); + Assert.assertEquals("data/index/testParseSingle$Person$LastName", field.get().getIndexName()); } catch (TableExistenceException | IndexAlreadyExistException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - - new File("metadata.db").deleteOnExit(); } - public void testParseMulti() throws SqlSyntaxException { + @Test + public void testParseMulti() throws SqlSyntaxException, IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { var sql = "CREATE INDEX PersonIndex ON testParseMulti$Person (LastName, ID) ;"; // 解析 var stmt = SqlParser.parseStatement(sql); - assertTrue(stmt instanceof CreateIndexStatement); + Assert.assertTrue(stmt instanceof CreateIndexStatement); // 准备预期结果 var manager = new TableManager(); var context = TransactionContext.empty(); var fields = new ArrayList(); - var dummy = new Table("testParseMulti.__reserved__", fields); - fields.add(new VarcharField("LastName", 20, false, dummy)); - fields.add(new IntField("ID", false, dummy)); + fields.add(new VarcharField("LastName", 20, false, null)); + fields.add(new IntField("ID", false, null)); try { - manager.createTable(context, "testParseMulti$Person", fields, PATH + "testParseMulti.Person.db"); - } catch (TableExistenceException e) { + manager.createTable(context, "testParseMulti$Person", fields, FileUtil.TABLE_PATH + "testParseMulti.Person.db"); + } catch (TableExistenceException | RecordNotFoundException | ArgumentException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { var table = manager.getTable("testParseMulti$Person"); var field1 = table.getField("LastName"); var field2 = table.getField("ID"); - assertTrue(field1.isPresent()); - assertFalse(field1.get().indexed()); - assertTrue(field2.isPresent()); - assertFalse(field2.get().indexed()); + Assert.assertTrue(field1.isPresent()); + Assert.assertFalse(field1.get().indexed()); + Assert.assertTrue(field2.isPresent()); + Assert.assertFalse(field2.get().indexed()); - var createExe = new CreateIndexExecutor((CreateIndexStatement) stmt, manager); + var createExe = new CreateIndexExecutor((CreateIndexStatement) stmt, manager, context); createExe.execute(); - assertTrue(field1.get().indexed()); - assertFalse(field2.get().indexed()); - assertEquals("PersonIndex", field1.get().getIndexName()); + Assert.assertTrue(field1.get().indexed()); + Assert.assertFalse(field2.get().indexed()); + Assert.assertEquals("data/index/testParseMulti$Person$LastName", field1.get().getIndexName()); } catch (TableExistenceException | IndexAlreadyExistException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - - new File("metadata.db").deleteOnExit(); } } diff --git a/src/test/java/net/kaaass/rumbase/query/CreateTableExecutorTest.java b/src/test/java/net/kaaass/rumbase/query/CreateTableExecutorTest.java index 59733fe..7c1b01d 100644 --- a/src/test/java/net/kaaass/rumbase/query/CreateTableExecutorTest.java +++ b/src/test/java/net/kaaass/rumbase/query/CreateTableExecutorTest.java @@ -2,10 +2,13 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; import net.kaaass.rumbase.parse.SqlParser; import net.kaaass.rumbase.parse.exception.SqlSyntaxException; import net.kaaass.rumbase.parse.stmt.CreateTableStatement; import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; import net.kaaass.rumbase.table.Table; import net.kaaass.rumbase.table.TableManager; import net.kaaass.rumbase.table.exception.TableConflictException; @@ -13,13 +16,25 @@ import net.kaaass.rumbase.table.field.FieldType; import net.kaaass.rumbase.table.field.VarcharField; import net.kaaass.rumbase.transaction.TransactionContext; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; @Slf4j -public class CreateTableExecutorTest extends TestCase { +public class CreateTableExecutorTest { - public void testCreate() throws SqlSyntaxException { + @BeforeClass + @AfterClass + public static void clearDataFolder() { + log.info("清除数据文件夹..."); + FileUtil.removeDir(new File(FileUtil.DATA_PATH)); + } + + @Test + public void testCreate() throws SqlSyntaxException, IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { var sql = "CREATE TABLE testCreate$Persons\n" + "(\n" + "Id_P int not null,\n" + @@ -28,7 +43,7 @@ public void testCreate() throws SqlSyntaxException { ")"; // 解析 var stmt = SqlParser.parseStatement(sql); - assertTrue(stmt instanceof CreateTableStatement); + Assert.assertTrue(stmt instanceof CreateTableStatement); // 执行 var manager = new TableManager(); var context = TransactionContext.empty(); @@ -37,7 +52,7 @@ public void testCreate() throws SqlSyntaxException { exe.execute(); } catch (TableExistenceException | TableConflictException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 确认结果 Table table = null; @@ -45,28 +60,26 @@ public void testCreate() throws SqlSyntaxException { table = manager.getTable("testCreate$Persons"); } catch (TableExistenceException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - assertNotNull(table); + Assert.assertNotNull(table); var fields = table.getFields(); - assertEquals(3, fields.size()); - - assertEquals("Id_P", fields.get(0).getName()); - assertEquals(FieldType.INT, fields.get(0).getType()); - assertFalse(fields.get(0).isNullable()); + Assert.assertEquals(3, fields.size()); + Assert.assertEquals("Id_P", fields.get(0).getName()); + Assert.assertEquals(FieldType.INT, fields.get(0).getType()); + Assert.assertFalse(fields.get(0).isNullable()); - assertEquals("LastName", fields.get(1).getName()); - assertEquals(FieldType.VARCHAR, fields.get(1).getType()); - assertEquals(255, ((VarcharField)fields.get(1)).getLimit()); - assertTrue(fields.get(1).isNullable()); + Assert.assertEquals("LastName", fields.get(1).getName()); + Assert.assertEquals(FieldType.VARCHAR, fields.get(1).getType()); + Assert.assertEquals(255, ((VarcharField) fields.get(1)).getLimit()); + Assert.assertTrue(fields.get(1).isNullable()); - assertEquals("FirstName", fields.get(2).getName()); - assertEquals(FieldType.VARCHAR, fields.get(2).getType()); - assertEquals(255, ((VarcharField)fields.get(1)).getLimit()); - assertFalse(fields.get(2).isNullable()); - new File("metadata.db").deleteOnExit(); + Assert.assertEquals("FirstName", fields.get(2).getName()); + Assert.assertEquals(FieldType.VARCHAR, fields.get(2).getType()); + Assert.assertEquals(255, ((VarcharField) fields.get(1)).getLimit()); + Assert.assertFalse(fields.get(2).isNullable()); } } diff --git a/src/test/java/net/kaaass/rumbase/query/DeleteExecutorTest.java b/src/test/java/net/kaaass/rumbase/query/DeleteExecutorTest.java index 322761a..0a8387d 100644 --- a/src/test/java/net/kaaass/rumbase/query/DeleteExecutorTest.java +++ b/src/test/java/net/kaaass/rumbase/query/DeleteExecutorTest.java @@ -2,6 +2,7 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; import net.kaaass.rumbase.parse.SqlParser; import net.kaaass.rumbase.parse.exception.SqlSyntaxException; @@ -16,39 +17,48 @@ import net.kaaass.rumbase.table.field.IntField; import net.kaaass.rumbase.table.field.VarcharField; import net.kaaass.rumbase.transaction.TransactionContext; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; import java.util.ArrayList; @Slf4j -public class DeleteExecutorTest extends TestCase { +public class DeleteExecutorTest { - private static final String PATH = "build/"; + @BeforeClass + @AfterClass + public static void clearDataFolder() { + log.info("清除数据文件夹..."); + FileUtil.removeDir(new File(FileUtil.DATA_PATH)); + } - public void testDelete() throws SqlSyntaxException { + @Test + public void testDelete() throws SqlSyntaxException, IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { var sql = "DELETE FROM testDelete$Person WHERE LastName = 'KevinAxel'"; // 解析 var stmt = SqlParser.parseStatement(sql); - assertTrue(stmt instanceof DeleteStatement); + Assert.assertTrue(stmt instanceof DeleteStatement); var manager = new TableManager(); var context = TransactionContext.empty(); var fields = new ArrayList(); - var dummy = new Table("testDelete.__reserved__", fields); - var id = new IntField("ID", false, dummy); - fields.add(new VarcharField("LastName", 20, false, dummy)); + var id = new IntField("ID", false, null); + fields.add(new VarcharField("LastName", 20, false, null)); fields.add(id); Table table = null; try { - manager.createTable(context, "testDelete$Person", fields, PATH + "testDelete.Person.db"); + manager.createTable(context, "testDelete$Person", fields, FileUtil.TABLE_PATH + "testDelete.Person.db"); id.createIndex(); table = manager.getTable("testDelete$Person"); - } catch (TableExistenceException | IndexAlreadyExistException e) { + } catch (TableExistenceException | IndexAlreadyExistException | RecordNotFoundException | ArgumentException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - assertNotNull(table); + Assert.assertNotNull(table); try { table.insert(context, new ArrayList<>() {{ add(0, "'KevinAxel'"); @@ -61,25 +71,25 @@ public void testDelete() throws SqlSyntaxException { } catch (TableConflictException | TableExistenceException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 测试插入结果 try { var data = table.readAll(context); - assertEquals(2, data.size()); + Assert.assertEquals(2, data.size()); - assertEquals(2, data.get(0).size()); - assertEquals("KevinAxel", (String) data.get(0).get(0)); - assertEquals(1, (int) data.get(0).get(1)); + Assert.assertEquals(2, data.get(0).size()); + Assert.assertEquals("KevinAxel", (String) data.get(0).get(0)); + Assert.assertEquals(1, (int) data.get(0).get(1)); - assertEquals(2, data.get(1).size()); - assertEquals("KAAAsS", (String) data.get(1).get(0)); - assertEquals(2, (int) data.get(1).get(1)); + Assert.assertEquals(2, data.get(1).size()); + Assert.assertEquals("KAAAsS", (String) data.get(1).get(0)); + Assert.assertEquals(2, (int) data.get(1).get(1)); } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 执行 @@ -88,51 +98,48 @@ public void testDelete() throws SqlSyntaxException { exe.execute(); } catch (TableExistenceException | ArgumentException | IndexAlreadyExistException | TableConflictException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 测试执行结果 try { var data = table.readAll(context); - assertEquals(1, data.size()); + Assert.assertEquals(1, data.size()); - assertEquals(2, data.get(0).size()); - assertEquals("KAAAsS", (String) data.get(0).get(0)); - assertEquals(2, (int) data.get(0).get(1)); + Assert.assertEquals(2, data.get(0).size()); + Assert.assertEquals("KAAAsS", (String) data.get(0).get(0)); + Assert.assertEquals(2, (int) data.get(0).get(1)); } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - - new File("metadata.db").deleteOnExit(); - } - public void testDeleteAll() throws SqlSyntaxException { + @Test + public void testDeleteAll() throws SqlSyntaxException, IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { var sql = "DELETE FROM testDeleteAll$Person "; // 解析 var stmt = SqlParser.parseStatement(sql); - assertTrue(stmt instanceof DeleteStatement); + Assert.assertTrue(stmt instanceof DeleteStatement); var manager = new TableManager(); var context = TransactionContext.empty(); var fields = new ArrayList(); - var dummy = new Table("testDeleteAll.__reserved__", fields); - var id = new IntField("ID", false, dummy); - fields.add(new VarcharField("LastName", 20, false, dummy)); + var id = new IntField("ID", false, null); + fields.add(new VarcharField("LastName", 20, false, null)); fields.add(id); Table table = null; try { - manager.createTable(context, "testDeleteAll$Person", fields, PATH + "testDeleteAll.Person.db"); + manager.createTable(context, "testDeleteAll$Person", fields, FileUtil.TABLE_PATH + "testDeleteAll.Person.db"); id.createIndex(); table = manager.getTable("testDeleteAll$Person"); - } catch (TableExistenceException | IndexAlreadyExistException e) { + } catch (TableExistenceException | IndexAlreadyExistException | RecordNotFoundException | ArgumentException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - assertNotNull(table); + Assert.assertNotNull(table); try { table.insert(context, new ArrayList<>() {{ add(0, "'KevinAxel'"); @@ -145,25 +152,25 @@ public void testDeleteAll() throws SqlSyntaxException { } catch (TableConflictException | TableExistenceException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 测试插入结果 try { var data = table.readAll(context); - assertEquals(2, data.size()); + Assert.assertEquals(2, data.size()); - assertEquals(2, data.get(0).size()); - assertEquals("KevinAxel", (String) data.get(0).get(0)); - assertEquals(1, (int) data.get(0).get(1)); + Assert.assertEquals(2, data.get(0).size()); + Assert.assertEquals("KevinAxel", (String) data.get(0).get(0)); + Assert.assertEquals(1, (int) data.get(0).get(1)); - assertEquals(2, data.get(1).size()); - assertEquals("KAAAsS", (String) data.get(1).get(0)); - assertEquals(2, (int) data.get(1).get(1)); + Assert.assertEquals(2, data.get(1).size()); + Assert.assertEquals("KAAAsS", (String) data.get(1).get(0)); + Assert.assertEquals(2, (int) data.get(1).get(1)); } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 执行 @@ -172,20 +179,17 @@ public void testDeleteAll() throws SqlSyntaxException { exe.execute(); } catch (TableExistenceException | ArgumentException | IndexAlreadyExistException | TableConflictException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 测试执行结果 try { var data = table.readAll(context); - assertEquals(0, data.size()); + Assert.assertEquals(0, data.size()); } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - - new File("metadata.db").deleteOnExit(); - } } diff --git a/src/test/java/net/kaaass/rumbase/query/InsertExecutorTest.java b/src/test/java/net/kaaass/rumbase/query/InsertExecutorTest.java index 45d2624..2755b12 100644 --- a/src/test/java/net/kaaass/rumbase/query/InsertExecutorTest.java +++ b/src/test/java/net/kaaass/rumbase/query/InsertExecutorTest.java @@ -2,6 +2,7 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; import net.kaaass.rumbase.parse.SqlParser; import net.kaaass.rumbase.parse.exception.SqlSyntaxException; @@ -16,39 +17,48 @@ import net.kaaass.rumbase.table.field.IntField; import net.kaaass.rumbase.table.field.VarcharField; import net.kaaass.rumbase.transaction.TransactionContext; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; import java.util.ArrayList; @Slf4j -public class InsertExecutorTest extends TestCase { +public class InsertExecutorTest { - private static final String PATH = "build/"; + @BeforeClass + @AfterClass + public static void clearDataFolder() { + log.info("清除数据文件夹..."); + FileUtil.removeDir(new File(FileUtil.DATA_PATH)); + } - public void testInsertColumnValue() throws SqlSyntaxException { + @Test + public void testInsertColumnValue() throws SqlSyntaxException, IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { var sql = "INSERT INTO Persons (Persons.LastName, Address) VALUES ('Wilson', 'Champs-Elysees')"; // 解析 var stmt = SqlParser.parseStatement(sql); - assertTrue(stmt instanceof InsertStatement); + Assert.assertTrue(stmt instanceof InsertStatement); var manager = new TableManager(); var context = TransactionContext.empty(); var fields = new ArrayList(); - var dummy = new Table("testInsertColumnValue.__reserved__", fields); - var lastName = new VarcharField("LastName", 20, false, dummy); + var lastName = new VarcharField("LastName", 20, false, null); fields.add(lastName); - fields.add(new VarcharField("Address", 255, false, dummy)); + fields.add(new VarcharField("Address", 255, false, null)); Table table = null; try { - manager.createTable(context, "Persons", fields, PATH + "testInsertColumnValue.Persons.db"); + manager.createTable(context, "Persons", fields, FileUtil.TABLE_PATH + "testInsertColumnValue.Persons.db"); lastName.createIndex(); table = manager.getTable("Persons"); - } catch (TableExistenceException | IndexAlreadyExistException e) { + } catch (TableExistenceException | IndexAlreadyExistException | RecordNotFoundException | ArgumentException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - assertNotNull(table); + Assert.assertNotNull(table); // 执行 var exe = new InsertExecutor((InsertStatement) stmt, manager, context); @@ -56,49 +66,44 @@ public void testInsertColumnValue() throws SqlSyntaxException { exe.execute(); } catch (TableExistenceException | TableConflictException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 确认结果 try { var data = table.readAll(context); - assertEquals(1, data.size()); - assertEquals("Wilson", data.get(0).get(0)); - assertEquals("Champs-Elysees", data.get(0).get(1)); + Assert.assertEquals(1, data.size()); + Assert.assertEquals("Wilson", data.get(0).get(0)); + Assert.assertEquals("Champs-Elysees", data.get(0).get(1)); } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - - new File("metadata.db").deleteOnExit(); - } - public void testInsertValue() throws SqlSyntaxException { + @Test + public void testInsertValue() throws SqlSyntaxException, IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { var sql = "INSERT INTO stu VALUES (20200101, 'KAAAsS', true, 3.9)"; // 解析 var stmt = SqlParser.parseStatement(sql); - assertTrue(stmt instanceof InsertStatement); + Assert.assertTrue(stmt instanceof InsertStatement); var manager = new TableManager(); var context = TransactionContext.empty(); var fields = new ArrayList(); - var dummy = new Table("testInsertValue.__reserved__", fields); - var id = new IntField("ID", false, dummy); - fields.add(new VarcharField("LastName", 20, false, dummy)); + var id = new IntField("ID", false, null); + fields.add(new VarcharField("LastName", 20, false, null)); fields.add(id); Table table = null; try { - manager.createTable(context, "Person", fields, PATH + "testInsertValue.Person.db"); + manager.createTable(context, "Person", fields, FileUtil.TABLE_PATH + "testInsertValue.Person.db"); id.createIndex(); table = manager.getTable("Person"); - } catch (TableExistenceException | IndexAlreadyExistException e) { + } catch (TableExistenceException | IndexAlreadyExistException | RecordNotFoundException | ArgumentException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - assertNotNull(table); - new File("metadata.db").deleteOnExit(); - + Assert.assertNotNull(table); } } diff --git a/src/test/java/net/kaaass/rumbase/query/SelectExecutorTest.java b/src/test/java/net/kaaass/rumbase/query/SelectExecutorTest.java index 8e86c7a..7b3f7c9 100644 --- a/src/test/java/net/kaaass/rumbase/query/SelectExecutorTest.java +++ b/src/test/java/net/kaaass/rumbase/query/SelectExecutorTest.java @@ -2,6 +2,7 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; import net.kaaass.rumbase.parse.SqlParser; import net.kaaass.rumbase.parse.exception.SqlSyntaxException; @@ -17,16 +18,26 @@ import net.kaaass.rumbase.table.field.IntField; import net.kaaass.rumbase.table.field.VarcharField; import net.kaaass.rumbase.transaction.TransactionContext; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; import java.util.ArrayList; @Slf4j -public class SelectExecutorTest extends TestCase { +public class SelectExecutorTest { - private static final String PATH = "build/"; + @BeforeClass + @AfterClass + public static void clearDataFolder() { + log.info("清除数据文件夹..."); + FileUtil.removeDir(new File(FileUtil.DATA_PATH)); + } - public void testSelect() throws SqlSyntaxException { + @Test + public void testSelect() throws SqlSyntaxException, IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { var sql = "SELECT distinct name, testSelect$account.ID, testSelect$account.balance \n" + "from testSelect$account join testSelect$payment on testSelect$account.ID = testSelect$payment.ID\n" + "WHERE testSelect$account.ID > 1 and (testSelect$payment.type = 'N' or testSelect$payment.type = 'T') \n" + @@ -40,22 +51,20 @@ public void testSelect() throws SqlSyntaxException { // 创建account表 var accountFields = new ArrayList(); - var accountDummy = new Table("testSelect.__reserved__", accountFields); - var id = new IntField("ID", false, accountDummy); + var id = new IntField("ID", false, null); accountFields.add(id); - accountFields.add(new VarcharField("name", 20, false, accountDummy)); - accountFields.add(new FloatField("balance", false, accountDummy)); + accountFields.add(new VarcharField("name", 20, false, null)); + accountFields.add(new FloatField("balance", false, null)); Table account = null; try { - manager.createTable(context, "testSelect$account", accountFields, PATH + "testSelect.account.db"); + manager.createTable(context, "testSelect$account", accountFields, FileUtil.TABLE_PATH + "testSelect.account.db"); id.createIndex(); account = manager.getTable("testSelect$account"); - } catch (TableExistenceException | IndexAlreadyExistException e) { + } catch (TableExistenceException | IndexAlreadyExistException | RecordNotFoundException | ArgumentException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - - assertNotNull(account); + Assert.assertNotNull(account); try { account.insert(context, new ArrayList<>() {{ add(0, "1"); @@ -75,52 +84,51 @@ public void testSelect() throws SqlSyntaxException { } catch (TableConflictException | TableExistenceException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 测试插入结果 try { var data = account.readAll(context); - assertEquals(3, data.size()); + Assert.assertEquals(3, data.size()); - assertEquals(3, data.get(0).size()); - assertEquals(1, (int) data.get(0).get(0)); - assertEquals("KevinAxel", (String) data.get(0).get(1)); - assertEquals(5000f, data.get(0).get(2)); + Assert.assertEquals(3, data.get(0).size()); + Assert.assertEquals(1, (int) data.get(0).get(0)); + Assert.assertEquals("KevinAxel", (String) data.get(0).get(1)); + Assert.assertEquals(5000f, data.get(0).get(2)); - assertEquals(3, data.get(1).size()); - assertEquals(2, (int) data.get(1).get(0)); - assertEquals("KAAAsS", (String) data.get(1).get(1)); - assertEquals(8000f, data.get(1).get(2)); + Assert.assertEquals(3, data.get(1).size()); + Assert.assertEquals(2, (int) data.get(1).get(0)); + Assert.assertEquals("KAAAsS", (String) data.get(1).get(1)); + Assert.assertEquals(8000f, data.get(1).get(2)); - assertEquals(3, data.get(2).size()); - assertEquals(3, (int) data.get(2).get(0)); - assertEquals("kkk", (String) data.get(2).get(1)); - assertEquals(8000f, data.get(2).get(2)); + Assert.assertEquals(3, data.get(2).size()); + Assert.assertEquals(3, (int) data.get(2).get(0)); + Assert.assertEquals("kkk", (String) data.get(2).get(1)); + Assert.assertEquals(8000f, data.get(2).get(2)); } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 创建payment表 var paymentFields = new ArrayList(); - var paymentDummy = new Table("testSelect.__reserved__", paymentFields); - var paymentId = new IntField("ID", false, paymentDummy); + var paymentId = new IntField("ID", false, null); paymentFields.add(paymentId); - paymentFields.add(new VarcharField("type", 1, false, paymentDummy)); + paymentFields.add(new VarcharField("type", 1, false, null)); Table payment = null; try { - manager.createTable(context, "testSelect$payment", paymentFields, PATH + "testSelect.payment.db"); + manager.createTable(context, "testSelect$payment", paymentFields, FileUtil.TABLE_PATH + "testSelect.payment.db"); paymentId.createIndex(); payment = manager.getTable("testSelect$payment"); - } catch (TableExistenceException | IndexAlreadyExistException e) { + } catch (TableExistenceException | IndexAlreadyExistException | RecordNotFoundException | ArgumentException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - assertNotNull(payment); + Assert.assertNotNull(payment); try { payment.insert(context, new ArrayList<>() {{ add(0, "1"); @@ -137,30 +145,30 @@ public void testSelect() throws SqlSyntaxException { } catch (TableConflictException | TableExistenceException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 测试插入结果 try { var data = payment.readAll(context); - assertEquals(3, data.size()); + Assert.assertEquals(3, data.size()); - assertEquals(2, data.get(0).size()); - assertEquals(1, (int) data.get(0).get(0)); - assertEquals("N", (String) data.get(0).get(1)); + Assert.assertEquals(2, data.get(0).size()); + Assert.assertEquals(1, (int) data.get(0).get(0)); + Assert.assertEquals("N", (String) data.get(0).get(1)); - assertEquals(2, data.get(1).size()); - assertEquals(2, (int) data.get(1).get(0)); - assertEquals("T", (String) data.get(1).get(1)); + Assert.assertEquals(2, data.get(1).size()); + Assert.assertEquals(2, (int) data.get(1).get(0)); + Assert.assertEquals("T", (String) data.get(1).get(1)); - assertEquals(2, data.get(2).size()); - assertEquals(3, (int) data.get(2).get(0)); - assertEquals("T", (String) data.get(2).get(1)); + Assert.assertEquals(2, data.get(2).size()); + Assert.assertEquals(3, (int) data.get(2).get(0)); + Assert.assertEquals("T", (String) data.get(2).get(1)); } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 执行语句 @@ -169,33 +177,29 @@ public void testSelect() throws SqlSyntaxException { exe.execute(); } catch (TableConflictException | ArgumentException | TableExistenceException | IndexAlreadyExistException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } var cols = exe.getResultTable(); var data = exe.getResultData(); - assertEquals(3, cols.size()); - assertEquals("testSelect$account", cols.get(0).getTableName()); - assertEquals("name", cols.get(0).getFieldName()); - assertEquals("testSelect$account", cols.get(1).getTableName()); - assertEquals("ID", cols.get(1).getFieldName()); - assertEquals("testSelect$account", cols.get(2).getTableName()); - assertEquals("balance", cols.get(2).getFieldName()); - - assertEquals(2, data.size()); - - assertEquals(3, data.get(0).size()); - assertEquals(3, data.get(0).get(0)); - assertEquals("kkk", data.get(0).get(1)); - assertEquals(8000f, data.get(0).get(2)); - - assertEquals(3, data.get(0).size()); - assertEquals(2, data.get(1).get(0)); - assertEquals("KAAAsS", data.get(1).get(1)); - assertEquals(8000f, data.get(1).get(2)); - - - new File("metadata.db").deleteOnExit(); - + Assert.assertEquals(3, cols.size()); + Assert.assertEquals("testSelect$account", cols.get(0).getTableName()); + Assert.assertEquals("name", cols.get(0).getFieldName()); + Assert.assertEquals("testSelect$account", cols.get(1).getTableName()); + Assert.assertEquals("ID", cols.get(1).getFieldName()); + Assert.assertEquals("testSelect$account", cols.get(2).getTableName()); + Assert.assertEquals("balance", cols.get(2).getFieldName()); + + Assert.assertEquals(2, data.size()); + + Assert.assertEquals(3, data.get(0).size()); + Assert.assertEquals(3, data.get(0).get(0)); + Assert.assertEquals("kkk", data.get(0).get(1)); + Assert.assertEquals(8000f, data.get(0).get(2)); + + Assert.assertEquals(3, data.get(0).size()); + Assert.assertEquals(2, data.get(1).get(0)); + Assert.assertEquals("KAAAsS", data.get(1).get(1)); + Assert.assertEquals(8000f, data.get(1).get(2)); } } diff --git a/src/test/java/net/kaaass/rumbase/query/UpdateExecutorTest.java b/src/test/java/net/kaaass/rumbase/query/UpdateExecutorTest.java index c2f594d..0de8f0a 100644 --- a/src/test/java/net/kaaass/rumbase/query/UpdateExecutorTest.java +++ b/src/test/java/net/kaaass/rumbase/query/UpdateExecutorTest.java @@ -1,7 +1,7 @@ package net.kaaass.rumbase.query; -import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; import net.kaaass.rumbase.parse.SqlParser; import net.kaaass.rumbase.parse.exception.SqlSyntaxException; @@ -13,78 +13,90 @@ import net.kaaass.rumbase.table.exception.TableConflictException; import net.kaaass.rumbase.table.exception.TableExistenceException; import net.kaaass.rumbase.table.field.BaseField; +import net.kaaass.rumbase.table.field.IntField; import net.kaaass.rumbase.table.field.VarcharField; import net.kaaass.rumbase.transaction.TransactionContext; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; import java.util.ArrayList; @Slf4j -public class UpdateExecutorTest extends TestCase { +public class UpdateExecutorTest { - private static final String PATH = "build/"; + @BeforeClass + @AfterClass + public static void clearDataFolder() { + log.info("清除数据文件夹..."); + FileUtil.removeDir(new File(FileUtil.DATA_PATH)); + } - public void testUpdateWithCondition() throws SqlSyntaxException { + @Test + public void testUpdateWithCondition() throws SqlSyntaxException, IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { var sql = "UPDATE testUpdateWithCondition$Person SET Address = 'Zhongshan 23', City = 'Nanjing'\n" + - "WHERE LastName = 'Wilson'"; + "WHERE ID = 2"; // 解析 var stmt = SqlParser.parseStatement(sql); - assertTrue(stmt instanceof UpdateStatement); + Assert.assertTrue(stmt instanceof UpdateStatement); var manager = new TableManager(); var context = TransactionContext.empty(); var fields = new ArrayList(); var dummy = new Table("testUpdateWithCondition.__reserved__", fields); - var lastName = new VarcharField("LastName", 20, false, dummy); + var lastName = new IntField("ID", false, dummy); fields.add(lastName); fields.add(new VarcharField("City", 20, false, dummy)); fields.add(new VarcharField("Address", 20, false, dummy)); Table table = null; try { - manager.createTable(context, "testUpdateWithCondition$Person", fields, PATH + "testUpdateWithCondition.Person.db"); + manager.createTable(context, "testUpdateWithCondition$Person", fields, FileUtil.TABLE_PATH + "testUpdateWithCondition.Person.db"); lastName.createIndex(); table = manager.getTable("testUpdateWithCondition$Person"); - } catch (TableExistenceException | IndexAlreadyExistException e) { + } catch (TableExistenceException | IndexAlreadyExistException | RecordNotFoundException | ArgumentException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - assertNotNull(table); + Assert.assertNotNull(table); try { table.insert(context, new ArrayList<>() {{ - add(0, "'Wilson'"); - add(1, "'JiaXing'"); - add(2, "'Zhongshan 45'"); - }}); - table.insert(context, new ArrayList<>() {{ - add(0, "'KAAAsS'"); + add(0, "1"); add(1, "'WenZhou'"); add(2, "'Zhongshan 78'"); }}); + table.insert(context, new ArrayList<>() {{ + add(0, "2"); + add(1, "'JiaXing'"); + add(2, "'Zhongshan 45'"); + }}); } catch (TableConflictException | TableExistenceException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 测试插入结果 try { var data = table.readAll(context); - assertEquals(2, data.size()); + Assert.assertEquals(2, data.size()); - assertEquals(3, data.get(0).size()); - assertEquals("Wilson", (String) data.get(0).get(0)); - assertEquals("JiaXing", (String) data.get(0).get(1)); - assertEquals("Zhongshan 45", (String) data.get(0).get(2)); + Assert.assertEquals(3, data.get(0).size()); + Assert.assertEquals(1, data.get(0).get(0)); + Assert.assertEquals("WenZhou", data.get(0).get(1)); + Assert.assertEquals("Zhongshan 78", data.get(0).get(2)); + + Assert.assertEquals(3, data.get(1).size()); + Assert.assertEquals(2, data.get(1).get(0)); + Assert.assertEquals("JiaXing", data.get(1).get(1)); + Assert.assertEquals("Zhongshan 45", data.get(1).get(2)); - assertEquals(3, data.get(1).size()); - assertEquals("KAAAsS", (String) data.get(1).get(0)); - assertEquals("WenZhou", (String) data.get(1).get(1)); - assertEquals("Zhongshan 78", (String) data.get(1).get(2)); } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 执行 @@ -93,33 +105,33 @@ public void testUpdateWithCondition() throws SqlSyntaxException { exe.execute(); } catch (TableExistenceException | ArgumentException | IndexAlreadyExistException | TableConflictException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 检查执行结果 try { var data = table.readAll(context); - assertEquals(2, data.size()); + log.info("Result: {}", data); + Assert.assertEquals(2, data.size()); - assertEquals(3, data.get(0).size()); - assertEquals("Wilson", (String) data.get(0).get(0)); - assertEquals("Nanjing", (String) data.get(0).get(1)); - assertEquals("Zhongshan 23", (String) data.get(0).get(2)); + Assert.assertEquals(3, data.get(1).size()); + Assert.assertEquals(2, data.get(1).get(0)); + Assert.assertEquals("Nanjing", data.get(1).get(1)); + Assert.assertEquals("Zhongshan 23", data.get(1).get(2)); - assertEquals(3, data.get(1).size()); - assertEquals("KAAAsS", (String) data.get(1).get(0)); - assertEquals("WenZhou", (String) data.get(1).get(1)); - assertEquals("Zhongshan 78", (String) data.get(1).get(2)); + Assert.assertEquals(3, data.get(0).size()); + Assert.assertEquals(1, data.get(0).get(0)); + Assert.assertEquals("WenZhou", data.get(0).get(1)); + Assert.assertEquals("Zhongshan 78", data.get(0).get(2)); } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - new File("metadata.db").deleteOnExit(); - } - public void testUpdateWithoutCondition() throws SqlSyntaxException { + @Test + public void testUpdateWithoutCondition() throws SqlSyntaxException, IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { var sql = "UPDATE testUpdateWithoutCondition$Person SET Address = 'Zhongshan 23', City = 'Nanjing'"; // 解析 var stmt = SqlParser.parseStatement(sql); @@ -128,57 +140,56 @@ public void testUpdateWithoutCondition() throws SqlSyntaxException { var manager = new TableManager(); var context = TransactionContext.empty(); var fields = new ArrayList(); - var dummy = new Table("testUpdateWithoutCondition.__reserved__", fields); - var lastName = new VarcharField("LastName", 20, false, dummy); + var lastName = new IntField("ID", false, null); fields.add(lastName); - fields.add(new VarcharField("City", 20, false, dummy)); - fields.add(new VarcharField("Address", 20, false, dummy)); + fields.add(new VarcharField("City", 20, false, null)); + fields.add(new VarcharField("Address", 20, false, null)); Table table = null; try { - manager.createTable(context, "testUpdateWithoutCondition$Person", fields, PATH + "testUpdateWithoutCondition.Person.db"); + manager.createTable(context, "testUpdateWithoutCondition$Person", fields, FileUtil.TABLE_PATH + "testUpdateWithoutCondition.Person.db"); lastName.createIndex(); table = manager.getTable("testUpdateWithoutCondition$Person"); } catch (TableExistenceException | IndexAlreadyExistException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - assertNotNull(table); + Assert.assertNotNull(table); try { table.insert(context, new ArrayList<>() {{ - add(0, "'Wilson'"); + add(0, "1"); add(1, "'JiaXing'"); add(2, "'Zhongshan 45'"); }}); table.insert(context, new ArrayList<>() {{ - add(0, "'KAAAsS'"); + add(0, "2"); add(1, "'WenZhou'"); add(2, "'Zhongshan 78'"); }}); } catch (TableConflictException | TableExistenceException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 测试插入结果 try { var data = table.readAll(context); - assertEquals(2, data.size()); + Assert.assertEquals(2, data.size()); - assertEquals(3, data.get(0).size()); - assertEquals("Wilson", (String) data.get(0).get(0)); - assertEquals("JiaXing", (String) data.get(0).get(1)); - assertEquals("Zhongshan 45", (String) data.get(0).get(2)); + Assert.assertEquals(3, data.get(0).size()); + Assert.assertEquals(1, data.get(0).get(0)); + Assert.assertEquals("JiaXing", data.get(0).get(1)); + Assert.assertEquals("Zhongshan 45", data.get(0).get(2)); - assertEquals(3, data.get(1).size()); - assertEquals("KAAAsS", (String) data.get(1).get(0)); - assertEquals("WenZhou", (String) data.get(1).get(1)); - assertEquals("Zhongshan 78", (String) data.get(1).get(2)); + Assert.assertEquals(3, data.get(1).size()); + Assert.assertEquals(2, data.get(1).get(0)); + Assert.assertEquals("WenZhou", data.get(1).get(1)); + Assert.assertEquals("Zhongshan 78", data.get(1).get(2)); } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 执行 @@ -187,29 +198,28 @@ public void testUpdateWithoutCondition() throws SqlSyntaxException { exe.execute(); } catch (TableExistenceException | ArgumentException | IndexAlreadyExistException | TableConflictException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 检查执行结果 try { var data = table.readAll(context); - assertEquals(2, data.size()); + log.info("Result: {}", data); + Assert.assertEquals(2, data.size()); - assertEquals(3, data.get(0).size()); - assertEquals("KAAAsS", (String) data.get(0).get(0)); - assertEquals("Nanjing", (String) data.get(0).get(1)); - assertEquals("Zhongshan 23", (String) data.get(0).get(2)); + Assert.assertEquals(3, data.get(0).size()); + Assert.assertEquals(1, data.get(0).get(0)); + Assert.assertEquals("Nanjing", data.get(0).get(1)); + Assert.assertEquals("Zhongshan 23", data.get(0).get(2)); - assertEquals(3, data.get(1).size()); - assertEquals("Wilson", (String) data.get(1).get(0)); - assertEquals("Nanjing", (String) data.get(1).get(1)); - assertEquals("Zhongshan 23", (String) data.get(1).get(2)); + Assert.assertEquals(3, data.get(1).size()); + Assert.assertEquals(2, data.get(1).get(0)); + Assert.assertEquals("Nanjing", data.get(1).get(1)); + Assert.assertEquals("Zhongshan 23", data.get(1).get(2)); } catch (TableExistenceException | TableConflictException | ArgumentException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - new File("metadata.db").deleteOnExit(); - } } diff --git a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java index 8ab58a2..5667e1e 100644 --- a/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java +++ b/src/test/java/net/kaaass/rumbase/record/IRecordStorageTest.java @@ -2,8 +2,13 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.record.exception.RecordNotFoundException; import net.kaaass.rumbase.transaction.TransactionContext; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; import java.util.UUID; @@ -17,22 +22,34 @@ * @see net.kaaass.rumbase.record.IRecordStorage */ @Slf4j -public class IRecordStorageTest extends TestCase { +public class IRecordStorageTest { - public final static String PATH = "build/"; + @BeforeClass + public static void createDataFolder() { + FileUtil.prepare(); + } + + @AfterClass + public static void clearDataFolder() { + FileUtil.clear(); + } + + public final static String PATH = FileUtil.TEST_PATH; + @Test public void testQuery() { var storage = RecordManager.fromFile(PATH + "test_query"); var context = TransactionContext.empty(); try { storage.query(context, UUID.randomUUID().getLeastSignificantBits()); - fail("unknown physical record should get exception"); + Assert.fail("unknown physical record should get exception"); } catch (RecordNotFoundException e) { log.error("Exception expected: ", e); } } + @Test public void testInsert() throws RecordNotFoundException { var storage = RecordManager.fromFile(PATH + "test_insert"); var context = TransactionContext.empty(); @@ -40,10 +57,11 @@ public void testInsert() throws RecordNotFoundException { var id = storage.insert(context, new byte[]{0x1, 0x2, 0x1f}); var result = storage.queryOptional(context, id); - assertTrue("result should present", result.isPresent()); + Assert.assertTrue("result should present", result.isPresent()); assertArrayEquals(new byte[]{0x1, 0x2, 0x1f}, result.get()); } + @Test public void testDelete() throws RecordNotFoundException { var storage = RecordManager.fromFile(PATH + "test_delete"); var context = TransactionContext.empty(); @@ -52,16 +70,16 @@ public void testDelete() throws RecordNotFoundException { storage.insert(context, new byte[]{0x7, 0x3, 0x1f}); var id = storage.insert(context, new byte[]{0x54, 0x23, 0x23, 0x44}); var result = storage.queryOptional(context, id); - assertTrue("result should present", result.isPresent()); + Assert.assertTrue("result should present", result.isPresent()); storage.delete(context, id); result = storage.queryOptional(context, id); - assertTrue("record should be deleted", result.isEmpty()); + Assert.assertTrue("record should be deleted", result.isEmpty()); } + @Test public void testMetadata() { - new File(PATH + "test_metadata").deleteOnExit(); var storage = RecordManager.fromFile(PATH + "test_metadata"); var context = TransactionContext.empty(); diff --git a/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java b/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java index fac8a73..e12d1bb 100644 --- a/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java +++ b/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java @@ -2,19 +2,35 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.record.exception.RecordNotFoundException; import net.kaaass.rumbase.transaction.TransactionIsolation; import net.kaaass.rumbase.transaction.TransactionManager; import net.kaaass.rumbase.transaction.TransactionManagerImpl; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.IOException; @Slf4j -public class MvccReadCommitTest extends TestCase { +public class MvccReadCommitTest { - public final static String PATH = "build/"; + @BeforeClass + public static void createDataFolder() { + FileUtil.prepare(); + } + + @AfterClass + public static void clearDataFolder() { + FileUtil.clear(); + } + + public final static String PATH = FileUtil.TEST_PATH; + @Test public void testReadSelf() throws RecordNotFoundException { var storage = RecordManager.fromFile(PATH + "testReadSelf"); var manager = new FakeTxManager(TransactionIsolation.READ_COMMITTED); @@ -22,17 +38,18 @@ public void testReadSelf() throws RecordNotFoundException { var tx1 = manager.begin(); // 事务1的记录自身可见 var a1 = storage.insert(tx1, new byte[]{0x23, 0x63}); - assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + Assert.assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); // 事务2不可见a1 var tx2 = manager.begin(); - assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + Assert.assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); // 事务1新纪录也可见 for (int i = 0; i < 100; i++) { var uuid = storage.insert(tx1, new byte[]{0x23, 0x63, 0x44}); - assertTrue("uuid see a1", storage.queryOptional(tx1, uuid).isPresent()); + Assert.assertTrue("uuid see a1", storage.queryOptional(tx1, uuid).isPresent()); } } + @Test public void testReadOther() throws RecordNotFoundException { var storage = RecordManager.fromFile(PATH + "testReadOther"); var manager = new FakeTxManager(TransactionIsolation.READ_COMMITTED); @@ -41,25 +58,26 @@ public void testReadOther() throws RecordNotFoundException { var tx2 = manager.begin(); // 事务2创建的b1,事务1不可见 var b1 = storage.insert(tx2, new byte[]{0x1, 0x2, 0x3}); - assertTrue("tx2 see b1", storage.queryOptional(tx2, b1).isPresent()); - assertTrue("tx1 blind b1", storage.queryOptional(tx1, b1).isEmpty()); + Assert.assertTrue("tx2 see b1", storage.queryOptional(tx2, b1).isPresent()); + Assert.assertTrue("tx1 blind b1", storage.queryOptional(tx1, b1).isEmpty()); // 事务1创建的a1,事务2不可见 var a1 = storage.insert(tx1, new byte[]{0x6, 0x5, 0x4, 0x32}); - assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); - assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + Assert.assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + Assert.assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); // 事务2提交,则事务1可见b1 tx2.commit(); - assertTrue("tx1 see b1 after commit", storage.queryOptional(tx1, b1).isPresent()); + Assert.assertTrue("tx1 see b1 after commit", storage.queryOptional(tx1, b1).isPresent()); // 新建事务3 var tx3 = manager.begin(); // 事务3可以看到b1,但是不能看到a1 - assertTrue("tx3 see b1 after commit", storage.queryOptional(tx3, b1).isPresent()); - assertTrue("tx3 blind a1 before commit", storage.queryOptional(tx3, a1).isEmpty()); + Assert.assertTrue("tx3 see b1 after commit", storage.queryOptional(tx3, b1).isPresent()); + Assert.assertTrue("tx3 blind a1 before commit", storage.queryOptional(tx3, a1).isEmpty()); // 提交事务1,a1可见 tx1.commit(); - assertTrue("tx3 see a1 after commit", storage.queryOptional(tx3, a1).isPresent()); + Assert.assertTrue("tx3 see a1 after commit", storage.queryOptional(tx3, a1).isPresent()); } + @Test public void testDelete() throws RecordNotFoundException { var storage = RecordManager.fromFile(PATH + "testDelete"); var manager = new FakeTxManager(TransactionIsolation.READ_COMMITTED); @@ -67,24 +85,25 @@ public void testDelete() throws RecordNotFoundException { var tx1 = manager.begin(); var a1 = storage.insert(tx1, new byte[]{0x1, 0x2, 0x3}); var a2 = storage.insert(tx1, new byte[]{0x5, 0x6, 0x7}); - assertTrue(storage.queryOptional(tx1, a1).isPresent()); - assertTrue(storage.queryOptional(tx1, a2).isPresent()); + Assert.assertTrue(storage.queryOptional(tx1, a1).isPresent()); + Assert.assertTrue(storage.queryOptional(tx1, a2).isPresent()); // 自身删除 storage.delete(tx1, a1); - assertTrue("a1 should be deleted", storage.queryOptional(tx1, a1).isEmpty()); + Assert.assertTrue("a1 should be deleted", storage.queryOptional(tx1, a1).isEmpty()); // 提交事务1 tx1.commit(); // 事务2删除,事务3仍然可见a2 var tx2 = manager.begin(); var tx3 = manager.begin(); storage.delete(tx2, a2); - assertTrue("tx2 blind a2 after delete", storage.queryOptional(tx2, a2).isEmpty()); - assertTrue("tx3 see a2 before commit", storage.queryOptional(tx3, a2).isPresent()); + Assert.assertTrue("tx2 blind a2 after delete", storage.queryOptional(tx2, a2).isEmpty()); + Assert.assertTrue("tx3 see a2 before commit", storage.queryOptional(tx3, a2).isPresent()); // 事务2提交,事务3不可见a2 tx2.commit(); - assertTrue("tx3 blind a2 after commit", storage.queryOptional(tx3, a2).isEmpty()); + Assert.assertTrue("tx3 blind a2 after commit", storage.queryOptional(tx3, a2).isEmpty()); } + @Test public void testReadSelfReal() throws RecordNotFoundException, IOException, FileException { var storage = RecordManager.fromFile(PATH + "testReadSelfReal"); var manager = new TransactionManagerImpl(); @@ -93,21 +112,22 @@ public void testReadSelfReal() throws RecordNotFoundException, IOException, File tx1.start(); // 事务1的记录自身可见 var a1 = storage.insert(tx1, new byte[]{0x23, 0x63}); - assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + Assert.assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); // 事务2不可见a1 var tx2 = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); tx2.start(); - assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + Assert.assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); // 事务1新纪录也可见 for (int i = 0; i < 100; i++) { var uuid = storage.insert(tx1, new byte[]{0x23, 0x63, 0x44}); - assertTrue("uuid see a1", storage.queryOptional(tx1, uuid).isPresent()); + Assert.assertTrue("uuid see a1", storage.queryOptional(tx1, uuid).isPresent()); } // tx1.commit(); tx2.commit(); } + @Test public void testReadOtherReal() throws RecordNotFoundException, IOException, FileException { var storage = RecordManager.fromFile(PATH + "testReadOtherReal"); var manager = new TransactionManagerImpl(); @@ -118,27 +138,28 @@ public void testReadOtherReal() throws RecordNotFoundException, IOException, Fil tx2.start(); // 事务2创建的b1,事务1不可见 var b1 = storage.insert(tx2, new byte[]{0x1, 0x2, 0x3}); - assertTrue("tx2 see b1", storage.queryOptional(tx2, b1).isPresent()); - assertTrue("tx1 blind b1", storage.queryOptional(tx1, b1).isEmpty()); + Assert.assertTrue("tx2 see b1", storage.queryOptional(tx2, b1).isPresent()); + Assert.assertTrue("tx1 blind b1", storage.queryOptional(tx1, b1).isEmpty()); // 事务1创建的a1,事务2不可见 var a1 = storage.insert(tx1, new byte[]{0x6, 0x5, 0x4, 0x32}); - assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); - assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + Assert.assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + Assert.assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); // 事务2提交,则事务1可见b1 tx2.commit(); - assertTrue("tx1 see b1 after commit", storage.queryOptional(tx1, b1).isPresent()); + Assert.assertTrue("tx1 see b1 after commit", storage.queryOptional(tx1, b1).isPresent()); // 新建事务3 var tx3 = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); tx3.start(); // 事务3可以看到b1,但是不能看到a1 - assertTrue("tx3 see b1 after commit", storage.queryOptional(tx3, b1).isPresent()); - assertTrue("tx3 blind a1 before commit", storage.queryOptional(tx3, a1).isEmpty()); + Assert.assertTrue("tx3 see b1 after commit", storage.queryOptional(tx3, b1).isPresent()); + Assert.assertTrue("tx3 blind a1 before commit", storage.queryOptional(tx3, a1).isEmpty()); // 提交事务1,a1可见 tx1.commit(); - assertTrue("tx3 see a1 after commit", storage.queryOptional(tx3, a1).isPresent()); + Assert.assertTrue("tx3 see a1 after commit", storage.queryOptional(tx3, a1).isPresent()); tx3.commit(); } + @Test public void testDeleteReal() throws RecordNotFoundException, IOException, FileException { var storage = RecordManager.fromFile(PATH + "testDeleteReal"); var manager = new TransactionManagerImpl(); @@ -147,11 +168,11 @@ public void testDeleteReal() throws RecordNotFoundException, IOException, FileEx tx1.start(); var a1 = storage.insert(tx1, new byte[]{0x1, 0x2, 0x3}); var a2 = storage.insert(tx1, new byte[]{0x5, 0x6, 0x7}); - assertTrue(storage.queryOptional(tx1, a1).isPresent()); - assertTrue(storage.queryOptional(tx1, a2).isPresent()); + Assert.assertTrue(storage.queryOptional(tx1, a1).isPresent()); + Assert.assertTrue(storage.queryOptional(tx1, a2).isPresent()); // 自身删除 storage.delete(tx1, a1); - assertTrue("a1 should be deleted", storage.queryOptional(tx1, a1).isEmpty()); + Assert.assertTrue("a1 should be deleted", storage.queryOptional(tx1, a1).isEmpty()); // 提交事务1 tx1.commit(); // 事务2删除,事务3仍然可见a2 @@ -160,11 +181,11 @@ public void testDeleteReal() throws RecordNotFoundException, IOException, FileEx var tx3 = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); tx3.start(); storage.delete(tx2, a2); - assertTrue("tx2 blind a2 after delete", storage.queryOptional(tx2, a2).isEmpty()); - assertTrue("tx3 see a2 before commit", storage.queryOptional(tx3, a2).isPresent()); + Assert.assertTrue("tx2 blind a2 after delete", storage.queryOptional(tx2, a2).isEmpty()); + Assert.assertTrue("tx3 see a2 before commit", storage.queryOptional(tx3, a2).isPresent()); // 事务2提交,事务3不可见a2 tx2.commit(); - assertTrue("tx3 blind a2 after commit", storage.queryOptional(tx3, a2).isEmpty()); + Assert.assertTrue("tx3 blind a2 after commit", storage.queryOptional(tx3, a2).isEmpty()); tx3.commit(); } } diff --git a/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java b/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java index ac0fce2..3f35268 100644 --- a/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java +++ b/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java @@ -2,6 +2,7 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.record.exception.NeedRollbackException; import net.kaaass.rumbase.record.exception.RecordNotFoundException; @@ -9,14 +10,29 @@ import net.kaaass.rumbase.transaction.TransactionIsolation; import net.kaaass.rumbase.transaction.TransactionManager; import net.kaaass.rumbase.transaction.TransactionManagerImpl; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.IOException; @Slf4j -public class MvccReadRepeatableTest extends TestCase { +public class MvccReadRepeatableTest { - public final static String PATH = "build/"; + @BeforeClass + public static void createDataFolder() { + FileUtil.prepare(); + } + + @AfterClass + public static void clearDataFolder() { + FileUtil.clear(); + } + + public final static String PATH = FileUtil.TEST_PATH; + @Test public void testReadSelf() throws RecordNotFoundException { var storage = RecordManager.fromFile(PATH + "testReadSelf"); var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); @@ -24,17 +40,18 @@ public void testReadSelf() throws RecordNotFoundException { var tx1 = manager.begin(); // 事务1的记录自身可见 var a1 = storage.insert(tx1, new byte[]{0x23, 0x63}); - assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + Assert.assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); // 事务2不可见a1 var tx2 = manager.begin(); - assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + Assert.assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); // 事务1新纪录也可见 for (int i = 0; i < 100; i++) { var uuid = storage.insert(tx1, new byte[]{0x23, 0x63, 0x44}); - assertTrue("uuid see a1", storage.queryOptional(tx1, uuid).isPresent()); + Assert.assertTrue("uuid see a1", storage.queryOptional(tx1, uuid).isPresent()); } } + @Test public void testReadOther() throws RecordNotFoundException { var storage = RecordManager.fromFile(PATH + "testReadOther"); var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); @@ -43,25 +60,26 @@ public void testReadOther() throws RecordNotFoundException { var tx2 = manager.begin(); // 事务2创建的b1,事务1不可见 var b1 = storage.insert(tx2, new byte[]{0x1, 0x2, 0x3}); - assertTrue("tx2 see b1", storage.queryOptional(tx2, b1).isPresent()); - assertTrue("tx1 blind b1", storage.queryOptional(tx1, b1).isEmpty()); + Assert.assertTrue("tx2 see b1", storage.queryOptional(tx2, b1).isPresent()); + Assert.assertTrue("tx1 blind b1", storage.queryOptional(tx1, b1).isEmpty()); // 事务1创建的a1,事务2不可见 var a1 = storage.insert(tx1, new byte[]{0x6, 0x5, 0x4, 0x32}); - assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); - assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + Assert.assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + Assert.assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); // 事务2提交,则事务1依旧不可见b1,因为事务1不可见事务2 tx2.commit(); - assertTrue("tx1 blind b1 after commit", storage.queryOptional(tx1, b1).isEmpty()); + Assert.assertTrue("tx1 blind b1 after commit", storage.queryOptional(tx1, b1).isEmpty()); // 新建事务3 var tx3 = manager.begin(); // 事务3可以看到b1,但是不能看到a1 - assertTrue("tx3 see b1 after commit", storage.queryOptional(tx3, b1).isPresent()); - assertTrue("tx3 blind a1 before commit", storage.queryOptional(tx3, a1).isEmpty()); + Assert.assertTrue("tx3 see b1 after commit", storage.queryOptional(tx3, b1).isPresent()); + Assert.assertTrue("tx3 blind a1 before commit", storage.queryOptional(tx3, a1).isEmpty()); // 提交事务1,a1还是不可见,因为事务3创建时事务1还在运行 tx1.commit(); - assertTrue("tx3 blind a1 after commit", storage.queryOptional(tx3, a1).isEmpty()); + Assert.assertTrue("tx3 blind a1 after commit", storage.queryOptional(tx3, a1).isEmpty()); } + @Test public void testDelete() throws RecordNotFoundException { var storage = RecordManager.fromFile(PATH + "testDelete"); var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); @@ -69,24 +87,25 @@ public void testDelete() throws RecordNotFoundException { var tx1 = manager.begin(); var a1 = storage.insert(tx1, new byte[]{0x1, 0x2, 0x3}); var a2 = storage.insert(tx1, new byte[]{0x5, 0x6, 0x7}); - assertTrue(storage.queryOptional(tx1, a1).isPresent()); - assertTrue(storage.queryOptional(tx1, a2).isPresent()); + Assert.assertTrue(storage.queryOptional(tx1, a1).isPresent()); + Assert.assertTrue(storage.queryOptional(tx1, a2).isPresent()); // 自身删除 storage.delete(tx1, a1); - assertTrue("a1 should be deleted", storage.queryOptional(tx1, a1).isEmpty()); + Assert.assertTrue("a1 should be deleted", storage.queryOptional(tx1, a1).isEmpty()); // 提交事务1 tx1.commit(); // 事务2删除,事务3仍然可见a2 var tx2 = manager.begin(); var tx3 = manager.begin(); storage.delete(tx2, a2); - assertTrue("tx2 blind a2 after delete", storage.queryOptional(tx2, a2).isEmpty()); - assertTrue("tx3 see a2 before commit", storage.queryOptional(tx3, a2).isPresent()); + Assert.assertTrue("tx2 blind a2 after delete", storage.queryOptional(tx2, a2).isEmpty()); + Assert.assertTrue("tx3 see a2 before commit", storage.queryOptional(tx3, a2).isPresent()); // 事务2提交,事务3依旧可见a2,因为事务3开始时事务2没有结束 tx2.commit(); - assertTrue("tx3 see a2 after commit", storage.queryOptional(tx3, a2).isPresent()); + Assert.assertTrue("tx3 see a2 after commit", storage.queryOptional(tx3, a2).isPresent()); } + @Test public void testVersionSkip() throws RecordNotFoundException, NeedRollbackException { var storage = RecordManager.fromFile(PATH + "testDelete"); var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); @@ -95,19 +114,20 @@ public void testVersionSkip() throws RecordNotFoundException, NeedRollbackExcept // 进行版本跳跃操作 var tx1 = manager.begin(); var tx2 = manager.begin(); - assertTrue(storage.queryOptional(tx1, r).isPresent()); - assertTrue(storage.queryOptional(tx2, r).isPresent()); + Assert.assertTrue(storage.queryOptional(tx1, r).isPresent()); + Assert.assertTrue(storage.queryOptional(tx2, r).isPresent()); storage.delete(tx1, r); tx1.commit(); try { storage.delete(tx2, r); - fail("Should rollback tx2 at here"); + Assert.fail("Should rollback tx2 at here"); tx2.commit(); } catch (NeedRollbackException e) { log.info("Expected runtime exception: ", e); } } + @Test public void testReadSelfReal() throws RecordNotFoundException, IOException, FileException { var storage = RecordManager.fromFile(PATH + "testReadSelfReal"); var manager = new TransactionManagerImpl(); @@ -116,21 +136,22 @@ public void testReadSelfReal() throws RecordNotFoundException, IOException, File tx1.start(); // 事务1的记录自身可见 var a1 = storage.insert(tx1, new byte[]{0x23, 0x63}); - assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + Assert.assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); // 事务2不可见a1 var tx2 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); tx2.start(); - assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + Assert.assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); // 事务1新纪录也可见 for (int i = 0; i < 100; i++) { var uuid = storage.insert(tx1, new byte[]{0x23, 0x63, 0x44}); - assertTrue("uuid see a1", storage.queryOptional(tx1, uuid).isPresent()); + Assert.assertTrue("uuid see a1", storage.queryOptional(tx1, uuid).isPresent()); } // tx1.commit(); tx2.commit(); } + @Test public void testReadOtherReal() throws RecordNotFoundException, IOException, FileException { var storage = RecordManager.fromFile(PATH + "testReadOtherReal"); var manager = new TransactionManagerImpl(); @@ -141,28 +162,29 @@ public void testReadOtherReal() throws RecordNotFoundException, IOException, Fil tx2.start(); // 事务2创建的b1,事务1不可见 var b1 = storage.insert(tx2, new byte[]{0x1, 0x2, 0x3}); - assertTrue("tx2 see b1", storage.queryOptional(tx2, b1).isPresent()); - assertTrue("tx1 blind b1", storage.queryOptional(tx1, b1).isEmpty()); + Assert.assertTrue("tx2 see b1", storage.queryOptional(tx2, b1).isPresent()); + Assert.assertTrue("tx1 blind b1", storage.queryOptional(tx1, b1).isEmpty()); // 事务1创建的a1,事务2不可见 var a1 = storage.insert(tx1, new byte[]{0x6, 0x5, 0x4, 0x32}); - assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); - assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); + Assert.assertTrue("tx1 see a1", storage.queryOptional(tx1, a1).isPresent()); + Assert.assertTrue("tx2 blind a1", storage.queryOptional(tx2, a1).isEmpty()); // 事务2提交,则事务1依旧不可见b1,因为事务1不可见事务2 tx2.commit(); - assertTrue("tx1 blind b1 after commit", storage.queryOptional(tx1, b1).isEmpty()); + Assert.assertTrue("tx1 blind b1 after commit", storage.queryOptional(tx1, b1).isEmpty()); // 新建事务3 var tx3 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); tx3.start(); // 事务3可以看到b1,但是不能看到a1 - assertTrue("tx3 see b1 after commit", storage.queryOptional(tx3, b1).isPresent()); - assertTrue("tx3 blind a1 before commit", storage.queryOptional(tx3, a1).isEmpty()); + Assert.assertTrue("tx3 see b1 after commit", storage.queryOptional(tx3, b1).isPresent()); + Assert.assertTrue("tx3 blind a1 before commit", storage.queryOptional(tx3, a1).isEmpty()); // 提交事务1,a1还是不可见,因为事务3创建时事务1还在运行 tx1.commit(); - assertTrue("tx3 blind a1 after commit", storage.queryOptional(tx3, a1).isEmpty()); + Assert.assertTrue("tx3 blind a1 after commit", storage.queryOptional(tx3, a1).isEmpty()); // tx3.commit(); } + @Test public void testDeleteReal() throws RecordNotFoundException, IOException, FileException { var storage = RecordManager.fromFile(PATH + "testDeleteReal"); var manager = new TransactionManagerImpl(); @@ -171,11 +193,11 @@ public void testDeleteReal() throws RecordNotFoundException, IOException, FileEx tx1.start(); var a1 = storage.insert(tx1, new byte[]{0x1, 0x2, 0x3}); var a2 = storage.insert(tx1, new byte[]{0x5, 0x6, 0x7}); - assertTrue(storage.queryOptional(tx1, a1).isPresent()); - assertTrue(storage.queryOptional(tx1, a2).isPresent()); + Assert.assertTrue(storage.queryOptional(tx1, a1).isPresent()); + Assert.assertTrue(storage.queryOptional(tx1, a2).isPresent()); // 自身删除 storage.delete(tx1, a1); - assertTrue("a1 should be deleted", storage.queryOptional(tx1, a1).isEmpty()); + Assert.assertTrue("a1 should be deleted", storage.queryOptional(tx1, a1).isEmpty()); // 提交事务1 tx1.commit(); // 事务2删除,事务3仍然可见a2 @@ -184,15 +206,16 @@ public void testDeleteReal() throws RecordNotFoundException, IOException, FileEx var tx3 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); tx3.start(); storage.delete(tx2, a2); - assertTrue("tx2 blind a2 after delete", storage.queryOptional(tx2, a2).isEmpty()); - assertTrue("tx3 see a2 before commit", storage.queryOptional(tx3, a2).isPresent()); + Assert.assertTrue("tx2 blind a2 after delete", storage.queryOptional(tx2, a2).isEmpty()); + Assert.assertTrue("tx3 see a2 before commit", storage.queryOptional(tx3, a2).isPresent()); // 事务2提交,事务3依旧可见a2,因为事务3开始时事务2没有结束 tx2.commit(); - assertTrue("tx3 see a2 after commit", storage.queryOptional(tx3, a2).isPresent()); + Assert.assertTrue("tx3 see a2 after commit", storage.queryOptional(tx3, a2).isPresent()); // tx3.commit(); } + @Test public void testVersionSkipReal() throws RecordNotFoundException, NeedRollbackException, IOException, FileException { var storage = RecordManager.fromFile(PATH + "testDeleteReal"); var manager = new TransactionManagerImpl(); @@ -203,13 +226,13 @@ public void testVersionSkipReal() throws RecordNotFoundException, NeedRollbackEx tx1.start(); var tx2 = manager.createTransactionContext(TransactionIsolation.REPEATABLE_READ); tx2.start(); - assertTrue(storage.queryOptional(tx1, r).isPresent()); - assertTrue(storage.queryOptional(tx2, r).isPresent()); + Assert.assertTrue(storage.queryOptional(tx1, r).isPresent()); + Assert.assertTrue(storage.queryOptional(tx2, r).isPresent()); storage.delete(tx1, r); tx1.commit(); try { storage.delete(tx2, r); - fail("Should rollback tx2 at here"); + Assert.fail("Should rollback tx2 at here"); tx2.commit(); } catch (NeedRollbackException e) { log.info("Expected runtime exception: ", e); diff --git a/src/test/java/net/kaaass/rumbase/table/BaseFieldTest.java b/src/test/java/net/kaaass/rumbase/table/BaseFieldTest.java index 19cfde1..b9076df 100644 --- a/src/test/java/net/kaaass/rumbase/table/BaseFieldTest.java +++ b/src/test/java/net/kaaass/rumbase/table/BaseFieldTest.java @@ -2,10 +2,16 @@ import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.index.exception.IndexNotFoundException; import net.kaaass.rumbase.table.exception.TableConflictException; import net.kaaass.rumbase.table.exception.TableExistenceException; import net.kaaass.rumbase.table.field.*; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -20,37 +26,40 @@ * @see BaseField */ @Slf4j -public class BaseFieldTest extends TestCase { +public class BaseFieldTest { - private static final String PATH = "build/"; + @BeforeClass + @AfterClass + public static void clearDataFolder() { + log.info("清除数据文件夹..."); + FileUtil.removeDir(FileUtil.DATA_PATH); + } + @Test public void testCheckStr() { - var dummy = new Table(PATH + "testCheckStrTable", new ArrayList<>()); - // test int - var intField = new IntField("testCheckStrInt", false, dummy); - assertTrue(intField.checkStr("1")); - assertFalse(intField.checkStr("1.2")); - assertFalse(intField.checkStr("1aa")); + var intField = new IntField("testCheckStrInt", false, null); + Assert.assertTrue(intField.checkStr("1")); + Assert.assertFalse(intField.checkStr("1.2")); + Assert.assertFalse(intField.checkStr("1aa")); // test float - var floatField = new FloatField("testCheckStrFloat", false, dummy); - assertTrue(floatField.checkStr("1")); - assertTrue(floatField.checkStr("1.2")); - assertFalse(floatField.checkStr("1aa")); + var floatField = new FloatField("testCheckStrFloat", false, null); + Assert.assertTrue(floatField.checkStr("1")); + Assert.assertTrue(floatField.checkStr("1.2")); + Assert.assertFalse(floatField.checkStr("1aa")); // test varchar - var varcharField = new VarcharField("testCheckStrVarchar", 20, false, dummy); - assertTrue(varcharField.checkStr("'aaaa'")); - assertFalse(varcharField.checkStr("'aaaa aaaa aaaa aaaa aaaa'")); + var varcharField = new VarcharField("testCheckStrVarchar", 20, false, null); + Assert.assertTrue(varcharField.checkStr("'aaaa'")); + Assert.assertFalse(varcharField.checkStr("'aaaa aaaa aaaa aaaa aaaa'")); } + @Test public void testDeserialize() { - var dummy = new Table(PATH + "testDeserializeTable", new ArrayList<>()); - // 测试数据 var bytes = new byte[]{ 0, @@ -65,40 +74,39 @@ public void testDeserialize() { }; var inputStream = new ByteArrayInputStream(bytes); - var intField = new IntField("testDeserializeInt", false, dummy); + var intField = new IntField("testDeserializeInt", false, null); try { var intRes = (int) intField.deserialize(inputStream); - assertEquals(33, intRes); + Assert.assertEquals(33, intRes); } catch (TableConflictException e) { log.error("Exception expected: ", e); - fail("proper format should not fail to parse"); + Assert.fail("proper format should not fail to parse"); } - var floatField = new FloatField("testDeserializeFloat", false, dummy); + var floatField = new FloatField("testDeserializeFloat", false, null); try { var floatRes = (float) floatField.deserialize(inputStream); - assertEquals(1.2f, floatRes); + Assert.assertTrue(1.2f - floatRes < 0.0001); } catch (TableConflictException e) { log.error("Exception expected: ", e); - fail("proper format should not fail to parse"); + Assert.fail("proper format should not fail to parse"); } - var varcharField = new VarcharField("testDeserializeVarchar", 20, false, dummy); + var varcharField = new VarcharField("testDeserializeVarchar", 20, false, null); try { var varcharRes = (String) varcharField.deserialize(inputStream); - assertEquals("test varchar", varcharRes); + Assert.assertEquals("test varchar", varcharRes); } catch (TableConflictException e) { - fail("proper format should not fail to parse"); + Assert.fail("proper format should not fail to parse"); } - assertEquals(0, inputStream.available()); + Assert.assertEquals(0, inputStream.available()); } + @Test public void testCheckInputStream() { - var dummy = new Table(PATH + "testCheckInputStreamTable", new ArrayList<>()); - // 测试数据 var bytes = new byte[]{ 0, @@ -113,23 +121,23 @@ public void testCheckInputStream() { }; var inputStream = new ByteArrayInputStream(bytes); - var intField = new IntField("testCheckInputStreamInt", false, dummy); - assertTrue(intField.checkInputStream(inputStream)); + var intField = new IntField("testCheckInputStreamInt", false, null); + Assert.assertTrue(intField.checkInputStream(inputStream)); - var floatField = new FloatField("testCheckInputStreamFloat", false, dummy); - assertTrue(floatField.checkInputStream(inputStream)); + var floatField = new FloatField("testCheckInputStreamFloat", false, null); + Assert.assertTrue(floatField.checkInputStream(inputStream)); - var varcharField = new VarcharField("testCheckInputStreamVarchar", 20, false, dummy); - assertTrue(varcharField.checkInputStream(inputStream)); + var varcharField = new VarcharField("testCheckInputStreamVarchar", 20, false, null); + Assert.assertTrue(varcharField.checkInputStream(inputStream)); - assertEquals(0, inputStream.available()); + Assert.assertEquals(0, inputStream.available()); } + @Test public void testSerialize() { - var dummy = new Table(PATH + "testSerialize", new ArrayList<>()); - var intField = new IntField("testSerializeInt", false, dummy); + var intField = new IntField("testSerializeInt", false, null); var intBos1 = new ByteArrayOutputStream(); var intBos2 = new ByteArrayOutputStream(); @@ -142,17 +150,17 @@ public void testSerialize() { assertArrayEquals(expected, intBos1.toByteArray()); } catch (TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { intField.serialize(intBos2, "'xx'"); - fail(); + Assert.fail(); } catch (TableConflictException e) { log.error("Exception expected: ", e); } - var floatField = new FloatField("testSerializeFloat", false, dummy); + var floatField = new FloatField("testSerializeFloat", false, null); var floatBos1 = new ByteArrayOutputStream(); var floatBos2 = new ByteArrayOutputStream(); @@ -165,17 +173,17 @@ public void testSerialize() { assertArrayEquals(expected, floatBos1.toByteArray()); } catch (TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { floatField.serialize(floatBos2, "'xx'"); - fail(); + Assert.fail(); } catch (TableConflictException e) { log.error("Exception expected: ", e); } - var varcharField = new VarcharField("testSerializeVarchar", 20, false, dummy); + var varcharField = new VarcharField("testSerializeVarchar", 20, false, null); var varcharBos1 = new ByteArrayOutputStream(); var varcharBos2 = new ByteArrayOutputStream(); @@ -191,45 +199,47 @@ public void testSerialize() { assertArrayEquals(expected, varcharBos1.toByteArray()); } catch (TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { varcharField.serialize(varcharBos2, "'test varchar too looooooooooooong'"); - fail(); + Assert.fail(); } catch (TableConflictException e) { log.error("Exception expected: ", e); } } + @Test public void testDoubleCreateIndex() { - var dummy = new Table(PATH + "testCreateIndexTable", new ArrayList<>()); + var dummy = new Table("testCreateIndexTable", new ArrayList<>()); BaseField field = new IntField("testCreateIndexField", false, dummy); try { field.createIndex(); } catch (IndexAlreadyExistException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { field.createIndex(); - fail(); + Assert.fail(); } catch (IndexAlreadyExistException e) { log.error("Exception expected: ", e); } } + @Test public void testInsertIndex() { - var dummy = new Table(PATH + "testInsertIndexTable", new ArrayList<>()); + var dummy = new Table("testInsertIndexTable", new ArrayList<>()); var intField = new IntField("testInsertIndexInt", false, dummy); try { intField.insertIndex("1", 1); - fail(); + Assert.fail(); } catch (TableExistenceException | TableConflictException e) { log.error("Exception expected: ", e); } @@ -238,7 +248,7 @@ public void testInsertIndex() { intField.createIndex(); } catch (IndexAlreadyExistException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { @@ -246,12 +256,12 @@ public void testInsertIndex() { intField.insertIndex("1", 1); } catch (TableExistenceException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { intField.insertIndex("1xx", 1); - fail(); + Assert.fail(); } catch (TableExistenceException | TableConflictException e) { log.error("Exception expected: ", e); } @@ -260,7 +270,7 @@ public void testInsertIndex() { try { floatField.insertIndex("1.2", 1); - fail(); + Assert.fail(); } catch (TableExistenceException | TableConflictException e) { log.error("Exception expected: ", e); } @@ -269,7 +279,7 @@ public void testInsertIndex() { floatField.createIndex(); } catch (IndexAlreadyExistException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { @@ -277,12 +287,12 @@ public void testInsertIndex() { floatField.insertIndex("1.2", 1); } catch (TableExistenceException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { floatField.insertIndex("1xx", 1); - fail(); + Assert.fail(); } catch (TableExistenceException | TableConflictException e) { log.error("Exception expected: ", e); } @@ -292,7 +302,7 @@ public void testInsertIndex() { try { varcharField.insertIndex("xxx", 1); - fail(); + Assert.fail(); } catch (TableExistenceException e) { log.error("Exception expected: ", e); } @@ -301,7 +311,7 @@ public void testInsertIndex() { varcharField.createIndex(); } catch (IndexAlreadyExistException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { @@ -309,13 +319,15 @@ public void testInsertIndex() { varcharField.insertIndex("xxx", 1); } catch (TableExistenceException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } } + @Test public void testQueryIndex() { - var dummy = new Table(PATH + "testQueryIndexTable", new ArrayList<>()); + + var dummy = new Table("testQueryIndexTable", new ArrayList<>()); var intField = new IntField("testQueryIndexInt", false, dummy); @@ -323,7 +335,7 @@ public void testQueryIndex() { intField.createIndex(); } catch (IndexAlreadyExistException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { @@ -331,24 +343,24 @@ public void testQueryIndex() { intField.insertIndex("1", 1); } catch (TableExistenceException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { var uuid = intField.queryIndex("1"); - assertEquals(1, uuid.get(0).longValue()); - assertEquals(1, uuid.get(1).longValue()); + Assert.assertEquals(1, uuid.get(0).longValue()); + Assert.assertEquals(1, uuid.get(1).longValue()); } catch (TableExistenceException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { var uuid = intField.queryIndex("2"); - assertTrue(uuid.isEmpty()); + Assert.assertTrue(uuid.isEmpty()); } catch (TableExistenceException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } var floatField = new FloatField("testQueryIndexFloat", false, dummy); @@ -357,7 +369,7 @@ public void testQueryIndex() { floatField.createIndex(); } catch (IndexAlreadyExistException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { @@ -365,24 +377,24 @@ public void testQueryIndex() { floatField.insertIndex("1.2", 1); } catch (TableExistenceException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { var uuid = floatField.queryIndex("1.2"); - assertEquals(1, uuid.get(0).longValue()); - assertEquals(1, uuid.get(1).longValue()); + Assert.assertEquals(1, uuid.get(0).longValue()); + Assert.assertEquals(1, uuid.get(1).longValue()); } catch (TableExistenceException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { var uuid = floatField.queryIndex("2.2"); - assertTrue(uuid.isEmpty()); + Assert.assertTrue(uuid.isEmpty()); } catch (TableExistenceException | TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } var varcharField = new VarcharField("testQueryIndexVarchar", 20, false, dummy); @@ -391,7 +403,7 @@ public void testQueryIndex() { varcharField.createIndex(); } catch (IndexAlreadyExistException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { @@ -399,28 +411,29 @@ public void testQueryIndex() { varcharField.insertIndex("xxx", 1); } catch (TableExistenceException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { var uuid = varcharField.queryIndex("xxx"); - assertEquals(1, uuid.get(0).longValue()); - assertEquals(1, uuid.get(1).longValue()); + Assert.assertEquals(1, uuid.get(0).longValue()); + Assert.assertEquals(1, uuid.get(1).longValue()); } catch (TableExistenceException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { var uuid = varcharField.queryIndex("x"); - assertTrue(uuid.isEmpty()); + Assert.assertTrue(uuid.isEmpty()); } catch (TableExistenceException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } } - public void testLoad() { + @Test + public void testLoad() throws IndexNotFoundException { var bytes = new byte[]{ // testLoadInt 11, @@ -466,33 +479,32 @@ public void testLoad() { }; var stream = new ByteArrayInputStream(bytes); - var dummy = new Table(PATH + "testLoadTable", new ArrayList<>()); - var intField = BaseField.load(stream, dummy); - assertNotNull(intField); - assertEquals("testLoadInt", intField.getName()); - assertEquals(FieldType.INT, intField.getType()); + var intField = BaseField.load(stream, null); + Assert.assertNotNull(intField); + Assert.assertEquals("testLoadInt", intField.getName()); + Assert.assertEquals(FieldType.INT, intField.getType()); - var floatField = BaseField.load(stream, dummy); - assertNotNull(floatField); - assertEquals("testLoadFloat", floatField.getName()); - assertEquals(FieldType.FLOAT, floatField.getType()); + var floatField = BaseField.load(stream, null); + Assert.assertNotNull(floatField); + Assert.assertEquals("testLoadFloat", floatField.getName()); + Assert.assertEquals(FieldType.FLOAT, floatField.getType()); - var varcharField = BaseField.load(stream, dummy); - assertNotNull(varcharField); - assertEquals("testLoadVarchar", varcharField.getName()); - assertEquals(FieldType.VARCHAR, varcharField.getType()); - assertEquals(12, ((VarcharField) varcharField).getLimit()); + var varcharField = BaseField.load(stream, null); + Assert.assertNotNull(varcharField); + Assert.assertEquals("testLoadVarchar", varcharField.getName()); + Assert.assertEquals(FieldType.VARCHAR, varcharField.getType()); + Assert.assertEquals(12, ((VarcharField) varcharField).getLimit()); } + @Test public void testPersist() { - var dummy = new Table(PATH + "testLoadTable", new ArrayList<>()); var out = new ByteArrayOutputStream(); - var intField = new IntField("testPersistInt", false, dummy); - var floatField = new FloatField("testPersistFloat", false, dummy); - var varcharField = new VarcharField("testPersistVarchar", 12, false, dummy); + var intField = new IntField("testPersistInt", false, null); + var floatField = new FloatField("testPersistFloat", false, null); + var varcharField = new VarcharField("testPersistVarchar", 12, false, null); intField.persist(out); floatField.persist(out); diff --git a/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java b/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java index 3031a0b..9128abf 100644 --- a/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java +++ b/src/test/java/net/kaaass/rumbase/table/TableManagerTest.java @@ -1,13 +1,21 @@ package net.kaaass.rumbase.table; -import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; +import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.query.exception.ArgumentException; +import net.kaaass.rumbase.record.exception.RecordNotFoundException; +import net.kaaass.rumbase.table.exception.TableConflictException; import net.kaaass.rumbase.table.exception.TableExistenceException; import net.kaaass.rumbase.table.field.BaseField; import net.kaaass.rumbase.table.field.FloatField; import net.kaaass.rumbase.table.field.IntField; import net.kaaass.rumbase.table.field.VarcharField; import net.kaaass.rumbase.transaction.TransactionContext; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.File; import java.util.ArrayList; @@ -19,106 +27,104 @@ * @see net.kaaass.rumbase.table.TableManager */ @Slf4j -public class TableManagerTest extends TestCase { +public class TableManagerTest { - private static final String PATH = "build/"; + @BeforeClass + @AfterClass + public static void clearDataFolder() { + log.info("清除数据文件夹..."); + FileUtil.removeDir(new File(FileUtil.DATA_PATH)); + } - public void testShowTables() { - var prefix = PATH + "testShowTables"; + @Test + public void testShowTables() throws IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { + var prefix = "testShowTables"; var tbm = new TableManager(); var fieldList = new ArrayList(); - var table = new Table(prefix + "Table", fieldList); // 增加测试表字段 - var intField = new IntField(prefix + "age", false, table); - var floatField = new FloatField(prefix + "balance", false, table); - var varcharField = new VarcharField(prefix + "name", 20, false, table); + var intField = new IntField(prefix + "age", false, null); + var floatField = new FloatField(prefix + "balance", false, null); + var varcharField = new VarcharField(prefix + "name", 20, false, null); fieldList.add(intField); fieldList.add(floatField); fieldList.add(varcharField); try { - tbm.createTable(TransactionContext.empty(), prefix + "Table", fieldList, prefix + ".db"); - } catch (TableExistenceException e) { + tbm.createTable(TransactionContext.empty(), prefix + "Table", fieldList, FileUtil.TABLE_PATH + prefix + ".db"); + } catch (TableExistenceException | RecordNotFoundException | ArgumentException | TableConflictException e) { e.printStackTrace(); - fail(); + Assert.fail(); } var tables = tbm.showTables(); - assertEquals(1, tables.size()); - assertEquals(prefix + "Table", tables.get(0)); - - new File("metadata.db").deleteOnExit(); + Assert.assertEquals(4, tables.size()); + Assert.assertEquals(prefix + "Table", tables.get(0)); } - public void testCreateTable() { - var prefix = PATH + "testCreateTable"; + @Test + public void testCreateTable() throws IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { + var prefix = "testCreateTable"; var tbm = new TableManager(); var fieldList = new ArrayList(); - var table = new Table(prefix + "Table", fieldList); // 增加测试表字段 - var intField = new IntField(prefix + "age", false, table); - var floatField = new FloatField(prefix + "balance", false, table); - var varcharField = new VarcharField(prefix + "name", 20, false, table); + var intField = new IntField(prefix + "age", false, null); + var floatField = new FloatField(prefix + "balance", false, null); + var varcharField = new VarcharField(prefix + "name", 20, false, null); fieldList.add(intField); fieldList.add(floatField); fieldList.add(varcharField); try { - tbm.createTable(TransactionContext.empty(), prefix + "Table", fieldList, prefix + ".db"); + tbm.createTable(TransactionContext.empty(), prefix + "Table", fieldList, FileUtil.TABLE_PATH + prefix + ".db"); } catch (TableExistenceException e) { e.printStackTrace(); - fail(); + Assert.fail(); } - - new File("metadata.db").deleteOnExit(); - } - public void testGetTable() { - var prefix = PATH + "testGetTable"; + @Test + public void testGetTable() throws IndexAlreadyExistException, TableExistenceException, TableConflictException, RecordNotFoundException, ArgumentException { + var prefix = "testGetTable"; var tbm = new TableManager(); var fieldList = new ArrayList(); - var table = new Table(prefix + "Table", fieldList); // 增加测试表字段 - var intField = new IntField(prefix + "age", false, table); - var floatField = new FloatField(prefix + "balance", false, table); - var varcharField = new VarcharField(prefix + "name", 20, false, table); + var intField = new IntField(prefix + "age", false, null); + var floatField = new FloatField(prefix + "balance", false, null); + var varcharField = new VarcharField(prefix + "name", 20, false, null); fieldList.add(intField); fieldList.add(floatField); fieldList.add(varcharField); try { - tbm.createTable(TransactionContext.empty(), prefix + "Table", fieldList, prefix + ".db"); + tbm.createTable(TransactionContext.empty(), prefix + "Table", fieldList, FileUtil.TABLE_PATH + prefix + ".db"); } catch (TableExistenceException e) { e.printStackTrace(); - fail(); + Assert.fail(); } try { var t = tbm.getTable(prefix + "Table"); - assertEquals(prefix + "Table", t.tableName); - assertEquals(intField.getName(), t.fields.get(0).getName()); - assertEquals(intField.getType(), t.fields.get(0).getType()); - assertEquals(floatField.getName(), t.fields.get(1).getName()); - assertEquals(floatField.getType(), t.fields.get(1).getType()); - assertEquals(varcharField.getName(), t.fields.get(2).getName()); - assertEquals(varcharField.getType(), t.fields.get(2).getType()); - assertEquals(varcharField.getLimit(), ((VarcharField) t.fields.get(2)).getLimit()); + Assert.assertEquals(prefix + "Table", t.tableName); + Assert.assertEquals(intField.getName(), t.fields.get(0).getName()); + Assert.assertEquals(intField.getType(), t.fields.get(0).getType()); + Assert.assertEquals(floatField.getName(), t.fields.get(1).getName()); + Assert.assertEquals(floatField.getType(), t.fields.get(1).getType()); + Assert.assertEquals(varcharField.getName(), t.fields.get(2).getName()); + Assert.assertEquals(varcharField.getType(), t.fields.get(2).getType()); + Assert.assertEquals(varcharField.getLimit(), ((VarcharField) t.fields.get(2)).getLimit()); } catch (TableExistenceException e) { - fail(); + Assert.fail(); } - - new File("metadata.db").deleteOnExit(); } } diff --git a/src/test/java/net/kaaass/rumbase/table/TableTest.java b/src/test/java/net/kaaass/rumbase/table/TableTest.java index 575d799..100edf8 100644 --- a/src/test/java/net/kaaass/rumbase/table/TableTest.java +++ b/src/test/java/net/kaaass/rumbase/table/TableTest.java @@ -4,7 +4,9 @@ import com.igormaznitsa.jbbp.io.JBBPByteOrder; import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.index.exception.IndexAlreadyExistException; +import net.kaaass.rumbase.index.exception.IndexNotFoundException; import net.kaaass.rumbase.query.exception.ArgumentException; import net.kaaass.rumbase.record.RecordManager; import net.kaaass.rumbase.record.exception.RecordNotFoundException; @@ -12,8 +14,13 @@ import net.kaaass.rumbase.table.exception.TableExistenceException; import net.kaaass.rumbase.table.field.*; import net.kaaass.rumbase.transaction.TransactionContext; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -27,12 +34,25 @@ * @see Table */ @Slf4j -public class TableTest extends TestCase { +public class TableTest { - private static final String PATH = "build/"; + @BeforeClass + public static void createDataFolder() { + log.info("创建测试文件夹..."); + FileUtil.createDir(FileUtil.TEST_PATH); + FileUtil.removeDir(FileUtil.DATA_PATH); + } + + @AfterClass + public static void clearDataFolder() { + log.info("清除测试文件夹..."); + FileUtil.removeDir(FileUtil.TEST_PATH); + FileUtil.removeDir(FileUtil.DATA_PATH); + } + @Test public void testLoad() { - var prefix = PATH + "testLoad"; + var prefix = "testLoad"; var byteOS = new ByteArrayOutputStream(); var out = new JBBPBitOutputStream(byteOS); @@ -53,29 +73,30 @@ public void testLoad() { out.writeInt(12, JBBPByteOrder.BIG_ENDIAN); } catch (IOException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } - var storage = RecordManager.fromFile(prefix + "Table"); + var storage = RecordManager.fromFile(FileUtil.TEST_PATH + prefix + "Table"); storage.setMetadata(TransactionContext.empty(), byteOS.toByteArray()); - var table = Table.load(RecordManager.fromFile(prefix + "Table")); + var table = Table.load(RecordManager.fromFile(FileUtil.TEST_PATH + prefix + "Table")); - assertNotNull(table); - assertEquals("testLoadTable", table.getTableName()); - assertEquals(-1L, table.getNext()); - assertEquals(TableStatus.NORMAL, table.getStatus()); + Assert.assertNotNull(table); + Assert.assertEquals("testLoadTable", table.getTableName()); + Assert.assertEquals(-1L, table.getNext()); + Assert.assertEquals(TableStatus.NORMAL, table.getStatus()); var fields = table.getFields(); - assertEquals("testLoadInt", fields.get(0).getName()); - assertEquals(FieldType.INT, fields.get(0).getType()); - assertEquals("testLoadFloat", fields.get(1).getName()); - assertEquals(FieldType.FLOAT, fields.get(1).getType()); - assertEquals("testLoadVarchar", fields.get(2).getName()); - assertEquals(FieldType.VARCHAR, fields.get(2).getType()); - assertEquals(12, ((VarcharField) fields.get(2)).getLimit()); + Assert.assertEquals("testLoadInt", fields.get(0).getName()); + Assert.assertEquals(FieldType.INT, fields.get(0).getType()); + Assert.assertEquals("testLoadFloat", fields.get(1).getName()); + Assert.assertEquals(FieldType.FLOAT, fields.get(1).getType()); + Assert.assertEquals("testLoadVarchar", fields.get(2).getName()); + Assert.assertEquals(FieldType.VARCHAR, fields.get(2).getType()); + Assert.assertEquals(12, ((VarcharField) fields.get(2)).getLimit()); } + @Test public void testPersist() { var fieldList = new ArrayList(); @@ -105,7 +126,7 @@ public void testPersist() { out.writeInt(12, JBBPByteOrder.BIG_ENDIAN); } catch (IOException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } assertArrayEquals(new byte[0], table.getRecordStorage().getMetadata(context)); @@ -116,7 +137,7 @@ public void testPersist() { Table createTestTable(String prefix) { var fieldList = new ArrayList(); - var table = new Table(PATH + prefix + "Table", fieldList); + var table = new Table(prefix + "Table", fieldList); // 增加测试表字段 var intField = new IntField(prefix + "age", false, table); @@ -132,12 +153,13 @@ Table createTestTable(String prefix) { floatField.createIndex(); } catch (IndexAlreadyExistException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } return table; } + @Test public void testCURD() { var prefix = "testCURD"; @@ -163,73 +185,73 @@ public void testCURD() { table.insert(context, data2); } catch (TableConflictException | TableExistenceException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { // 查询记录,测试插入情况与查询情况 var iter = table.searchFirst(prefix + "age", "0"); - assertTrue(iter.hasNext()); + Assert.assertTrue(iter.hasNext()); var pair1 = iter.next(); - assertTrue(iter.hasNext()); + Assert.assertTrue(iter.hasNext()); var pair2 = iter.next(); - assertFalse(iter.hasNext()); - assertEquals(1L, pair1.getKey()); - assertEquals(33L, pair2.getKey()); + Assert.assertFalse(iter.hasNext()); + Assert.assertEquals(1L, pair1.getKey()); + Assert.assertEquals(33L, pair2.getKey()); var res1 = table.read(context, pair1.getUuid()); var res2 = table.read(context, pair2.getUuid()); - assertTrue(res1.isPresent()); - assertTrue(res2.isPresent()); + Assert.assertTrue(res1.isPresent()); + Assert.assertTrue(res2.isPresent()); - assertEquals(1, (int) res1.get().get(0)); - assertEquals(-0.4f, res1.get().get(1)); - assertEquals("ya test varchar", (String) res1.get().get(2)); + Assert.assertEquals(1, (int) res1.get().get(0)); + Assert.assertEquals(-0.4f, res1.get().get(1)); + Assert.assertEquals("ya test varchar", (String) res1.get().get(2)); - assertEquals(33, (int) res2.get().get(0)); - assertEquals(1.2f, res2.get().get(1)); - assertEquals("test varchar", (String) res2.get().get(2)); + Assert.assertEquals(33, (int) res2.get().get(0)); + Assert.assertEquals(1.2f, res2.get().get(1)); + Assert.assertEquals("test varchar", (String) res2.get().get(2)); // 测试删除记录 table.delete(context, pair2.getUuid()); var iter2 = table.searchFirst(prefix + "age", "33"); - assertTrue(iter2.hasNext()); + Assert.assertTrue(iter2.hasNext()); var pair3 = iter2.next(); - assertEquals(33L, pair3.getKey()); + Assert.assertEquals(33L, pair3.getKey()); var res3 = table.read(context, pair3.getUuid()); - assertTrue(res3.isEmpty()); // 记录不存在 + Assert.assertTrue(res3.isEmpty()); // 记录不存在 // 测试更新记录 table.update(context, pair1.getUuid(), data); var iter3 = table.searchFirst(prefix + "age", "33"); - assertTrue(iter3.hasNext()); + Assert.assertTrue(iter3.hasNext()); var pair4 = iter3.next(); - assertTrue(iter3.hasNext()); + Assert.assertTrue(iter3.hasNext()); var pair5 = iter3.next(); // 有效的被更新记录 - assertEquals(33L, pair4.getKey()); + Assert.assertEquals(33L, pair4.getKey()); var res4 = table.read(context, pair4.getUuid()); - assertTrue(res4.isPresent()); + Assert.assertTrue(res4.isPresent()); // 四记录 - assertEquals(33L, pair5.getKey()); + Assert.assertEquals(33L, pair5.getKey()); var res5 = table.read(context, pair5.getUuid()); - assertTrue(res5.isEmpty()); + Assert.assertTrue(res5.isEmpty()); // 测试记录是否被更新 - assertEquals(33, (int) res4.get().get(0)); - assertEquals(1.2f, res4.get().get(1)); - assertEquals("test varchar", (String) res4.get().get(2)); + Assert.assertEquals(33, (int) res4.get().get(0)); + Assert.assertEquals(1.2f, res4.get().get(1)); + Assert.assertEquals("test varchar", (String) res4.get().get(2)); } catch (TableExistenceException | TableConflictException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } } @@ -287,6 +309,7 @@ void addTestData(TransactionContext context, Table table) throws TableConflictEx }}); } + @Test public void testSearch() { var prefix = "testSearch"; @@ -300,7 +323,7 @@ public void testSearch() { addTestData(context, table); } catch (TableConflictException | TableExistenceException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 查询记录 @@ -312,21 +335,22 @@ public void testSearch() { var res = table.read(context, uuid); res.ifPresent(resList::add); } - assertEquals(7, (int) resList.get(0).get(0)); - assertEquals(1.2f, resList.get(0).get(1)); - assertEquals("test varchar", (String) resList.get(0).get(2)); + Assert.assertEquals(7, (int) resList.get(0).get(0)); + Assert.assertEquals(1.2f, resList.get(0).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(0).get(2)); - assertEquals(7, (int) resList.get(1).get(0)); - assertEquals(1.2f, resList.get(1).get(1)); - assertEquals("test varchar", (String) resList.get(1).get(2)); + Assert.assertEquals(7, (int) resList.get(1).get(0)); + Assert.assertEquals(1.2f, resList.get(1).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(1).get(2)); } catch (TableExistenceException | TableConflictException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } } + @Test public void testSearchAll() { var prefix = "testSearchAll"; @@ -340,7 +364,7 @@ public void testSearchAll() { addTestData(context, table); } catch (TableConflictException | TableExistenceException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } @@ -353,53 +377,54 @@ public void testSearchAll() { table.read(context, uuid.getUuid()).ifPresent(resList::add); } - assertEquals(1, (int) resList.get(0).get(0)); - assertEquals(1.2f, resList.get(0).get(1)); - assertEquals("test varchar", (String) resList.get(0).get(2)); + Assert.assertEquals(1, (int) resList.get(0).get(0)); + Assert.assertEquals(1.2f, resList.get(0).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(0).get(2)); - assertEquals(2, (int) resList.get(1).get(0)); - assertEquals(1.2f, resList.get(1).get(1)); - assertEquals("test varchar", (String) resList.get(1).get(2)); + Assert.assertEquals(2, (int) resList.get(1).get(0)); + Assert.assertEquals(1.2f, resList.get(1).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(1).get(2)); - assertEquals(3, (int) resList.get(2).get(0)); - assertEquals(1.2f, resList.get(2).get(1)); - assertEquals("test varchar", (String) resList.get(2).get(2)); + Assert.assertEquals(3, (int) resList.get(2).get(0)); + Assert.assertEquals(1.2f, resList.get(2).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(2).get(2)); - assertEquals(3, (int) resList.get(3).get(0)); - assertEquals(1.2f, resList.get(3).get(1)); - assertEquals("test varchar", (String) resList.get(3).get(2)); + Assert.assertEquals(3, (int) resList.get(3).get(0)); + Assert.assertEquals(1.2f, resList.get(3).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(3).get(2)); - assertEquals(4, (int) resList.get(4).get(0)); - assertEquals(1.2f, resList.get(4).get(1)); - assertEquals("test varchar", (String) resList.get(4).get(2)); + Assert.assertEquals(4, (int) resList.get(4).get(0)); + Assert.assertEquals(1.2f, resList.get(4).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(4).get(2)); - assertEquals(5, (int) resList.get(5).get(0)); - assertEquals(1.2f, resList.get(5).get(1)); - assertEquals("test varchar", (String) resList.get(5).get(2)); + Assert.assertEquals(5, (int) resList.get(5).get(0)); + Assert.assertEquals(1.2f, resList.get(5).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(5).get(2)); - assertEquals(6, (int) resList.get(6).get(0)); - assertEquals(1.2f, resList.get(6).get(1)); - assertEquals("test varchar", (String) resList.get(6).get(2)); + Assert.assertEquals(6, (int) resList.get(6).get(0)); + Assert.assertEquals(1.2f, resList.get(6).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(6).get(2)); - assertEquals(7, (int) resList.get(7).get(0)); - assertEquals(1.2f, resList.get(7).get(1)); - assertEquals("test varchar", (String) resList.get(7).get(2)); + Assert.assertEquals(7, (int) resList.get(7).get(0)); + Assert.assertEquals(1.2f, resList.get(7).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(7).get(2)); - assertEquals(7, (int) resList.get(8).get(0)); - assertEquals(1.2f, resList.get(8).get(1)); - assertEquals("test varchar", (String) resList.get(8).get(2)); + Assert.assertEquals(7, (int) resList.get(8).get(0)); + Assert.assertEquals(1.2f, resList.get(8).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(8).get(2)); - assertEquals(8, (int) resList.get(9).get(0)); - assertEquals(1.2f, resList.get(9).get(1)); - assertEquals("test varchar", (String) resList.get(9).get(2)); + Assert.assertEquals(8, (int) resList.get(9).get(0)); + Assert.assertEquals(1.2f, resList.get(9).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(9).get(2)); } catch (TableExistenceException | TableConflictException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } } + @Test public void testSearchFirst() { var prefix = "testSearchFirst"; @@ -412,7 +437,7 @@ public void testSearchFirst() { addTestData(context, table); } catch (TableConflictException | TableExistenceException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 测试searchFirst try { @@ -423,24 +448,25 @@ public void testSearchFirst() { table.read(context, uuid.getUuid()).ifPresent(resList::add); } - assertEquals(3, (int) resList.get(0).get(0)); - assertEquals(1.2f, resList.get(0).get(1)); - assertEquals("test varchar", (String) resList.get(0).get(2)); + Assert.assertEquals(3, (int) resList.get(0).get(0)); + Assert.assertEquals(1.2f, resList.get(0).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(0).get(2)); - assertEquals(3, (int) resList.get(1).get(0)); - assertEquals(1.2f, resList.get(1).get(1)); - assertEquals("test varchar", (String) resList.get(1).get(2)); + Assert.assertEquals(3, (int) resList.get(1).get(0)); + Assert.assertEquals(1.2f, resList.get(1).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(1).get(2)); - assertEquals(4, (int) resList.get(2).get(0)); - assertEquals(1.2f, resList.get(2).get(1)); - assertEquals("test varchar", (String) resList.get(2).get(2)); + Assert.assertEquals(4, (int) resList.get(2).get(0)); + Assert.assertEquals(1.2f, resList.get(2).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(2).get(2)); } catch (TableExistenceException | TableConflictException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } } + @Test public void testSearchFirstNotEqual() { var prefix = "testSearchFirstNotEqual"; @@ -453,7 +479,7 @@ public void testSearchFirstNotEqual() { addTestData(context, table); } catch (TableConflictException | TableExistenceException | ArgumentException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } // 测试searchFirstNotEqual @@ -465,16 +491,17 @@ public void testSearchFirstNotEqual() { table.read(context, uuid.getUuid()).ifPresent(resList::add); } - assertEquals(4, (int) resList.get(0).get(0)); - assertEquals(1.2f, resList.get(0).get(1)); - assertEquals("test varchar", (String) resList.get(0).get(2)); + Assert.assertEquals(4, (int) resList.get(0).get(0)); + Assert.assertEquals(1.2f, resList.get(0).get(1)); + Assert.assertEquals("test varchar", (String) resList.get(0).get(2)); } catch (TableExistenceException | TableConflictException | RecordNotFoundException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } } + @Test public void testCheckStringEntry() { var passEntry = new ArrayList(); passEntry.add("33"); @@ -495,11 +522,12 @@ public void testCheckStringEntry() { fieldList.add(floatField); fieldList.add(varcharField); - assertTrue(table.checkStringEntry(passEntry)); - assertFalse(table.checkStringEntry(failEntry)); + Assert.assertTrue(table.checkStringEntry(passEntry)); + Assert.assertFalse(table.checkStringEntry(failEntry)); } + @Test public void testStringEntryToBytes() { var passEntry = new ArrayList(); passEntry.add("33"); @@ -536,17 +564,18 @@ public void testStringEntryToBytes() { assertArrayEquals(expected, bytes); } catch (TableConflictException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { table.stringEntryToBytes(failEntry); - fail(""); + Assert.fail(""); } catch (TableConflictException e) { log.error("Exception expected: ", e); } } + @Test public void testParseEntry() { var passEntry = new byte[]{ 0, @@ -583,17 +612,17 @@ public void testParseEntry() { try { var list = table.parseEntry(passEntry); - assertEquals(33, (int) list.get(0)); - assertEquals(1.2f, list.get(1)); - assertEquals("test varchar", (String) list.get(2)); + Assert.assertEquals(33, (int) list.get(0)); + Assert.assertEquals(1.2f, list.get(1)); + Assert.assertEquals("test varchar", (String) list.get(2)); } catch (TableConflictException | IOException e) { log.error("Exception expected: ", e); - fail(); + Assert.fail(); } try { table.parseEntry(failEntry); - fail(); + Assert.fail(); } catch (TableConflictException | IOException e) { log.error("Exception expected: ", e); } diff --git a/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java b/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java index 9b97c6d..fa4fa23 100644 --- a/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java +++ b/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java @@ -1,7 +1,7 @@ package net.kaaass.rumbase.transaction; -import junit.framework.TestCase; import lombok.extern.slf4j.Slf4j; +import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.transaction.exception.DeadlockException; import org.junit.AfterClass; @@ -21,33 +21,16 @@ @Slf4j public class TransactionContextTest { - public static void removeDir(File dir) { - File[] files = dir.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isDirectory()) { - removeDir(file); - } else { - file.delete(); - } - } - } - - dir.delete(); + @BeforeClass + public static void createDataFolder() { + log.info("创建测试文件夹..."); + FileUtil.createDir(FileUtil.TEST_PATH); } - /** - * 创建临时文件生成目录 - */ - @BeforeClass - public static void createTmpDir() { - File dir = new File("test_gen_files"); - if (!dir.exists()) { - dir.mkdir(); - } else { - removeDir(dir); - dir.mkdir(); - } + @AfterClass + public static void clearDataFolder() { + log.info("清除测试文件夹..."); + FileUtil.removeDir(FileUtil.TEST_PATH); } /** @@ -75,7 +58,6 @@ public void testCreateTransaction() throws IOException, FileException { */ @Test public void testChangeStatus() throws IOException, FileException { - // TODO 将Mock类改成实现类 var manager = new TransactionManagerImpl("test_gen_files/test_change.log"); var committedTransaction = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); // 事务初始状态 @@ -101,7 +83,6 @@ public void testChangeStatus() throws IOException, FileException { */ @Test public void testTransactionPersistence() throws IOException, FileException { - // TODO 将Mock类改成实现类 var manager = new TransactionManagerImpl("test_gen_files/test_persistence.log"); // 事务创建,事务状态记录数改变 var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); @@ -156,7 +137,6 @@ public void testTransactionRecovery() throws IOException, FileException { */ @Test public void testAddLock() throws IOException, FileException { - // TODO 将Mock类改成实现类 var manager = new TransactionManagerImpl("test_gen_files/test_add_lock.log"); var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); var transaction2 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); From 2e72e1aa3a4660c8becb8451f0e66ea09d53a0f2 Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Sun, 17 Jan 2021 15:06:20 +0800 Subject: [PATCH 17/18] =?UTF-8?q?[M]=E4=BF=AE=E6=AD=A3=E4=BA=8B=E5=8A=A1?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E6=AD=BB=E9=94=81=E5=86=B3=E6=96=AD=E5=99=A8?= =?UTF-8?q?=E7=9A=84=E5=B9=B6=E5=8F=91=E9=97=AE=E9=A2=98=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 增加状态判断 * 完善测试用例,修正死锁测试 * 添加状态异常 * 修正事务测试 * 由于签名变更调整 * 增加对超级事务的特判 * 修正事务测试 * 修正死锁测试可能的延迟问题 * 为释放锁添加同步 * 增加若干调试输出 * 解决死锁监测的并发问题 * 使用并发度较低的方法解决了死锁问题 * 增加三线程死锁测试 * 测试结束时删除xid.log * 暂时屏蔽3线程死锁 * 更改单元测试日志文件的位置 * 修正签名问题 Co-authored-by: Criterionist <1229089076@qq.com> --- .gitignore | 2 + .../rumbase/record/MvccRecordStorage.java | 5 + .../exception/RecordNotFoundException.java | 2 + .../net/kaaass/rumbase/server/Session.java | 29 ++++- .../kaaass/rumbase/table/TableManager.java | 5 +- .../transaction/TransactionContext.java | 11 +- .../transaction/TransactionContextImpl.java | 109 +++++++++++++----- .../exception/StatusException.java | 31 +++++ .../rumbase/transaction/lock/Graph.java | 7 ++ .../transaction/lock/LockTableImpl.java | 70 ++++++----- .../transaction/lock/LockTableManager.java | 1 + .../rumbase/transaction/lock/TxItem.java | 9 ++ .../rumbase/transaction/lock/TxList.java | 7 ++ .../rumbase/record/MvccReadCommitTest.java | 11 +- .../record/MvccReadRepeatableTest.java | 15 +-- .../transaction/TransactionContextTest.java | 32 +++-- 16 files changed, 254 insertions(+), 92 deletions(-) create mode 100644 src/main/java/net/kaaass/rumbase/transaction/exception/StatusException.java diff --git a/.gitignore b/.gitignore index c66cfe5..fe93c2a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ testFile test_gen_files +xid.log +metadata.db # IntelliJ IDEA .idea diff --git a/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java b/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java index 29a6a0a..6f3ebc7 100644 --- a/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java +++ b/src/main/java/net/kaaass/rumbase/record/MvccRecordStorage.java @@ -14,6 +14,7 @@ import net.kaaass.rumbase.transaction.TransactionIsolation; import net.kaaass.rumbase.transaction.TransactionStatus; import net.kaaass.rumbase.transaction.exception.DeadlockException; +import net.kaaass.rumbase.transaction.exception.StatusException; import java.util.Optional; @@ -58,6 +59,8 @@ public Optional queryOptional(TransactionContext txContext, long recordI txContext.sharedLock(recordId, this.identifiedName); } catch (DeadlockException e) { throw new NeedRollbackException(2, e); + } catch (StatusException e) { + throw new RecordNotFoundException(3, e); } } // 读取数据 @@ -93,6 +96,8 @@ public void delete(TransactionContext txContext, long recordId) throws RecordNot txContext.exclusiveLock(recordId, this.identifiedName); } catch (DeadlockException e) { throw new NeedRollbackException(2, e); + } catch (StatusException e) { + throw new RecordNotFoundException(3, e); } var xid = txContext.getXid(); if (xid == 0) { diff --git a/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java b/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java index fd5857b..0c8644b 100644 --- a/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java +++ b/src/main/java/net/kaaass/rumbase/record/exception/RecordNotFoundException.java @@ -10,6 +10,7 @@ *

* E5001-1 物理记录不存在 * E5001-2 由于事务性,记录不可见 + * E5001-3 事务未处于活动状态,不可访问记录 * * @author kaaass */ @@ -18,6 +19,7 @@ public class RecordNotFoundException extends RumbaseException { public static final Map REASONS = new HashMap<>() {{ put(1, "物理记录不存在"); put(2, "由于事务隔离或已经被删除,记录不可见"); + put(3, "事务未处于活动状态,不可访问记录"); }}; /** diff --git a/src/main/java/net/kaaass/rumbase/server/Session.java b/src/main/java/net/kaaass/rumbase/server/Session.java index bcb033d..5db2376 100644 --- a/src/main/java/net/kaaass/rumbase/server/Session.java +++ b/src/main/java/net/kaaass/rumbase/server/Session.java @@ -19,6 +19,7 @@ import net.kaaass.rumbase.table.exception.TableExistenceException; import net.kaaass.rumbase.transaction.TransactionContext; import net.kaaass.rumbase.transaction.TransactionIsolation; +import net.kaaass.rumbase.transaction.exception.StatusException; import java.io.*; import java.net.Socket; @@ -150,6 +151,7 @@ public void onClose() { } catch (RumbaseRuntimeException e) { log.warn("退出会话 {} 时提交事务失败", sessionId, e); say(e); + } catch (StatusException ignored) { } } // 删除活跃会话 @@ -383,10 +385,15 @@ private void checkAutoCommitAfter(boolean rollback) { if (autoCommit) { try { assert currentContext != null; - if (rollback) { - currentContext.rollback(); - } else { - currentContext.commit(); + try { + if (rollback) { + currentContext.rollback(); + } else { + currentContext.commit(); + } + } catch (StatusException e) { + log.warn("自动提交事务失败,会话 {} ", sessionId, e); + say(e); } currentContext = null; } finally { @@ -416,7 +423,12 @@ public Boolean visit(CommitStatement statement) { return false; } // 提交事务 - currentContext.commit(); + try { + currentContext.commit(); + } catch (StatusException e) { + say(e); + return false; + } say("成功提交事务" + currentContext.getXid() + "\n"); currentContext = null; return false; @@ -429,7 +441,12 @@ public Boolean visit(RollbackStatement statement) { return false; } // 回滚事务 - currentContext.rollback(); + try { + currentContext.rollback(); + } catch (StatusException e) { + say(e); + return false; + } say("成功回滚事务" + currentContext.getXid() + "\n"); currentContext = null; return false; diff --git a/src/main/java/net/kaaass/rumbase/table/TableManager.java b/src/main/java/net/kaaass/rumbase/table/TableManager.java index ecb925a..5695809 100644 --- a/src/main/java/net/kaaass/rumbase/table/TableManager.java +++ b/src/main/java/net/kaaass/rumbase/table/TableManager.java @@ -11,6 +11,7 @@ import net.kaaass.rumbase.table.exception.TableExistenceException; import net.kaaass.rumbase.table.field.VarcharField; import net.kaaass.rumbase.transaction.TransactionContext; +import net.kaaass.rumbase.transaction.exception.StatusException; import java.io.File; import java.util.*; @@ -55,7 +56,7 @@ public class TableManager { * * @param context 事务context */ - public void commit(TransactionContext context) { + public void commit(TransactionContext context) throws StatusException { context.commit(); } @@ -64,7 +65,7 @@ public void commit(TransactionContext context) { * * @param context 事务context */ - public void abort(TransactionContext context) { + public void abort(TransactionContext context) throws StatusException { context.rollback(); } diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java index f0f1692..bd8ec79 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionContext.java @@ -1,6 +1,7 @@ package net.kaaass.rumbase.transaction; import net.kaaass.rumbase.transaction.exception.DeadlockException; +import net.kaaass.rumbase.transaction.exception.StatusException; import java.util.List; @@ -61,17 +62,17 @@ static TransactionContext empty() { /** * 事务开始 */ - void start(); + void start() throws StatusException; /** * 事务提交 */ - void commit(); + void commit() throws StatusException; /** * 事务撤销 */ - void rollback(); + void rollback() throws StatusException; /** * 对记录加共享锁 @@ -80,7 +81,7 @@ static TransactionContext empty() { * @param tableName 表字段 * @throws DeadlockException 发生死锁异常 */ - void sharedLock(long uuid, String tableName) throws DeadlockException; + void sharedLock(long uuid, String tableName) throws DeadlockException, StatusException; /** * 对记录加排他锁 @@ -89,5 +90,5 @@ static TransactionContext empty() { * @param tableName 表字段 * @throws DeadlockException 发生死锁异常 */ - void exclusiveLock(long uuid, String tableName) throws DeadlockException; + void exclusiveLock(long uuid, String tableName) throws DeadlockException, StatusException; } diff --git a/src/main/java/net/kaaass/rumbase/transaction/TransactionContextImpl.java b/src/main/java/net/kaaass/rumbase/transaction/TransactionContextImpl.java index bb803bd..643f127 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/TransactionContextImpl.java +++ b/src/main/java/net/kaaass/rumbase/transaction/TransactionContextImpl.java @@ -3,11 +3,14 @@ import lombok.Getter; import lombok.Setter; import net.kaaass.rumbase.transaction.exception.DeadlockException; +import net.kaaass.rumbase.transaction.exception.StatusException; import net.kaaass.rumbase.transaction.lock.LockTable; import net.kaaass.rumbase.transaction.lock.LockTableImpl; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * 事务上下文的实现 @@ -36,6 +39,10 @@ public class TransactionContextImpl implements TransactionContext { */ @Getter private final List snapshot; + /** + * 状态互斥锁 + */ + private final Lock statusLock = new ReentrantLock(); /** * 事务状态 */ @@ -122,44 +129,68 @@ public TransactionContextImpl(int xid, TransactionIsolation isolation, Transacti * 开始事务 */ @Override - public void start() { - this.status = TransactionStatus.ACTIVE; - if (manager != null) { - manager.changeTransactionStatus(xid, TransactionStatus.ACTIVE); + public void start() throws StatusException { + statusLock.lock(); + try { + if (this.xid != 0 && !this.status.equals(TransactionStatus.PREPARING)) { + throw new StatusException(1); + } + this.status = TransactionStatus.ACTIVE; + if (manager != null) { + manager.changeTransactionStatus(xid, TransactionStatus.ACTIVE); + } + } finally { + statusLock.unlock(); } - } /** * 提交事务 */ @Override - public void commit() { - // 修改状态 - this.status = TransactionStatus.COMMITTED; - if (manager != null) { - manager.changeTransactionStatus(xid, TransactionStatus.COMMITTED); + public void commit() throws StatusException { + statusLock.lock(); + try { + if (this.xid != 0 && !this.status.equals(TransactionStatus.ACTIVE)) { + throw new StatusException(1); + } + // 修改状态 + this.status = TransactionStatus.COMMITTED; + if (manager != null) { + manager.changeTransactionStatus(xid, TransactionStatus.COMMITTED); + } + + // 释放锁 + LockTable lockTable = LockTableImpl.getInstance(); + lockTable.release(xid); + } finally { + statusLock.unlock(); } - - // 释放锁 - LockTable lockTable = LockTableImpl.getInstance(); - lockTable.release(xid); } /** * 中止事务 */ @Override - public void rollback() { - // 修改状态 - this.status = TransactionStatus.ABORTED; - if (manager != null) { - manager.changeTransactionStatus(xid, TransactionStatus.ABORTED); + public void rollback() throws StatusException { + statusLock.lock(); + try { + if (this.xid != 0 && !this.status.equals(TransactionStatus.ACTIVE)) { + throw new StatusException(1); + } + + // 修改状态 + this.status = TransactionStatus.ABORTED; + if (manager != null) { + manager.changeTransactionStatus(xid, TransactionStatus.ABORTED); + } + + // 释放锁 + LockTable lockTable = LockTableImpl.getInstance(); + lockTable.release(xid); + } finally { + statusLock.unlock(); } - - // 释放锁 - LockTable lockTable = LockTableImpl.getInstance(); - lockTable.release(xid); } /** @@ -169,10 +200,19 @@ public void rollback() { * @param tableName 表字段 */ @Override - public void sharedLock(long uuid, String tableName) throws DeadlockException { - //TODO 加锁 - LockTable lockTable = LockTableImpl.getInstance(); - lockTable.addSharedLock(xid, uuid, tableName); + public void sharedLock(long uuid, String tableName) throws DeadlockException, StatusException { + statusLock.lock(); + try { + if (this.xid != 0 && !this.status.equals(TransactionStatus.ACTIVE)) { + throw new StatusException(1); + } + + LockTable lockTable = LockTableImpl.getInstance(); + lockTable.addSharedLock(xid, uuid, tableName); + } finally { + statusLock.unlock(); + } + } /** @@ -182,10 +222,17 @@ public void sharedLock(long uuid, String tableName) throws DeadlockException { * @param tableName 表字段 */ @Override - public void exclusiveLock(long uuid, String tableName) throws DeadlockException { - //TODO 加锁 - LockTable lockTable = LockTableImpl.getInstance(); - lockTable.addExclusiveLock(xid, uuid, tableName); + public void exclusiveLock(long uuid, String tableName) throws DeadlockException, StatusException { + statusLock.lock(); + try { + if (this.xid != 0 && !this.status.equals(TransactionStatus.ACTIVE)) { + throw new StatusException(1); + } + LockTable lockTable = LockTableImpl.getInstance(); + lockTable.addExclusiveLock(xid, uuid, tableName); + } finally { + statusLock.unlock(); + } } } diff --git a/src/main/java/net/kaaass/rumbase/transaction/exception/StatusException.java b/src/main/java/net/kaaass/rumbase/transaction/exception/StatusException.java new file mode 100644 index 0000000..6801f39 --- /dev/null +++ b/src/main/java/net/kaaass/rumbase/transaction/exception/StatusException.java @@ -0,0 +1,31 @@ +package net.kaaass.rumbase.transaction.exception; + + +import net.kaaass.rumbase.exception.RumbaseException; + +import java.util.HashMap; +import java.util.Map; + +/** + * E6002 事务状态异常 + *

+ * E6002-1 事务状态异常 + * + * @author criki + */ +public class StatusException extends RumbaseException { + + public static final Map REASONS = new HashMap<>() {{ + put(1, "事务状态异常"); + }}; + + /** + * 事务状态异常 + * + * @param subId 子错误号 + */ + public StatusException(int subId) { + super(6001, subId, REASONS.get(subId)); + } + +} diff --git a/src/main/java/net/kaaass/rumbase/transaction/lock/Graph.java b/src/main/java/net/kaaass/rumbase/transaction/lock/Graph.java index 1301612..43d1075 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/lock/Graph.java +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/Graph.java @@ -94,4 +94,11 @@ private boolean dfs(int u) { visited.put(u, 1); return false; } + + @Override + public String toString() { + return "Graph{" + + "waitGraph=" + waitGraph + + '}'; + } } diff --git a/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableImpl.java b/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableImpl.java index 7b617cf..ee354ee 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableImpl.java +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableImpl.java @@ -75,23 +75,29 @@ private void addLockTemplate(int xid, DataItemId id, LockMode mode) throws Deadl // 判断是否能加锁 boolean canGrant = list.canGrant(mode); log.info("{} can grant {} lock : {}", xid, mode, canGrant); - // 虚加锁 - list.weakInsert(xid, id, mode, canGrant); - // 检测死锁 - if (deadlockCheck()) { - log.info("deadlock"); - list.pop(); - throw new DeadlockException(1); + // TODO 并发度差,最好改成读写锁 + synchronized (this) { + // 虚加锁 + list.weakInsert(xid, id, mode, canGrant); + // 检测死锁 + if (deadlockCheck()) { + log.info("deadlock"); + list.pop(); + throw new DeadlockException(1); + } } // 可以加锁 // 对于互斥锁,如果发生等待,在等待处即已释放,此处无需释放 canUnlock = canGrant; - // 移除虚锁 - list.pop(); - // 正式加锁 - list.insert(xid, id, mode, canGrant); + // TODO 并发度差,最好改成读写锁 + synchronized (list) { + // 移除虚锁 + list.pop(); + // 正式加锁 + list.insert(xid, id, mode, canGrant); + } } finally { if (canUnlock) { list.mutexLock.unlock(); @@ -133,21 +139,26 @@ public void addExclusiveLock(int xid, long uuid, String tableName) throws Deadlo */ @Override public void release(int xid) { - Set dataItemSet = new HashSet<>(); - List sharedLocks = TxList.sharedLocks.get(xid); - List exclusiveLocks = TxList.exclusiveLocks.get(xid); - log.info("{}'s sharedLocks: {}", xid, sharedLocks); - log.info("{}'s exclusiveLocks: {}", xid, exclusiveLocks); - if (sharedLocks != null) { - dataItemSet.addAll(sharedLocks); - } - if (exclusiveLocks != null) { + lock.lock(); + try { + Set dataItemSet = new HashSet<>(); + List sharedLocks = TxList.sharedLocks.get(xid); + List exclusiveLocks = TxList.exclusiveLocks.get(xid); + log.info("{}'s sharedLocks: {}", xid, sharedLocks); + log.info("{}'s exclusiveLocks: {}", xid, exclusiveLocks); + if (sharedLocks != null) { + dataItemSet.addAll(sharedLocks); + } + if (exclusiveLocks != null) { - dataItemSet.addAll(exclusiveLocks); - } + dataItemSet.addAll(exclusiveLocks); + } - for (DataItemId id : dataItemSet) { - release(xid, id); + for (DataItemId id : dataItemSet) { + release(xid, id); + } + } finally { + lock.unlock(); } } @@ -241,8 +252,11 @@ private boolean deadlockCheck() { // 建图 // 遍历每一个等待队列 - for (TxList list : lockTable.values()) { + var lockTableView = Collections.unmodifiableMap(lockTable); + log.debug("Lock table: {}", lockTableView); + for (TxList list : lockTableView.values()) { List waitingTxs = new ArrayList<>(list.locks); + log.debug("locks: {}", list.locks); // 对等待队列中建立等待关系 for (int i = 0; i < waitingTxs.size() - 1; i++) { TxItem frontItem = waitingTxs.get(i); @@ -256,7 +270,7 @@ private boolean deadlockCheck() { continue; } - log.info("[CREATING GRAPH] add edge : {} -> {}", backItem.xid, frontItem.xid); + log.debug("[CREATING GRAPH] add edge : {} -> {}", backItem.xid, frontItem.xid); // backItem等待frontItem graph.addEdge(backItem.xid, frontItem.xid); @@ -267,13 +281,13 @@ private boolean deadlockCheck() { // 邻近的后面的锁等待该锁 TxItem backItem = waitingTxs.get(i + 1); // backItem等待frontItem - log.info("[CREATING GRAPH] add edge : {} -> {}", backItem.xid, frontItem.xid); + log.debug("[CREATING GRAPH] add edge : {} -> {}", backItem.xid, frontItem.xid); graph.addEdge(backItem.xid, frontItem.xid); } } } - log.info("create graph successful!"); + log.debug("create graph successful: {}", graph); // 图成环,则有死锁 return graph.hasLoop(); } diff --git a/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableManager.java b/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableManager.java index 77bd2b2..6b37c95 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableManager.java +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/LockTableManager.java @@ -12,6 +12,7 @@ * * @author criki */ +@Deprecated public class LockTableManager { /** * 表名与锁表的映射 diff --git a/src/main/java/net/kaaass/rumbase/transaction/lock/TxItem.java b/src/main/java/net/kaaass/rumbase/transaction/lock/TxItem.java index 9bafce6..2db6513 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/lock/TxItem.java +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/TxItem.java @@ -93,4 +93,13 @@ public void abort() { lock.unlock(); } } + + @Override + public String toString() { + return "TxItem{" + + "granted=" + granted + + ", xid=" + xid + + ", mode=" + mode + + '}'; + } } diff --git a/src/main/java/net/kaaass/rumbase/transaction/lock/TxList.java b/src/main/java/net/kaaass/rumbase/transaction/lock/TxList.java index 2ad3386..a264c62 100644 --- a/src/main/java/net/kaaass/rumbase/transaction/lock/TxList.java +++ b/src/main/java/net/kaaass/rumbase/transaction/lock/TxList.java @@ -131,4 +131,11 @@ public void weakInsert(int xid, DataItemId id, LockMode mode, boolean granted) { // 加入等待队列 locks.add(item); } + + @Override + public String toString() { + return "TxList{" + + "locks=" + locks + + '}'; + } } diff --git a/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java b/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java index e12d1bb..772c9e8 100644 --- a/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java +++ b/src/test/java/net/kaaass/rumbase/record/MvccReadCommitTest.java @@ -8,6 +8,7 @@ import net.kaaass.rumbase.transaction.TransactionIsolation; import net.kaaass.rumbase.transaction.TransactionManager; import net.kaaass.rumbase.transaction.TransactionManagerImpl; +import net.kaaass.rumbase.transaction.exception.StatusException; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -50,7 +51,7 @@ public void testReadSelf() throws RecordNotFoundException { } @Test - public void testReadOther() throws RecordNotFoundException { + public void testReadOther() throws RecordNotFoundException, StatusException { var storage = RecordManager.fromFile(PATH + "testReadOther"); var manager = new FakeTxManager(TransactionIsolation.READ_COMMITTED); // 创建事务12 @@ -78,7 +79,7 @@ public void testReadOther() throws RecordNotFoundException { } @Test - public void testDelete() throws RecordNotFoundException { + public void testDelete() throws RecordNotFoundException, StatusException { var storage = RecordManager.fromFile(PATH + "testDelete"); var manager = new FakeTxManager(TransactionIsolation.READ_COMMITTED); // 创建事务1、记录a1a2 @@ -104,7 +105,7 @@ public void testDelete() throws RecordNotFoundException { } @Test - public void testReadSelfReal() throws RecordNotFoundException, IOException, FileException { + public void testReadSelfReal() throws RecordNotFoundException, IOException, FileException, StatusException { var storage = RecordManager.fromFile(PATH + "testReadSelfReal"); var manager = new TransactionManagerImpl(); // 创建事务1 @@ -128,7 +129,7 @@ public void testReadSelfReal() throws RecordNotFoundException, IOException, File } @Test - public void testReadOtherReal() throws RecordNotFoundException, IOException, FileException { + public void testReadOtherReal() throws RecordNotFoundException, IOException, FileException, StatusException { var storage = RecordManager.fromFile(PATH + "testReadOtherReal"); var manager = new TransactionManagerImpl(); // 创建事务12 @@ -160,7 +161,7 @@ public void testReadOtherReal() throws RecordNotFoundException, IOException, Fil } @Test - public void testDeleteReal() throws RecordNotFoundException, IOException, FileException { + public void testDeleteReal() throws RecordNotFoundException, IOException, FileException, StatusException { var storage = RecordManager.fromFile(PATH + "testDeleteReal"); var manager = new TransactionManagerImpl(); // 创建事务1、记录a1a2 diff --git a/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java b/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java index 3f35268..91d1e2e 100644 --- a/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java +++ b/src/test/java/net/kaaass/rumbase/record/MvccReadRepeatableTest.java @@ -10,6 +10,7 @@ import net.kaaass.rumbase.transaction.TransactionIsolation; import net.kaaass.rumbase.transaction.TransactionManager; import net.kaaass.rumbase.transaction.TransactionManagerImpl; +import net.kaaass.rumbase.transaction.exception.StatusException; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -52,7 +53,7 @@ public void testReadSelf() throws RecordNotFoundException { } @Test - public void testReadOther() throws RecordNotFoundException { + public void testReadOther() throws RecordNotFoundException, StatusException { var storage = RecordManager.fromFile(PATH + "testReadOther"); var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); // 创建事务12 @@ -80,7 +81,7 @@ public void testReadOther() throws RecordNotFoundException { } @Test - public void testDelete() throws RecordNotFoundException { + public void testDelete() throws RecordNotFoundException, StatusException { var storage = RecordManager.fromFile(PATH + "testDelete"); var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); // 创建事务1、记录a1a2 @@ -106,7 +107,7 @@ public void testDelete() throws RecordNotFoundException { } @Test - public void testVersionSkip() throws RecordNotFoundException, NeedRollbackException { + public void testVersionSkip() throws RecordNotFoundException, NeedRollbackException, StatusException { var storage = RecordManager.fromFile(PATH + "testDelete"); var manager = new FakeTxManager(TransactionIsolation.REPEATABLE_READ); // 创建公共版本 @@ -128,7 +129,7 @@ public void testVersionSkip() throws RecordNotFoundException, NeedRollbackExcept } @Test - public void testReadSelfReal() throws RecordNotFoundException, IOException, FileException { + public void testReadSelfReal() throws RecordNotFoundException, IOException, FileException, StatusException { var storage = RecordManager.fromFile(PATH + "testReadSelfReal"); var manager = new TransactionManagerImpl(); // 创建事务1 @@ -152,7 +153,7 @@ public void testReadSelfReal() throws RecordNotFoundException, IOException, File } @Test - public void testReadOtherReal() throws RecordNotFoundException, IOException, FileException { + public void testReadOtherReal() throws RecordNotFoundException, IOException, FileException, StatusException { var storage = RecordManager.fromFile(PATH + "testReadOtherReal"); var manager = new TransactionManagerImpl(); // 创建事务12 @@ -185,7 +186,7 @@ public void testReadOtherReal() throws RecordNotFoundException, IOException, Fil } @Test - public void testDeleteReal() throws RecordNotFoundException, IOException, FileException { + public void testDeleteReal() throws RecordNotFoundException, IOException, FileException, StatusException { var storage = RecordManager.fromFile(PATH + "testDeleteReal"); var manager = new TransactionManagerImpl(); // 创建事务1、记录a1a2 @@ -216,7 +217,7 @@ public void testDeleteReal() throws RecordNotFoundException, IOException, FileEx } @Test - public void testVersionSkipReal() throws RecordNotFoundException, NeedRollbackException, IOException, FileException { + public void testVersionSkipReal() throws RecordNotFoundException, NeedRollbackException, IOException, FileException, StatusException { var storage = RecordManager.fromFile(PATH + "testDeleteReal"); var manager = new TransactionManagerImpl(); // 创建公共版本 diff --git a/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java b/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java index fa4fa23..6dc4f0e 100644 --- a/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java +++ b/src/test/java/net/kaaass/rumbase/transaction/TransactionContextTest.java @@ -4,6 +4,7 @@ import net.kaaass.rumbase.FileUtil; import net.kaaass.rumbase.page.exception.FileException; import net.kaaass.rumbase.transaction.exception.DeadlockException; +import net.kaaass.rumbase.transaction.exception.StatusException; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -57,7 +58,7 @@ public void testCreateTransaction() throws IOException, FileException { * 测试事务变化 */ @Test - public void testChangeStatus() throws IOException, FileException { + public void testChangeStatus() throws IOException, FileException, StatusException { var manager = new TransactionManagerImpl("test_gen_files/test_change.log"); var committedTransaction = manager.createTransactionContext(TransactionIsolation.READ_COMMITTED); // 事务初始状态 @@ -82,7 +83,7 @@ public void testChangeStatus() throws IOException, FileException { * 测试事务持久化 */ @Test - public void testTransactionPersistence() throws IOException, FileException { + public void testTransactionPersistence() throws IOException, FileException, StatusException { var manager = new TransactionManagerImpl("test_gen_files/test_persistence.log"); // 事务创建,事务状态记录数改变 var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); @@ -115,7 +116,7 @@ public void testTransactionPersistence() throws IOException, FileException { * 测试事务状态复原 */ @Test - public void testTransactionRecovery() throws IOException, FileException { + public void testTransactionRecovery() throws IOException, FileException, StatusException { var manager = new TransactionManagerImpl("test_gen_files/test_recovery.log"); // 事务创建,事务状态记录数改变 var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); @@ -136,12 +137,14 @@ public void testTransactionRecovery() throws IOException, FileException { * 测试事务上锁 */ @Test - public void testAddLock() throws IOException, FileException { + public void testAddLock() throws IOException, FileException, StatusException { var manager = new TransactionManagerImpl("test_gen_files/test_add_lock.log"); var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); var transaction2 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); String tableName = "test"; + transaction1.start(); + transaction2.start(); // 互斥锁 new Thread(() -> { @@ -151,7 +154,11 @@ public void testAddLock() throws IOException, FileException { e.printStackTrace(); } log.info("transaction2 commit"); - transaction2.commit(); + try { + transaction2.commit(); + } catch (StatusException e) { + e.printStackTrace(); + } }).start(); try { transaction1.exclusiveLock(1, tableName); @@ -171,7 +178,7 @@ public void testAddLock() throws IOException, FileException { transaction1.commit(); transaction2.rollback(); - } catch (DeadlockException e) { + } catch (DeadlockException | StatusException e) { e.printStackTrace(); } } @@ -180,12 +187,15 @@ public void testAddLock() throws IOException, FileException { * 测试死锁 */ @Test - public void testDeadlock() throws IOException, FileException, InterruptedException { + public void testDeadlock() throws IOException, FileException, InterruptedException, StatusException { var manager = new TransactionManagerImpl("test_gen_files/test_deadlock.log"); var transaction1 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); var transaction2 = manager.createTransactionContext(TransactionIsolation.READ_UNCOMMITTED); String tableName = "test"; + transaction1.start(); + transaction2.start(); + AtomicBoolean deadlockDetect = new AtomicBoolean(false); new Thread(() -> { try { @@ -197,7 +207,13 @@ public void testDeadlock() throws IOException, FileException, InterruptedExcepti transaction2.exclusiveLock(1, tableName); } catch (DeadlockException e) { deadlockDetect.set(true); - transaction2.rollback(); + try { + transaction2.rollback(); + } catch (StatusException statusException) { + statusException.printStackTrace(); + } + e.printStackTrace(); + } catch (StatusException e) { e.printStackTrace(); } }).start(); From b1267a9ef93c023e7bdd2e09081f764dd4cf46cc Mon Sep 17 00:00:00 2001 From: KAAAsS Date: Sun, 17 Jan 2021 15:11:56 +0800 Subject: [PATCH 18/18] =?UTF-8?q?[V]=E7=89=88=E6=9C=AC=20v0.1=5Falpha?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7ee4f5d..6774050 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,23 @@ # RumBase Java -| master | dev | feature-mock | -| ------ | --- | ------------ | -| [![Build Status](https://www.travis-ci.com/kaaass/rumbase_java.svg?token=7d6V7UKwzfD6augATNKx&branch=master)](https://www.travis-ci.com/kaaass/rumbase_java) | [![Build Status](https://www.travis-ci.com/kaaass/rumbase_java.svg?token=7d6V7UKwzfD6augATNKx&branch=dev)](https://www.travis-ci.com/kaaass/rumbase_java) | [![Build Status](https://www.travis-ci.com/kaaass/rumbase_java.svg?token=7d6V7UKwzfD6augATNKx&branch=feature-mock)](https://www.travis-ci.com/kaaass/rumbase_java) | +| master | dev | +| ------ | --- | +| [![Build Status](https://www.travis-ci.com/kaaass/rumbase_java.svg?token=7d6V7UKwzfD6augATNKx&branch=master)](https://www.travis-ci.com/kaaass/rumbase_java) | [![Build Status](https://www.travis-ci.com/kaaass/rumbase_java.svg?token=7d6V7UKwzfD6augATNKx&branch=dev)](https://www.travis-ci.com/kaaass/rumbase_java) | -Java构建的高性能SQL关系型数据库。 +Java构建的SQL关系型数据库。 本项目为吉林大学2018级数据库系统课程&系统软件综合实践(荣誉课)课程设计。 +## 构建 + +1. 在 Release 页面下载源码或 clone 项目 +2. 在项目目录执行 `./gradlew build` + ## 分工 | **模块** | **内容** | **负责人** | **包** | | ----------------------- | -------------------- | ---------- | ------------ | +| Server Module | 服务器、会话管理 | @KAAAsS | server | | Query Parse Module | SQL 语句解析 | @KAAAsS | parse | | Query Execution Module | 查询执行、优化 | @KveinAxel | query | | Table Management Module | 系统内数据库、表管理 | @KveinAxel | table |