├── .gitignore ├── .idea ├── .gitignore ├── encodings.xml ├── misc.xml └── vcs.xml ├── README.md ├── pom.xml └── src ├── main └── java │ └── cn │ └── edu │ └── gzhu │ ├── backend │ ├── common │ │ ├── AbstractCache.java │ │ └── SubArray.java │ ├── dm │ │ ├── DataManager.java │ │ ├── DataManagerImpl.java │ │ ├── Recover.java │ │ ├── dataItem │ │ │ ├── DataItem.java │ │ │ └── impl │ │ │ │ └── DataItemImpl.java │ │ ├── logger │ │ │ ├── Logger.java │ │ │ └── impl │ │ │ │ └── LoggerImpl.java │ │ ├── page │ │ │ ├── Page.java │ │ │ ├── PageCache.java │ │ │ ├── PageOne.java │ │ │ ├── PageX.java │ │ │ └── impl │ │ │ │ ├── PageCacheImpl.java │ │ │ │ └── PageImpl.java │ │ └── pageIndex │ │ │ ├── PageIndex.java │ │ │ └── PageInfo.java │ ├── im │ │ ├── BPlusTree.java │ │ └── Node.java │ ├── parser │ │ ├── Parser.java │ │ ├── Tokenizer.java │ │ └── statement │ │ │ ├── Abort.java │ │ │ ├── Begin.java │ │ │ ├── Commit.java │ │ │ ├── Create.java │ │ │ ├── Delete.java │ │ │ ├── Drop.java │ │ │ ├── Insert.java │ │ │ ├── Select.java │ │ │ ├── Show.java │ │ │ ├── SingleExpression.java │ │ │ ├── Update.java │ │ │ └── Where.java │ ├── tbm │ │ ├── BeginRes.java │ │ ├── Booter.java │ │ ├── Field.java │ │ ├── FieldCalRes.java │ │ ├── Table.java │ │ ├── TableManager.java │ │ └── impl │ │ │ └── TableManagerImpl.java │ ├── tm │ │ ├── TransactionManager.java │ │ └── impl │ │ │ └── TransactionManagerImpl.java │ ├── utils │ │ ├── Panic.java │ │ ├── ParseStringRes.java │ │ ├── Parser.java │ │ ├── RandomUtil.java │ │ └── Types.java │ └── vm │ │ ├── Entry.java │ │ ├── LockTable.java │ │ ├── Transaction.java │ │ ├── VersionManager.java │ │ ├── Visibility.java │ │ └── impl │ │ └── VersionManagerImpl.java │ ├── client │ ├── Client.java │ ├── Launcher.java │ ├── RoundTripper.java │ └── Shell.java │ ├── common │ └── Error.java │ ├── server │ ├── Executor.java │ ├── Launcher.java │ └── Server.java │ └── transport │ ├── Encoder.java │ ├── Package.java │ ├── Packager.java │ └── Transporter.java └── test └── java └── cn └── edu └── gzhu └── MainTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JCSQL 2 | 使用JAVA去仿写MySQL基本功能: 3 | * 数据的可靠性和数据恢复 4 | * 两段锁协议(2PL)实现可串行化调度 5 | * MVCC 6 | * 两种事务隔离级别(读提交和可重复读) 7 | * 死锁处理 8 | * 简单的表和字段管理 9 | * 简陋的 SQL 解析(因为懒得写词法分析和自动机,就弄得比较简陋) 10 | * 基于 socket 的 server 和 client 11 | 12 | ## 后端划分为五个模块 13 | 分别如下: 14 | 1. Transaction Manager(TM) 15 | 2. Data Manager(DM) 16 | 3. Version Manager(VM) 17 | 4. Index Manager(IM) 18 | 5. Table Manager(TBM) 19 | 20 | 每个模块的职责如下: 21 | 22 | 1. TM 通过维护 XID 文件来维护事务的状态,并提供接口供其他模块来查询某个事务的状态。 23 | 2. DM 直接管理数据库 DB 文件和日志文件。DM 的主要职责有:1) 分页管理 DB 文件,并进行缓存;2) 管理日志文件,保证在发生错误时可以根据日志进行恢复;3) 抽象 DB 文件为 DataItem 供上层模块使用,并提供缓存。 24 | 3. VM 基于两段锁协议实现了调度序列的可串行化,并实现了 MVCC 以消除读写阻塞。同时实现了两种隔离级别。 25 | 4. IM 实现了基于 B+ 树的索引,BTW,目前 where 只支持已索引字段。 26 | 5. TBM 实现了对字段和表的管理。同时,解析 SQL 语句,并根据语句操作表。 27 | 28 | ## 各个模块提供的操作 29 | 1. DM:insert(x), update(x), read(x). 30 | DM 提供针对数据项(DataItem)的基本插入,更新与读取操作,且这些操作是原子性的。DM会直接对数据库文件进行读写。 31 | 2. TM:begin(T), commit(T), abort(T), isActive(T), isCommitted(T), isAborted(T)。 32 | TM 提供了针对事务的开始,提交,回滚操作,同时提供了对事务状态的查询操作。 33 | 3. VM:insert(X), update(X), read(X), delete(X)。 34 | VM 提供了针对记录(Entry)的增删改查操作,VM在内部为每条记录维护多个版本,并根据不同的事务,返回不同的版本。 35 | VM 对这些实现,是建立在 DM 和 TM 的各个操作上的,还有一个事务可见性类 Visibility。 36 | 4. TBM:execute(statement) 37 | TBM 就是非常高层的模块,能直接执行用户输入的语句(statement),然后进行执行。 38 | TBM 对语句的执行时建立在 VM 和 IM 提供的各个操作上的。 39 | 5. IM:value search(key), insert(key, value) 40 | IM 提供了对索引的基本操作 41 | 42 | ## TM 解析: 43 | 44 | TM 维护 XID 文件来维护事务的状态,并提供接口供其他模块来查询某个事务的状态。 45 | 46 | 事务的 XID 从 1 开始标号,自增且不可重复。**特殊规定 XID 为 0 是一个超级事务** 47 | 48 | 每个事务都有三种状态: 49 | 50 | 1. active 正在进行, 尚未结束 51 | 2. committed, 已提交 52 | 3. aborted, 已撤销(回滚) 53 | 54 | ```java 55 | // XID文件头长度 56 | public static final int LEN_XID_HEADER_LENGTH = 8; 57 | // 每个事务的占用长度 58 | private static final int XID_FIELD_SIZE = 1; 59 | ``` 60 | 61 | > XID 文件的头部,保存一个 8 字节的数字,记录这个 XID 文件管理的事务的个数 62 | > 63 | > 事务 xid 在文件中的状态就存储在 (xid - 1) + 8 字节处,xid - 1 是因为 xid 为 0 的超级事务的状态不需要记录,永远为 committed 状态 64 | 65 | ## DM 解析: 66 | 67 | 1. AbstractCache:引用计数法的缓存框架,留了两个从数据源获取数据和释放缓存的抽象方法给具体实现类去实现。 68 | 2. PageImpl:数据页的数据结构,包含页号、是否脏数据页、数据内容、所在的PageCache缓存。 69 | 3. PageOne:校验页面,用于启动DM的时候进行文件校验。 70 | 4. PageX:每个数据页的管理器。initRaw()新建一个数据页并设置FSO值,FSO后面存的其实就是一个个DataItem数据包 71 | 5. PageCacheImpl:数据页的缓存具体实现类,除了重写获取和释放两个方法外,还完成了所有数据页的统一管理: 72 | * 1)获取数据库中的数据页总数;getPageNumber() 73 | * 2)新建一个数据页并写入数据库文件;newPage(byte[] initData) 74 | * 3)从缓存中获取指定的数据页;getPage(int pgno) 75 | * 4)删除指定位置后面的数据页;truncateByBgno(int maxPgno) 76 | * 6、PageIndex:方便DataItem的快速定位插入,其实现原理可以理解为HashMap那种数组+链表结构(实际实现是 List+ArrayList),先是一个大小为41的数组 存的是区间号(区间号从1>开始),然后每个区间号数组后面跟一个数组存满足空闲大小的所有数据页信息(PageInfo)。 77 | * 7、Recover:日志恢复策略,主要维护两个日志:updateLog和insertLog,重做所有已完成事务 redo,撤销所有未完成事务undo 78 | * 8、DataManager:统揽全局的类,主要方法也就是读写和修改,全部通过DataItem进行。 79 | 80 | 流程: 81 | 82 | 首先从DataManager进去创建DM(打开DM就不谈了,只是多了个检验PageOne和更新PageIndex),需要执行的操作是: 83 | * 1)新建PageCache,DM里面有 页面缓存 和 DataItem缓存 两个实现;DataItem缓存也是在PageCache中获取的,DataItem缓存不存在的时候就去PageCache缓存获取,PageCache缓存没有才去数据库文件中获取; 84 | * 2)新建日志, 85 | * 3)构建DM管理器; 86 | * 4)初始化校验页面1: dm.initPageOne()nnnDataManager的所有功能(主要功能就是CRUD,进行数据的读写修改都是靠DataItem进行操作的 ,所以PageX管理页面的时候FSO后面的DATA其实就是一个个的DataItem包): 87 | 1. 初始化校验页面1: 88 | initPageOne() 和 启动时候进行校验:loadCheckPageOne() 89 | 2. 读取数据 read(long uid): 90 | 从DataItem缓存中读取一个DataItem数据包并进行校验,如果DataItem缓存中没有就会调用 DataManager下的getForCache(long uid)从PageCache缓存中读取DataItem数据包并加入DataItem缓存(其实PageCache缓存和DataItem缓存都是共用的一个cache Map存的,只是key不一样,page的key是页号,DataItem的key是uid,页号+偏移量),如果PgeCache也没有就去数据库文件读取。 91 | 3. 插入数据 insert(long xid, byte[] data): 92 | 先把数据打包成DataItem格式,然后在 pageIndex 中获取一个足以存储插入内容的页面的页号; 获取页面后,需要先写入插入日志Recover.insertLog(xid, pg, raw),接着才可以通过 pageX 在目标数据页插入数据PageX.insert(pg, raw),并返回插入位置的偏移。如果在pageIndex中没有空闲空间足够插入数据了,就需要新建一个数据页pc.newPage(PageX.initRaw())。最后需要将页面信息重新插入 pageIndex。 93 | 4. 修改数据就是先读取数据,然后修改DataItem内容,再插入DataItem数据。但是在修改数据操作的前后需要调用DataItemImp.after()进行解写锁并记录更新日志,这里需要依赖DataManager里面的logDataItem(long xid, DataItem di)方法; 94 | 5. 释放缓存: 95 | 释放DataItem的缓存,实质上就是释放DataItem所在页的PageCache缓存 96 | 97 | **详细说明:** 98 | 99 | ```java 100 | public class PageImpl implements Page { 101 | private int pageNumber; 102 | private byte[] data; 103 | private boolean dirty; 104 | private Lock lock; 105 | 106 | private PageCache pc; 107 | } 108 | ``` 109 | 110 | pageNumber 是这个页面的页号,**该页号从 1 开始**。data 就是这个页实际包含的字节数据。dirty 标志着这个页面是否是脏页面,在缓存驱逐的时候,脏页面需要被写回磁盘。 111 | 112 | ### 数据缓存页 113 | 114 | 考虑到实现遍历,采用引用计数缓存框架 115 | 116 | `AbstractCache` 是一个抽象类,内部有两个抽象方法,留给实现类去实现具体的操作: 117 | 118 | ```java 119 | /** 120 | * 当资源不在缓存时的获取行为 121 | */ 122 | protected abstract T getForCache(long key) throws Exception; 123 | /** 124 | * 当资源被驱逐时的写回行为 125 | */ 126 | protected abstract void releaseForCache(T obj); 127 | ``` 128 | 129 | 130 | 131 | ### 数据页管理 132 | 133 | #### 第一页 134 | 135 | 我们设置数据页的第一页为校验页面,在每次数据库启动时,会生成一串随机字节,存储在 100 ~ 107 字节。在数据库正常关闭时,会将这串字节拷贝到第一页的 108 ~ 115 字节。这样数据库在每次启动时,检查第一页两处的字节是否相同,以此来判断上一次是否正常关闭。如果是异常关闭,就需要执行数据库的回复流程。 136 | 137 | #### 普通页 138 | 139 | 一个普通页面以一个 2 字节无符号数起始,表示这一页的空闲位置的偏移,剩下的部分都是实际存储的数据。 140 | 141 | ### 日志文件的读写 142 | 143 | 日志的二进制文件,按照如下的格式进行排布: 144 | 145 | ``` 146 | [XChecksum][Log1][Log2][Log3]...[LogN][BadTail] 147 | ``` 148 | 149 | 其中 XChecksum 是一个四字节的整数,是对后续所有日志计算的校验和。Log1 ~ LogN 是常规的日志数据,BadTail 是在数据库崩溃时,没有来得及写完的日志数据,这个 BadTail 不一定存在。 150 | 151 | 每条日志的格式如下: 152 | 153 | ``` 154 | [Size][Checksum][Data] 155 | ``` 156 | 157 | 其中,Size 是一个四字节整数,标识了 Data 段的字节数。Checksum 则是该条日志的校验和。 158 | 159 | 单条日志的校验和,其实就是通过一个指定的种子实现的: 160 | 161 | ```java 162 | private int calChecksum(int xCheck, byte[] log) { 163 | for (byte b : log) { 164 | xCheck = xCheck * SEED + b; 165 | } 166 | return xCheck; 167 | } 168 | ``` 169 | 170 | 对所有日志求出校验和,求和就能得到日志文件的校验和 XChecksum。 171 | 172 | #### 实现Logger为迭代器模式 173 | 174 | 实现 next() 方法,不断地从文件中读取下一条日志,并将其中的 Data 解析出来并返回。 175 | 176 | ```java 177 | private byte[] internNext() { 178 | if(position + OF_DATA >= fileSize) { 179 | return null; 180 | } 181 | // 读取size 182 | ByteBuffer tmp = ByteBuffer.allocate(4); 183 | fc.position(position); 184 | fc.read(tmp); 185 | int size = Parser.parseInt(tmp.array()); 186 | if(position + size + OF_DATA > fileSize) { 187 | return null; 188 | } 189 | 190 | // 读取checksum+data 191 | ByteBuffer buf = ByteBuffer.allocate(OF_DATA + size); 192 | fc.position(position); 193 | fc.read(buf); 194 | byte[] log = buf.array(); 195 | 196 | // 校验 checksum 197 | int checkSum1 = calChecksum(0, Arrays.copyOfRange(log, OF_DATA, log.length)); 198 | int checkSum2 = Parser.parseInt(Arrays.copyOfRange(log, OF_CHECKSUM, OF_DATA)); 199 | if(checkSum1 != checkSum2) { 200 | return null; 201 | } 202 | position += log.length; 203 | return log; 204 | } 205 | ``` 206 | 207 | 在打开一个日志文件时,需要**首先校验日志文件的 XChecksum**,并**移除文件尾部可能存在的 BadTail**,由于 BadTail 该条日志尚未写入完成,文件的校验和也就不会包含该日志的校验和,去掉 BadTail 即可保证日志文件的一致性。 208 | 209 | 向日志文件写入日志时,也是首先将数据包裹成日志格式,写入文件后,再更新文件的校验和,更新校验和时,会刷新缓冲区,保证内容写入磁盘。 210 | 211 | ```java 212 | public void log(byte[] data) { 213 | byte[] log = wrapLog(data); 214 | ByteBuffer buf = ByteBuffer.wrap(log); 215 | lock.lock(); 216 | try { 217 | fc.position(fc.size()); 218 | fc.write(buf); 219 | } catch(IOException e) { 220 | Panic.panic(e); 221 | } finally { 222 | lock.unlock(); 223 | } 224 | updateXChecksum(log); 225 | } 226 | 227 | private void updateXChecksum(byte[] log) { 228 | this.xChecksum = calChecksum(this.xChecksum, log); 229 | fc.position(0); 230 | fc.write(ByteBuffer.wrap(Parser.int2Byte(xChecksum))); 231 | fc.force(false); 232 | } 233 | 234 | private byte[] wrapLog(byte[] data) { 235 | byte[] checksum = Parser.int2Byte(calChecksum(0, data)); 236 | byte[] size = Parser.int2Byte(data.length); 237 | return Bytes.concat(size, checksum, data); 238 | } 239 | ``` 240 | 241 | ### 恢复策略 242 | 243 | DM 为上层模块,提供了两种操作,分别是插入新数据 (I) 和 更新现有数据 (U)。 244 | 245 | DM的日志策略是: 246 | 247 | **在进行 I 和 U 操作之前,必须先进行对应的日志操作,在保证日志写入磁盘后,才进行数据操作** 248 | 249 | 这个日志策略,使得 DM 对于数据操作的磁盘同步,可以更加随意。日志在数据操作之前,保证到达了磁盘,那么即使该数据操作最后没有来得及同步到磁盘,数据库就发生了崩溃,后续也可以通过磁盘上的日志恢复该数据。 250 | 251 | 对于两种数据操作,DM 记录的日志如下: 252 | 253 | * (Ti, I, A, X):表示事务 Ti 在 A 位置插入一条数据 x 254 | * (Ti, U, A, oldx, newx):表示事务 Ti 将 A 位置的数据,从 oldx 更新为 new x 255 | 256 | #### 单线程 257 | 258 | 由于单线程,Ti、Tj、Tk 的日志永远不会相交。假设日志中最后一个事务是 Ti: 259 | 260 | 1. 对 Ti 之前所有的事务日志进行重做 (redo) 261 | 2. 接着检查 Ti 的状态(XID 文件),如果 Ti 的状态是已完成(包括 committed 和 aborted),就将 Ti 重做,否则进行撤销 (undo) 262 | 263 | 接着,是如何对事务 T 进行 redo: 264 | 265 | 1. 正序扫描事务 T 的所有日志 266 | 2. 如果日志是插入操作(Ti, I, A, x),就将 x 重新插入 A 位置 267 | 3. 如果日志是更新操作(Ti, U, A, oldx, newx),就将 A 位置的值设置为 newx 268 | 269 | undo的过程: 270 | 271 | 1. 倒序扫描事务 T 的所有日志 272 | 2. 如果日志是插入操作(Ti, I, A, x),就将 A 位置的数据删除 273 | 3. 如果日志是更新操作(Ti, U, A, oldx, newx),就将 A 位置的值设置为 oldx 274 | 275 | #### 多线程 276 | 277 | * 规定一:正在进行的事务,不会读取其他任何未提交的事务产生的数据 278 | 279 | 假设 x 的初始值是 0 280 | 281 | ```java 282 | T1 begin 283 | T2 begin 284 | T1 set x = x+1 // 产生的日志为(T1, U, A, 0, 1) 285 | T2 set x = x+1 // 产生的日志为(T1, U, A, 1, 2) 286 | T2 commit 287 | MYDB break down 288 | ``` 289 | 290 | 在系统崩溃时,T1 仍然是活跃状态。那么当数据库重新启动,执行恢复例程时,会对 T1 进行撤销,对 T2 进行重做,但是,无论撤销和重做的先后顺序如何,x 最后的结果,要么是 0,要么是 2,这都是错误的。 291 | 292 | > 出现这种问题的原因, 归根结底是因为我们的日志太过简单, 仅仅记录了”前相”和”后相”. 并单纯的依靠”前相”undo, 依靠”后相”redo. 这种简单的日志方式和恢复方式, 并不能涵盖住所有数据库操作形成的语义 293 | 294 | 解决方法有两种: 295 | 296 | 1. 增加日志种类 297 | 2. 限制数据库操作 298 | 299 | * 规定二:正在进行的事务,不会修改其他任何非提交的事务修改或产生的数据 300 | 301 | **由于 VM 的存在,传递到 DM 层,真正执行的操作序列,都可以保证规定 1 和规定 2。** 302 | 303 | 有了以上两条规定,并发情况下日志的恢复就很方便了: 304 | 305 | 1. 重做所有崩溃时已完成(committed 或 aborted)的事务 306 | 2. 撤销所有崩溃时未完成(active)的事务 307 | 308 | 在恢复后,数据库就会恢复到所有已完成事务的结束,所有未完成事务尚未开始的状态 309 | 310 | #### 实现 311 | 312 | ```java 313 | private static final byte LOG_TYPE_INSERT = 0; 314 | private static final byte LOG_TYPE_UPDATE = 1; 315 | 316 | // updateLog: 317 | // [LogType] [XID] [UID] [OldRaw] [NewRaw] 318 | 319 | // insertLog: 320 | // [LogType] [XID] [Pgno] [Offset] [Raw] 321 | ``` 322 | 323 | ### 页面索引 324 | 325 | 页面索引,缓存了每一页的空闲空间。用于在上层模块进行插入操作时,能够快速找到一个合适空间的页面,而无需从磁盘或者缓存中查找每一个页面的信息。 326 | 327 | * 将一页的空间划分成了40个区间 328 | * 在启动时,遍历所有的页面信息,获取页面的空闲空间,安排到这40个区间中。 329 | * insert 在请求一个页时,会首先将所需的空间向上取整,映射到某一个区间,随后取出这个区间的任何一页,都可以满足需求。 330 | 331 | #### 实现 332 | 333 | ```java 334 | public class PageIndex { 335 | // 将一页划成40个区间 336 | private static final int INTERVALS_NO = 40; 337 | private static final int THRESHOLD = PageCache.PAGE_SIZE / INTERVALS_NO; 338 | 339 | private List[] lists; 340 | } 341 | ``` 342 | 343 | 获取页面:算出区间号,直接获取 344 | 345 | ```java 346 | public PageInfo select(int spaceSize){ 347 | lock.lock(); 348 | try { 349 | int number = spaceSize / THRESHOLD; 350 | if(number < INTERVALS_NO) number ++; 351 | while (number <= INTERVALS_NO) { 352 | if(lists[number].size() == 0){ 353 | number ++; 354 | continue; 355 | } 356 | return lists[number].remove(0); 357 | } 358 | return null; 359 | } finally { 360 | lock.unlock(); 361 | } 362 | } 363 | ``` 364 | 365 | 返回的 PageInfo 包含页号和空闲空间大小的信息 366 | 367 | 可以看到,被选择的页是直接从 PageIndex 中移除,这意味着,同一个页面是不允许并发写的。在上层模块使用完这个页面后,需要将其重新插入 PageIndex。 368 | 369 | 插入实现: 370 | 371 | ```java 372 | public void add(int pageNum, int freeSpace){ 373 | lock.lock(); 374 | try { 375 | int number = freeSpace / THRESHOLD; 376 | lists[number].add(new PageInfo(pageNum, freeSpace)); 377 | } finally { 378 | lock.unlock(); 379 | } 380 | } 381 | ``` 382 | 383 | 在 DataManager 被创建时,需要获取所有页面并填充 PageIndex: 384 | 385 | ```java 386 | // 初始化pageIndex 387 | void fillPageIndex() { 388 | int pageNumber = pc.getPageNumber(); 389 | for(int i = 2; i <= pageNumber; i ++) { 390 | Page pg = null; 391 | try { 392 | pg = pc.getPage(i); 393 | } catch (Exception e) { 394 | Panic.panic(e); 395 | } 396 | pIndex.add(pg.getPageNumber(), PageX.getFreeSpace(pg)); 397 | pg.release(); 398 | } 399 | } 400 | ``` 401 | 402 | ### DataItem 403 | 404 | DataItem 是 DM 层向上提供的数据抽象。上层模块通过地址,向 DM 请求到对应的 DataItem,再获取到其中的数据。 405 | 406 | ```java 407 | public class DataItemImpl implements DataItem { 408 | private SubArray raw; 409 | private byte[] oldRaw; 410 | private DataManagerImpl dm; 411 | private long uid; 412 | private Page pg; 413 | } 414 | ``` 415 | 416 | 保存一个 dm 的引用是因为其释放依赖 dm 的释放(dm 同时实现了缓存接口,用于缓存 DataItem),以及修改数据时写落日志。 417 | 418 | DataItem 中保存的数据,结构如下: 419 | 420 | ```java 421 | [ValidFlag] [DataSize] [Data] 422 | ``` 423 | 424 | 其中 ValidFlag 占用 1 字节,标识了该 DataItem 是否有效。删除一个 DataItem,只需要简单地将其有效位设置为 0。DataSize 占用 2 字节,标识了后面 Data 的长度。 425 | 426 | 上层模块在获取到 DataItem 后,可以通过 `data()` 方法,该方法返回的数组是数据共享的,而不是拷贝实现的,所以使用了 SubArray。 427 | 428 | ```java 429 | @Override 430 | public SubArray data() { 431 | return new SubArray(raw.raw, raw.start+OF_DATA, raw.end); 432 | } 433 | ``` 434 | 435 | 在上层模块试图对 DataItem 进行修改时,需要遵循一定的流程:在修改之前需要调用 `before()` 方法,想要撤销修改时,调用 `unBefore()` 方法,在修改完成后,调用 `after()` 方法。整个流程,主要是为了保存前相数据,并及时落日志。DM 会保证对 DataItem 的修改是原子性的。 436 | 437 | ```java 438 | @Override 439 | public void before() { 440 | wLock.lock(); 441 | pg.setDirty(true); 442 | System.arraycopy(raw.raw, raw.start, oldRaw, 0, oldRaw.length); 443 | } 444 | 445 | @Override 446 | public void unBefore() { 447 | System.arraycopy(oldRaw, 0, raw.raw, raw.start, oldRaw.length); 448 | wLock.unlock(); 449 | } 450 | 451 | @Override 452 | public void after(long xid) { 453 | dm.logDataItem(xid, this); 454 | wLock.unlock(); 455 | } 456 | ``` 457 | 458 | `after()` 方法,主要就是调用 dm 中的一个方法,对修改操作落日志,不赘述。 459 | 460 | 在使用完 DataItem 后,也应当及时调用 release() 方法,释放掉 DataItem 的缓存(由 DM 缓存 DataItem)。 461 | 462 | ```java 463 | @Override 464 | public void release() { 465 | dm.releaseDataItem(this); 466 | } 467 | ``` 468 | 469 | ## VM 解析: 470 | 471 | **VM 基于两段锁协议实现了调度序列的可串行化,并实现了 MVCC 以消除读写阻塞。同时实现了两种隔离级别。** 472 | 473 | ### 2PL 与 MVCC 474 | 475 | #### 冲突 与 2PL 476 | 477 | 首先来定义数据库的冲突,暂时不考虑插入操作,只看更新操作(U)和读操作(R),两个操作只要满足下面三个条件,就可以说这两个操作相互冲突: 478 | 479 | 1. 这两个操作是由不同的事务执行的 480 | 2. 这两个操作其操作是同一个数据项 481 | 3. 这两个操作至少有一个是更新操作 482 | 483 | 那么,对同一个数据操作的冲突,其实就只有下面这两种情况: 484 | 485 | 1. 两个不同事务的 U 操作冲突 486 | 2. 两个不同事务的 U、R 操作冲突 487 | 488 | **那么冲突或者不冲突的意义是什么?** 489 | 490 | 作用在于**交换两个互不冲突的操作的顺序,不会对最终的结果造成影响**,而交换两个冲突操作的顺序,则是会有影响的。 491 | 492 | **由此看来,2PL 确实保证了调度序列的可串行化,但是不可避免地导致了事务间的相互阻塞,甚至可能导致死锁。为了提高事务处理的效率,降低阻塞概率,实现了 MVCC** 493 | 494 | #### MVCC 495 | 496 | **首先明确下记录和版本的概念** 497 | 498 | DM 层向上层提供了数据项(Data Item)的概念,VM 通过管理所有的数据项,向上层提供了记录(Entry)的概念。 499 | 500 | 上层模块通过 VM 操作数据的最小单位,就是记录。VM 则在其内部,为每个记录,维护了多个版本(Version)。每当上层模块对某个记录进行修改时,VM 就会为这个记录创建一个新的版本。 501 | 502 | **利用MVCC,降低了事务的阻塞概率。**譬如,T1 想要更新记录 X 的值,于是 T1 需要首先获取 X 的锁,接着更新,**也就是创建了一个新的 X 的版本**,假设为 x3。 503 | 504 | 假设 T1 还没有释放 X 的锁时,T2 想要读取 X 的值,这时候就不会阻塞,会返回一个较老版本的 X,例如 x2。这样最后执行的结果,就等价于,T2 先执行,T1 后执行,调度序列仍然是可串行化的。 505 | 506 | 如果 X 没有一个更老的版本,那只能等待 T1 释放锁了。 507 | 508 | 为了保证数据的可恢复,VM 层传递到 DM 的操作序列需要满足以下两个规则: 509 | 510 | > 规定1:正在进行的事务,不会读取其他任何未提交的事务产生的数据。 511 | > 规定2:正在进行的事务,不会修改其他任何未提交的事务修改或产生的数据。 512 | 513 | 由于 2PL 和 MVCC,我们可以看到,这两个条件都被很轻易地满足了。 514 | 515 | ### 记录的实现 516 | 517 | 使用 Entry 类维护记录。 518 | 519 | **MVCC实现了多版本,但在实际实现中,VM 并没有提供 Update 操作,对于字段的更新操作由后面的表和字段管理(TBM)实现。** 520 | 521 | 一条 Entry 中存储的数据格式如下: 522 | 523 | ```java 524 | [XMIN] [XMAX] [DATA] 525 | ``` 526 | 527 | XMIN 是创建该条记录(版本)的事务编号,而 XMAX 则是删除该条记录(版本)的事务编号。DATA 就是这条记录持有的数据。 528 | 529 | 根据这个结构,在创建记录时调用的 wrapEntryRaw() 方法如下: 530 | 531 | ```java 532 | public static byte[] wrapEntryRaw(long xid, byte[] data) { 533 | byte[] xmin = Parser.long2Byte(xid); 534 | byte[] xmax = new byte[8]; 535 | return Bytes.concat(xmin, xmax, data); 536 | } 537 | ``` 538 | 539 | 同样,如果要获取记录中持有的数据,也就需要按照这个结构来解析: 540 | 541 | ```java 542 | // 以拷贝的形式返回内容 543 | public byte[] data() { 544 | dataItem.rLock(); 545 | try { 546 | SubArray sa = dataItem.data(); 547 | byte[] data = new byte[sa.end - sa.start - OF_DATA]; 548 | System.arraycopy(sa.raw, sa.start+OF_DATA, data, 0, data.length); 549 | return data; 550 | } finally { 551 | dataItem.rUnLock(); 552 | } 553 | } 554 | ``` 555 | 556 | 这里以拷贝的形式返回数据,如果需要修改的话,需要对 DataItem 执行 `before()` 方法,这个在设置 XMAX 的值中体现了: 557 | 558 | ```java 559 | public void setXmax(long xid) { 560 | dataItem.before(); 561 | try { 562 | SubArray sa = dataItem.data(); 563 | System.arraycopy(Parser.long2Byte(xid), 0, sa.raw, sa.start+OF_XMAX, 8); 564 | } finally { 565 | dataItem.after(xid); 566 | } 567 | } 568 | ``` 569 | 570 | `before()` 和 `after()` 是在 DataItem 一节中就已经确定的数据项修改规则。 571 | 572 | ### 事务隔离级别 573 | 574 | #### 读提交 575 | 576 | 该数据库支持的最低的事务隔离程度,是“读提交”,即事务在读取数据时,只能读取已经提交事务产生的数据。保证最低的读提交的好处**防止级联回滚 与 commit 语义冲突**。 577 | 578 | 实现读提交,为每个版本维护两个变量:XMIN 和 XMAX: 579 | 580 | * XMIN:创建该版本的事务编号 581 | * XMAX:删除该版本的事务编号 582 | 583 | XMIN 应当在创建版本时填写,而 XMAX 则在版本被删除,或者有新版本出现时填写。 584 | 585 | XMAX 这个变量,也就解释了为什么 DM 层不提供删除操作,当想删除一个版本时,只需要设置其 XMAX,这样,这个版本对每一个 XMAX 之后的事务都是不可见的,也就等价于删除了。 586 | 587 | 如此,在读提交下,版本对事务的可见性逻辑如下: 588 | 589 | ```java 590 | (XMIN == Ti and // 由Ti创建且 591 | XMAX == NULL // 还未被删除 592 | ) 593 | or // 或 594 | (XMIN is commited and // 由一个已提交的事务创建且 595 | (XMAX == NULL or // 尚未删除或 596 | (XMAX != Ti and XMAX is not commited) // 由一个未提交的事务删除 597 | )) 598 | ``` 599 | 600 | 若条件为 true,则版本对 Ti 可见。那么获取 Ti 适合的版本,只需要从最新版本开始,依次向前检查可见性,如果为 true,就可以直接返回。 601 | 602 | 以下方法判断某个记录对事务 t 是否可见: 603 | 604 | ```java 605 | private static boolean readCommitted(TransactionManager tm, Transaction t, Entry e) { 606 | long xid = t.xid; 607 | long xmin = e.getXmin(); 608 | long xmax = e.getXmax(); 609 | if(xmin == xid && xmax == 0) return true; 610 | 611 | if(tm.isCommitted(xmin)) { 612 | if(xmax == 0) return true; 613 | if(xmax != xid) { 614 | if(!tm.isCommitted(xmax)) { 615 | return true; 616 | } 617 | } 618 | } 619 | return false; 620 | } 621 | ``` 622 | 623 | 这里的 Transaction 结构只提供了一个 XID。 624 | 625 | #### 可重复读 626 | 627 | 读提交会导致的问题大家也都很清楚,八股也背了不少。那就是不可重复读和幻读。这里我们来解决不可重复读的问题。 628 | 629 | 不可重复度,会导致一个事务在执行期间对同一个数据项的读取得到不同结果。如下面的结果,加入 X 初始值为 0: 630 | 631 | ```java 632 | T1 begin 633 | R1(X) // T1 读得 0 634 | T2 begin 635 | U2(X) // 将 X 修改为 1 636 | T2 commit 637 | R1(X) // T1 读的 1 638 | ``` 639 | 640 | 可以看到,T1 两次读 X,读到的结果不一样。如果想要避免这个情况,就需要引入更严格的隔离级别,即可重复读(repeatable read)。 641 | 642 | T1 在第二次读取的时候,读到了已经提交的 T2 修改的值,导致了这个问题。于是我们可以规定: 643 | 644 | 事务只能读取它开始时, 就已经结束的那些事务产生的数据版本 645 | 646 | 这条规定,增加于,事务需要忽略: 647 | 648 | 1. 在本事务后开始的事务的数据; 649 | 2. 本事务开始时还是 active 状态的事务的数据 650 | 651 | 对于第一条,只需要比较事务 ID,即可确定。而对于第二条,则需要在事务 Ti 开始时,记录下当前活跃的所有事务 SP(Ti),如果记录的某个版本,XMIN 在 SP(Ti) 中,也应当对 Ti 不可见。 652 | 653 | 于是,可重复读的判断逻辑如下: 654 | 655 | ```java 656 | (XMIN == Ti and // 由Ti创建且 657 | (XMAX == NULL or // 尚未被删除 658 | )) 659 | or // 或 660 | (XMIN is commited and // 由一个已提交的事务创建且 661 | XMIN < XID and // 这个事务小于Ti且 662 | XMIN is not in SP(Ti) and // 这个事务在Ti开始前提交且 663 | (XMAX == NULL or // 尚未被删除或 664 | (XMAX != Ti and // 由其他事务删除但是 665 | (XMAX is not commited or // 这个事务尚未提交或 666 | XMAX > Ti or // 这个事务在Ti开始之后才开始或 667 | XMAX is in SP(Ti) // 这个事务在Ti开始前还未提交 668 | )))) 669 | ``` 670 | 671 | 于是,需要提供一个结构,来抽象一个事务,以保存快照数据: 672 | 673 | ```java 674 | public class Transaction { 675 | public long xid; 676 | public int level; 677 | public Map snapshot; 678 | public Exception err; 679 | public boolean autoAborted; 680 | 681 | public static Transaction newTransaction(long xid, int level, Map active) { 682 | Transaction t = new Transaction(); 683 | t.xid = xid; 684 | t.level = level; 685 | if(level != 0) { 686 | t.snapshot = new HashMap<>(); 687 | for(Long x : active.keySet()) { 688 | t.snapshot.put(x, true); 689 | } 690 | } 691 | return t; 692 | } 693 | 694 | public boolean isInSnapshot(long xid) { 695 | if(xid == TransactionManagerImpl.SUPER_XID) { 696 | return false; 697 | } 698 | return snapshot.containsKey(xid); 699 | } 700 | } 701 | ``` 702 | 703 | 构造方法中的 active,保存着当前所有 active 的事务。于是,可重复读的隔离级别下,一个版本是否对事务可见的判断如下: 704 | 705 | ```java 706 | private static boolean repeatableRead(TransactionManager tm, Transaction t, Entry e) { 707 | long xid = t.xid; 708 | long xmin = e.getXmin(); 709 | long xmax = e.getXmax(); 710 | if(xmin == xid && xmax == 0) return true; 711 | 712 | if(tm.isCommitted(xmin) && xmin < xid && !t.isInSnapshot(xmin)) { 713 | if(xmax == 0) return true; 714 | if(xmax != xid) { 715 | if(!tm.isCommitted(xmax) xmax > xid t.isInSnapshot(xmax)) { 716 | return true; 717 | } 718 | } 719 | } 720 | return false; 721 | } 722 | ``` 723 | 724 | ### 版本跳跃问题 725 | 726 | 根据可见性,每个事务只能看到其他 committed 的事务所产生的数据。aborted 事务产生的数据就不会对其他事务产生任何影响,也就相当于,这个事务不曾存在过。 727 | 728 | 版本跳跃问题,考虑如下的情况,假设 X 最初只有 x0 版本,T1 和 T2 都是可重复读的隔离级别: 729 | 730 | ```java 731 | T1 begin 732 | T2 begin 733 | R1(X) // T1读取x0 734 | R2(X) // T2读取x0 735 | U1(X) // T1将X更新到x1 736 | T1 commit 737 | U2(X) // T2将X更新到x2 738 | T2 commit 739 | ``` 740 | 741 | 这种情况实际运行起来是没问题的,但是逻辑上不太正确。T1 将 X 从 x0 更新为了 x1,这是没错的。但是 T2 则是将 X 从 x0 更新成了 x2,跳过了 x1 版本。 742 | 743 | **读提交是允许版本跳跃的,而可重复读则是不允许版本跳跃的。** 744 | 745 | 解决版本跳跃的思路:**如果 Ti 需要修改 X,而 X 已经被 Ti 不可见的事务 Tj 修改了,那么要求 Ti 回滚。** 746 | 747 | 1. XID(Tj) > XID(Ti) 748 | 2. Tj in SP(Ti) 749 | 750 | 于是版本跳跃的检查也就很简单了,取出要修改的数据 X 的最新提交版本,并检查该最新版本的创建者对当前事务是否可见: 751 | 752 | ```java 753 | public static boolean isVersionSkip(TransactionManager tm, Transaction t, Entry e) { 754 | long xmax = e.getXmax(); 755 | if(t.level == 0) { 756 | return false; 757 | } else { 758 | return tm.isCommitted(xmax) && (xmax > t.xid t.isInSnapshot(xmax)); 759 | } 760 | } 761 | ``` 762 | 763 | ### 死锁检测 764 | 765 | 2PL 会阻塞事务,直至持有锁的线程释放锁。可以将这种等待关系抽象成有向边,例如 Tj 在等待 Ti,就可以表示为 Tj –> Ti。这样,无数有向边就可以形成一个图(不一定是连通图)。检测死锁也就简单了,只需要查看这个图中是否有环即可。 766 | 767 | 使用LockTable对象,在内存中维护这张图。 768 | 769 | ```java 770 | public class LockTable { 771 | 772 | private Map> x2u; // 某个XID已经获得的资源的UID列表 773 | private Map u2x; // UID被某个XID持有 774 | private Map> wait; // 正在等待UID的XID列表 775 | private Map waitLock; // 正在等待资源的XID的锁 776 | private Map waitU; // XID正在等待的UID 777 | private Lock lock; 778 | 779 | ... 780 | } 781 | ``` 782 | 783 | 在每次出现等待的情况时,就尝试向图中增加一条边,并进行死锁检测。如果检测到死锁,就撤销这条边,不允许添加,并撤销该事务。 784 | 785 | ```java 786 | // 不需要等待则返回null,否则返回锁对象 787 | // 会造成死锁则抛出异常 788 | public Lock add(long xid, long uid) throws Exception { 789 | lock.lock(); 790 | try { 791 | if(isInList(x2u, xid, uid)) { 792 | return null; 793 | } 794 | if(!u2x.containsKey(uid)) { 795 | u2x.put(uid, xid); 796 | putIntoList(x2u, xid, uid); 797 | return null; 798 | } 799 | waitU.put(xid, uid); 800 | putIntoList(wait, xid, uid); 801 | if(hasDeadLock()) { 802 | waitU.remove(xid); 803 | removeFromList(wait, uid, xid); 804 | throw Error.DeadlockException; 805 | } 806 | Lock l = new ReentrantLock(); 807 | l.lock(); 808 | waitLock.put(xid, l); 809 | return l; 810 | } finally { 811 | lock.unlock(); 812 | } 813 | } 814 | ``` 815 | 816 | 调用 add,如果需要等待的话,会返回一个上了锁的 Lock 对象。调用方在获取到该对象时,需要尝试获取该对象的锁,由此实现阻塞线程的目的,例如: 817 | 818 | ```java 819 | Lock l = lt.add(xid, uid); 820 | if(l != null) { 821 | l.lock(); // 阻塞在这一步 822 | l.unlock(); 823 | } 824 | ``` 825 | 826 | 查找图中是否有环的算法也非常简单,就是一个深搜,只是需要注意这个图不一定是连通图。思路就是为每个节点设置一个访问戳,都初始化为 -1,随后遍历所有节点,以每个非 -1 的节点作为根进行深搜,并将深搜该连通图中遇到的所有节点都设置为同一个数字,不同的连通图数字不同。这样,如果在遍历某个图时,遇到了之前遍历过的节点,说明出现了环。 827 | 828 | ## IM 解析: 829 | 830 | ### 二叉树索引 831 | 832 | 二叉树由一个个 Node 组成,每个 Node 都存储在一条 DataItem 中。结构如下: 833 | 834 | ``` 835 | [LeafFlag][KeyNumber][SiblingUid] 836 | [Son0][Key0][Son1][Key1]...[SonN][KeyN] 837 | ``` 838 | 839 | 其中 LeafFlag 标记了该节点是否是个叶子节点;KeyNumber 为该节点中 key 的个数;SiblingUid 是其兄弟节点存储在 DM 中的 UID。后续是穿插的子节点(SonN)和 KeyN。最后的一个 KeyN 始终为 MAX_VALUE,以此方便查找。 840 | 841 | Node 类持有了其 B+ 树结构的引用,DataItem 的引用和 SubArray 的引用,用于方便快速修改数据和释放数据。 842 | 843 | ```java 844 | public class Node { 845 | BPlusTree tree; 846 | DataItem dataItem; 847 | SubArray raw; 848 | long uid; 849 | ... 850 | } 851 | ``` 852 | 853 | 于是生成一个根节点的数据可以写成如下: 854 | 855 | ```java 856 | static byte[] newRootRaw(long left, long right, long key) { 857 | SubArray raw = new SubArray(new byte[NODE_SIZE], 0, NODE_SIZE); 858 | setRawIsLeaf(raw, false); 859 | setRawNoKeys(raw, 2); 860 | setRawSibling(raw, 0); 861 | setRawKthSon(raw, left, 0); 862 | setRawKthKey(raw, key, 0); 863 | setRawKthSon(raw, right, 1); 864 | setRawKthKey(raw, Long.MAX_VALUE, 1); 865 | return raw.raw; 866 | } 867 | ``` 868 | 869 | ## TBM 解析: 870 | 871 | ### SQL 解析器 872 | 873 | Parser 实现了对类 SQL 语句的结构化解析,将语句中包含的信息封装为对应语句的类,这些类可见 top.guoziyang.mydb.backend.parser.statement 包。 874 | 875 | parser 包的 Tokenizer 类,对语句进行逐字节解析,根据空白符或者上述词法规则,将语句切割成多个 token。对外提供了 `peek()`、`pop()` 方法方便取出 Token 进行解析。切割的实现不赘述。 876 | 877 | Parser 类则直接对外提供了 `Parse(byte[] statement)` 方法,核心就是一个调用 Tokenizer 类分割 Token,并根据词法规则包装成具体的 Statement 类并返回。解析过程很简单,仅仅是根据第一个 Token 来区分语句类型,并分别处理,不再赘述。 878 | 879 | 虽然根据编译原理,词法分析应当写一个自动机去做的,但是又不是不能用。 880 | 881 | ### 字段与表管理 882 | 883 | 管理表和字段的数据结构,例如表名、表字段信息和字段索引等。 884 | 885 | 由于 TBM 基于 VM,单个字段信息和表信息都是直接保存在 Entry 中。字段的二进制表示如下: 886 | 887 | ```java 888 | [FieldName][TypeName][IndexUid] 889 | ``` 890 | 891 | 这里 FieldName 和 TypeName,以及后面的表明,存储的都是字节形式的字符串。这里规定一个字符串的存储方式,以明确其存储边界。 892 | 893 | ```java 894 | [StringLength][StringData] 895 | ``` 896 | 897 | TypeName 为字段的类型,限定为 int32、int64 和 string 类型。如果这个字段有索引,那么 IndexUID 指向这个索引二叉树的根,否则该字段为0。 898 | 899 | 根据这个结构,通过一个 UID 从 VM 中读取并解析如下: 900 | 901 | ```java 902 | public static Field loadField(Table tb, long uid) { 903 | byte[] raw = null; 904 | try { 905 | raw = ((TableManagerImpl)tb.tbm).vm.read(TransactionManagerImpl.SUPER_XID, uid); 906 | } catch (Exception e) { 907 | Panic.panic(e); 908 | } 909 | assert raw != null; 910 | return new Field(uid, tb).parseSelf(raw); 911 | } 912 | 913 | private Field parseSelf(byte[] raw) { 914 | int position = 0; 915 | ParseStringRes res = Parser.parseString(raw); 916 | fieldName = res.str; 917 | position += res.next; 918 | res = Parser.parseString(Arrays.copyOfRange(raw, position, raw.length)); 919 | fieldType = res.str; 920 | position += res.next; 921 | this.index = Parser.parseLong(Arrays.copyOfRange(raw, position, position+8)); 922 | if(index != 0) { 923 | try { 924 | bt = BPlusTree.load(index, ((TableManagerImpl)tb.tbm).dm); 925 | } catch(Exception e) { 926 | Panic.panic(e); 927 | } 928 | } 929 | return this; 930 | } 931 | ``` 932 | 933 | 创建一个字段的方法类似,将相关的信息通过 VM 持久化即可: 934 | 935 | ```java 936 | private void persistSelf(long xid) throws Exception { 937 | byte[] nameRaw = Parser.string2Byte(fieldName); 938 | byte[] typeRaw = Parser.string2Byte(fieldType); 939 | byte[] indexRaw = Parser.long2Byte(index); 940 | this.uid = ((TableManagerImpl)tb.tbm).vm.insert(xid, Bytes.concat(nameRaw, typeRaw, indexRaw)); 941 | } 942 | ``` 943 | 944 | 一个数据库中存在多张表,TBM 使用链表的形式将其组织起来,每一张表都保存一个指向下一张表的 UID。表的二进制结构如下: 945 | 946 | ```java 947 | [TableName][NextTable] 948 | [Field1Uid][Field2Uid]...[FieldNUid] 949 | ``` 950 | 951 | 这里由于每个 Entry 中的数据,字节数是确定的,于是无需保存字段的个数。根据 UID 从 Entry 中读取表数据的过程和读取字段的过程类似。 952 | 953 | 对表和字段的操作,有一个很重要的步骤,就是计算 Where 条件的范围,目前 MYDB 的 Where 只支持两个条件的与和或。例如有条件的 Delete,计算 Where,最终就需要获取到条件范围内所有的 UID。MYDB 只支持已索引字段作为 Where 的条件。计算 Where 的范围,具体可以查看 Table 的 `parseWhere()` 和 `calWhere()` 方法,以及 Field 类的 `calExp()` 方法。 954 | 955 | 由于 TBM 的表管理,使用的是链表串起的 Table 结构,所以就必须保存一个链表的头节点,即第一个表的 UID,这样在 MYDB 启动时,才能快速找到表信息。 956 | 957 | MYDB 使用 Booter 类和 bt 文件,来管理 MYDB 的启动信息,虽然现在所需的启动信息,只有一个:头表的 UID。Booter 类对外提供了两个方法:load 和 update,并保证了其原子性。update 在修改 bt 文件内容时,没有直接对 bt 文件进行修改,而是首先将内容写入一个 bt_tmp 文件中,随后将这个文件重命名为 bt 文件。以期通过操作系统重命名文件的原子性,来保证操作的原子性。 958 | 959 | ```java 960 | public void update(byte[] data) { 961 | File tmp = new File(path + BOOTER_TMP_SUFFIX); 962 | try { 963 | tmp.createNewFile(); 964 | } catch (Exception e) { 965 | Panic.panic(e); 966 | } 967 | if(!tmp.canRead() !tmp.canWrite()) { 968 | Panic.panic(Error.FileCannotRWException); 969 | } 970 | try(FileOutputStream out = new FileOutputStream(tmp)) { 971 | out.write(data); 972 | out.flush(); 973 | } catch(IOException e) { 974 | Panic.panic(e); 975 | } 976 | try { 977 | Files.move(tmp.toPath(), new File(path+BOOTER_SUFFIX).toPath(), StandardCopyOption.REPLACE_EXISTING); 978 | } catch(IOException e) { 979 | Panic.panic(e); 980 | } 981 | file = new File(path+BOOTER_SUFFIX); 982 | if(!file.canRead() !file.canWrite()) { 983 | Panic.panic(Error.FileCannotRWException); 984 | } 985 | } 986 | ``` 987 | 988 | 989 | 990 | ## Transport: 991 | 992 | 将SQL语句和错误数据一起打包成一个package,然后packager调用Transporter将这个package通过socket连接进行发送和接收。encoder的编解码主要就是对sql语句里面的异常进行封包和解包处理工作。 993 | 994 | ## Server: 995 | 从服务端的Launcher入口进去,新建数据库或者打开存在的数据库,这里就要开启前面的模块了(tm、dm、vm、tbm); 然后开启Server,Server启动一个 ServerSocket 监听端口,当有请求到来时直接把请求丢给一个新线程HandleSocket处理。HandleSocket初始化一个Packager,循环接收来自客户端的数据并交给Executor处理,再将处理结果打包成Package通过Packager.send(packge)发送出去。Executor就是SQL语句执行的核心,调用 Parser 获取到对应语句的结构化信息对象,并根据对象的类型,调用 TBM 的不同方法进行处理。 996 | 997 | ## Client: 998 | 从客户端的Launcher入口进去,主要就是链接服务器,打开一个Shell类;shell类完成对用户输入的获取并调用client.execute()执行语句,还有关闭退出客户端功能。client就一个主要方法execute() ,接收 shell 发过来的sql语句,并打包成pkg进行单次收发操作roundTrip(),得到执行结果并返回; 999 | 1000 | ## read 语句的流程 1001 | 假设现在要执行 read * from student where id = 123456789 ,并且在 id 上已经建有索引,执行过程如下: 1002 | 1. TBM 接受语句,进行解析。 1003 | 2. TBM 调用 IM 的 search 方法,查找对应记录所在的地址。 1004 | 3. TBM 调用 VM 的 read 方法,并将地址作为参数,从VM中尝试读取记录内容。 1005 | 4. VM 通过 DM 的 read 方法,读取该条记录的最新版本。 1006 | 5. VM 检测该版本是否对该事务可见,其中需要 Visibility.isVisible() 方法。 1007 | 6. 如果可见,则返回该版本的数据。 1008 | 7. 如果不可见,则读取上一个版本,并重复 5,6,7 步骤。 1009 | 8. TBM 取得记录的二进制内容后,对其进行解析,还原出记录内容。 1010 | 9. TBM 将记录的内容返回给客户端。 1011 | 1012 | ## insert 语句的流程 1013 | 假设现在要执行 insert into student values ("zhangsan", 123456789) 这条语句。 1014 | 执行过程如下: 1015 | 1. TBM 接收语句,并进行解析。 1016 | 2. TBM 将 values 的值,二进制化。 1017 | 3. TBM 利用 VM 的 insert 操作,将二进制化后的数据,插入到数据库。 1018 | 4. VM 为该条数据建立版本控制,并利用 DM 的 insert方法,将数据插入到数据库。 1019 | 5. DM将数据插入到数据库,并返回其被存储的地址。 1020 | 6. VM 将得到的地址,作为该条记录的 handler, 返回给 TBM。 1021 | 7. TBM 计算该条语句的 key,并将 handler 作为 data,并调用 IM 的 insert,建立索引。 1022 | 8. IM 利用 DM 提供的 read 和 insert 等操作,将 key 和 data 存入索引中。 1023 | 9. TBM 返回客户端插入成功的信息。 1024 | 1025 | ## 运行方式 1026 | 注意首先需要在 pom.xml 中调整编译版本,如果导入 IDE,请更改项目的编译版本以适应你的 JDK 1027 | 1028 | 首先执行以下命令编译源码: 1029 | 1030 | ``` 1031 | mvn compile 1032 | ``` 1033 | 接着执行以下命令以 /tmp/mydb 作为路径创建数据库: 1034 | 1035 | ``` 1036 | mvn exec:java -Dexec.mainClass="cn.edu.gzhu.server.Launcher" -Dexec.args="-create /tmp/mydb" 1037 | ``` 1038 | 随后通过以下命令以默认参数启动数据库服务: 1039 | ``` 1040 | mvn exec:java -Dexec.mainClass="cn.edu.gzhu.server.Launcher" -Dexec.args="-open /tmp/mydb" 1041 | ``` 1042 | 这时数据库服务就已经启动在本机的 9999 端口。重新启动一个终端,执行以下命令启动客户端连接数据库: 1043 | 1044 | ``` 1045 | mvn exec:java -Dexec.mainClass="cn.edu.gzhu.client.Launcher" 1046 | ``` 1047 | 会启动一个交互式命令行,就可以在这里输入类 SQL 语法,回车会发送语句到服务,并输出执行的结果。 1048 | 1049 | ## 致谢 1050 | https://github.com/CN-GuoZiyang -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | cn.edu.gzhu 6 | JCSQL 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | JCSQL 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 1.8 16 | 8 17 | 8 18 | 19 | 20 | 21 | 22 | junit 23 | junit 24 | 4.13.2 25 | test 26 | 27 | 28 | com.google.guava 29 | guava 30 | 31.0.1-jre 31 | 32 | 33 | com.google.code.gson 34 | gson 35 | 2.8.9 36 | 37 | 38 | commons-codec 39 | commons-codec 40 | 1.15 41 | 42 | 43 | commons-cli 44 | commons-cli 45 | 1.5.0 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/common/AbstractCache.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.common; 2 | 3 | import java.util.HashMap; 4 | import java.util.Set; 5 | import java.util.concurrent.locks.Lock; 6 | import java.util.concurrent.locks.ReentrantLock; 7 | 8 | import cn.edu.gzhu.backend.utils.Panic; 9 | import cn.edu.gzhu.common.Error; 10 | 11 | /** 12 | * AbstractCache 实现了一个引用计数策略的缓存 13 | */ 14 | public abstract class AbstractCache { 15 | // 实际缓存的数据 16 | private HashMap cache; 17 | // 元素的引用个数 18 | private HashMap references; 19 | // 正在获取某资源的线程 20 | private HashMap getting; 21 | 22 | // 缓存的最大缓存资源数 23 | private int maxResource; 24 | // 缓存中元素的个数 25 | private int count = 0; 26 | private Lock lock; 27 | 28 | public AbstractCache(int maxResource){ 29 | this.maxResource = maxResource; 30 | cache = new HashMap<>(); 31 | references = new HashMap<>(); 32 | getting = new HashMap<>(); 33 | lock = new ReentrantLock(); 34 | } 35 | 36 | protected T get(long key) throws Exception{ 37 | while (true) { 38 | lock.lock(); 39 | if(getting.containsKey(key)){ 40 | // 请求的资源正在被其他线程获取 41 | lock.unlock(); 42 | try{ 43 | Thread.sleep(1); 44 | } catch (InterruptedException e){ 45 | e.printStackTrace(); 46 | continue; 47 | } 48 | continue; 49 | } 50 | if(cache.containsKey(key)){ 51 | // 资源在缓存中,直接返回 52 | T obj = cache.get(key); 53 | references.put(key, references.get(key) + 1); 54 | lock.unlock(); 55 | return obj; 56 | } 57 | // 尝试获取该资源 58 | if(maxResource > 0 && count == maxResource){ 59 | lock.unlock(); 60 | throw Error.CacheFullException; 61 | } 62 | count++; 63 | getting.put(key, true); 64 | lock.unlock(); 65 | break; 66 | } 67 | T obj = null; 68 | try { 69 | obj = getForCache(key); 70 | } catch (Exception e){ 71 | lock.lock(); 72 | count --; 73 | getting.remove(key); 74 | lock.unlock(); 75 | throw e; 76 | } 77 | lock.lock(); 78 | getting.remove(key); 79 | cache.put(key, obj); 80 | references.put(key, 1); 81 | lock.unlock(); 82 | return obj; 83 | } 84 | 85 | /** 86 | * 强行释放一个缓存 87 | */ 88 | protected void release(long key) { 89 | lock.lock(); 90 | try { 91 | int ref = references.get(key) - 1; 92 | if(ref == 0){ 93 | T obj = cache.get(key); 94 | releaseForCache(obj); 95 | references.remove(key); 96 | cache.remove(key); 97 | count --; 98 | } else { 99 | references.put(key, ref); 100 | } 101 | } catch (Exception e){ 102 | Panic.panic(e); 103 | } finally { 104 | lock.unlock(); 105 | } 106 | } 107 | 108 | /** 109 | * 关闭缓存,写回所有资源 110 | */ 111 | protected void close(){ 112 | lock.lock(); 113 | try{ 114 | Set keys = cache.keySet(); 115 | for (Long key : keys) { 116 | T obj = cache.get(key); 117 | releaseForCache(obj); 118 | references.remove(key); 119 | cache.remove(key); 120 | } 121 | } catch (Exception e){ 122 | Panic.panic(e); 123 | } finally { 124 | lock.unlock(); 125 | } 126 | } 127 | 128 | /** 129 | * 当资源不在缓存时的获取行为 130 | */ 131 | protected abstract T getForCache(long key) throws Exception; 132 | 133 | /** 134 | * 当资源被驱除时的写回行为 135 | */ 136 | protected abstract void releaseForCache(T obj); 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/common/SubArray.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.common; 2 | 3 | public class SubArray { 4 | public byte[] raw; 5 | public int start; 6 | public int end; 7 | 8 | public SubArray(byte[] raw, int start, int end) { 9 | this.raw = raw; 10 | this.start = start; 11 | this.end = end; 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/DataManager.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm; 2 | 3 | import cn.edu.gzhu.backend.dm.dataItem.DataItem; 4 | import cn.edu.gzhu.backend.dm.logger.Logger; 5 | import cn.edu.gzhu.backend.dm.page.PageCache; 6 | import cn.edu.gzhu.backend.dm.page.PageOne; 7 | import cn.edu.gzhu.backend.tm.TransactionManager; 8 | 9 | /** 10 | * DM的主要职责有: 11 | * 1)分页管理 DB 文件,并进行缓存。 12 | * 2)管理日志文件,保证在发生错误时可以根据日志进行回复。 13 | * 3)抽象 DB 文件为 DataItem 供上层模块使用,并提供缓存。 14 | * 可以归纳为以下两点:1. 上层模块和文件系统之间的抽象层,向下直接读取文件,向上提供数据的包装。 2. 日志功能 15 | * 16 | * 引入引用计数策略:只有上层模块主动释放引用,缓存在确保没有模块在使用这个资源了,才回去驱逐资源。 17 | */ 18 | public interface DataManager { 19 | DataItem read(long uid) throws Exception; 20 | long insert(long xid, byte[] data) throws Exception; 21 | void close(); 22 | 23 | public static DataManager create(String path, long memory, TransactionManager tm){ 24 | PageCache pc = PageCache.create(path, memory); 25 | Logger logger = Logger.create(path); 26 | DataManagerImpl dm = new DataManagerImpl(pc, logger, tm); 27 | dm.initPageOne(); 28 | return dm; 29 | } 30 | 31 | public static DataManager open(String path, long memory, TransactionManager tm){ 32 | PageCache pc = PageCache.open(path, memory); 33 | Logger logger = Logger.open(path); 34 | DataManagerImpl dm = new DataManagerImpl(pc, logger, tm); 35 | if(!dm.loadCheckPageOne()){ 36 | Recover.recover(tm, logger, pc); 37 | } 38 | dm.fillPageIndex(); 39 | PageOne.setVcOpen(dm.pageOne); 40 | dm.pc.flushPage(dm.pageOne); 41 | return dm; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/DataManagerImpl.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm; 2 | 3 | import cn.edu.gzhu.backend.common.AbstractCache; 4 | import cn.edu.gzhu.backend.dm.dataItem.DataItem; 5 | import cn.edu.gzhu.backend.dm.dataItem.impl.DataItemImpl; 6 | import cn.edu.gzhu.backend.dm.logger.Logger; 7 | import cn.edu.gzhu.backend.dm.page.Page; 8 | import cn.edu.gzhu.backend.dm.page.PageCache; 9 | import cn.edu.gzhu.backend.dm.page.PageOne; 10 | import cn.edu.gzhu.backend.dm.page.PageX; 11 | import cn.edu.gzhu.backend.dm.pageIndex.PageIndex; 12 | import cn.edu.gzhu.backend.dm.pageIndex.PageInfo; 13 | import cn.edu.gzhu.backend.tm.TransactionManager; 14 | import cn.edu.gzhu.backend.utils.Panic; 15 | import cn.edu.gzhu.backend.utils.Types; 16 | import cn.edu.gzhu.common.Error; 17 | 18 | public class DataManagerImpl extends AbstractCache implements DataManager{ 19 | TransactionManager tm; 20 | PageCache pc; 21 | Logger logger; 22 | PageIndex pageIndex; 23 | Page pageOne; 24 | 25 | public DataManagerImpl(PageCache pc, Logger logger, TransactionManager tm) { 26 | super(0); 27 | this.pc = pc; 28 | this.logger = logger; 29 | this.tm = tm; 30 | this.pageIndex = new PageIndex(); 31 | } 32 | 33 | @Override 34 | public DataItem read(long uid) throws Exception { 35 | DataItemImpl dataItem = (DataItemImpl) super.get(uid); 36 | if(!dataItem.isValid()){ 37 | dataItem.release(); 38 | return null; 39 | } 40 | return dataItem; 41 | } 42 | 43 | @Override 44 | public long insert(long xid, byte[] data) throws Exception { 45 | byte[] raw = DataItem.wrapDataItemRaw(data); 46 | if(raw.length > PageX.MAX_FREE_SPACE) { 47 | throw Error.DataTooLargeException; 48 | } 49 | PageInfo pageInfo = null; 50 | for (int i = 0; i < 5; i++) { 51 | pageInfo = pageIndex.select(raw.length); 52 | if(pageInfo != null){ 53 | break; 54 | } else { 55 | int newPageNum = pc.newPage(PageX.initRaw()); 56 | pageIndex.add(newPageNum, PageX.MAX_FREE_SPACE); 57 | } 58 | } 59 | if(pageInfo == null){ 60 | throw Error.DatabaseBusyException; 61 | } 62 | Page page = null; 63 | int freeSpace = 0; 64 | try { 65 | page = pc.getPage(pageInfo.pageNum); 66 | byte[] log = Recover.insertLog(xid, page, raw); 67 | logger.log(log); 68 | 69 | short offset = PageX.insert(page, raw); 70 | 71 | page.release(); 72 | return Types.addressToUid(pageInfo.pageNum, offset); 73 | } finally { 74 | // 将取出的 page 重新插入 pageIndex 75 | if(page != null){ 76 | pageIndex.add(pageInfo.pageNum, PageX.getFreeSpace(page)); 77 | } else { 78 | pageIndex.add(pageInfo.pageNum, freeSpace); 79 | } 80 | } 81 | } 82 | 83 | @Override 84 | public void close() { 85 | super.close(); 86 | logger.close(); 87 | 88 | PageOne.setVcClose(pageOne); 89 | pageOne.release(); 90 | pc.close(); 91 | } 92 | 93 | // 为 xid 生成 update 日志 94 | public void logDataItem(long xid, DataItem dataItem) { 95 | byte[] log = Recover.updateLog(xid, dataItem); 96 | logger.log(log); 97 | } 98 | 99 | public void releaseDataItem(DataItem dataItem){ 100 | super.release(dataItem.getUid()); 101 | } 102 | 103 | @Override 104 | protected DataItem getForCache(long uid) throws Exception { 105 | short offset = (short) (uid & ((1L << 16) - 1)); 106 | uid >>>= 32; 107 | int pageNum = (int) (uid & ((1L << 32) - 1)); 108 | Page page = pc.getPage(pageNum); 109 | return DataItem.parserDataItem(page, offset, this); 110 | } 111 | 112 | @Override 113 | protected void releaseForCache(DataItem dataItem) { 114 | dataItem.page().release(); 115 | } 116 | 117 | // 在创建文件时初始化 PageOne 118 | void initPageOne() { 119 | int pageNum = pc.newPage(PageOne.initRaw()); 120 | assert pageNum == 1; 121 | try { 122 | pageOne = pc.getPage(pageNum); 123 | } catch (Exception e) { 124 | Panic.panic(e); 125 | } 126 | pc.flushPage(pageOne); 127 | } 128 | 129 | // 在打开已有文件时读入 pageOne, 并验证正确性 130 | boolean loadCheckPageOne(){ 131 | try { 132 | pageOne = pc.getPage(1); 133 | } catch (Exception e){ 134 | Panic.panic(e); 135 | } 136 | return PageOne.checkVc(pageOne); 137 | } 138 | 139 | // 初始化 pageIndex 140 | void fillPageIndex(){ 141 | int pageNumber = pc.getPageNumber(); 142 | for (int i = 2; i <= pageNumber; i++) { 143 | Page page = null; 144 | try { 145 | page = pc.getPage(i); 146 | } catch (Exception e){ 147 | Panic.panic(e); 148 | } 149 | pageIndex.add(page.getPageNumber(), PageX.getFreeSpace(page)); 150 | page.release(); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/Recover.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm; 2 | 3 | import cn.edu.gzhu.backend.common.SubArray; 4 | import cn.edu.gzhu.backend.dm.dataItem.DataItem; 5 | import cn.edu.gzhu.backend.dm.logger.Logger; 6 | import cn.edu.gzhu.backend.dm.page.Page; 7 | import cn.edu.gzhu.backend.dm.page.PageCache; 8 | import cn.edu.gzhu.backend.dm.page.PageX; 9 | import cn.edu.gzhu.backend.tm.TransactionManager; 10 | import cn.edu.gzhu.backend.utils.Panic; 11 | import cn.edu.gzhu.backend.utils.Parser; 12 | import com.google.common.primitives.Bytes; 13 | import org.checkerframework.checker.units.qual.A; 14 | 15 | import java.util.*; 16 | 17 | /** 18 | * 在进行 插入和更新 操作之前,必须先进行对于的日志操作,在保证日志写入磁盘后,才进行数据操作。 19 | * 这个日志策略,使得 DM 对于数据操作的磁盘同步,可以更加随意。 20 | * 日志在操作数据之前,保证到达了磁盘,那么即使该数据操作最后没有来得及同步到磁盘,数据库就发生了崩溃,后续也可以通过磁盘上的日志恢复该数据。 21 | * 22 | * 为了保证数据的可恢复,VM 层传递到 DM 的操作序列需要满足以下两个规则: 23 | * 规定 1:正在进行的事务,不会读取其他任何未提交的事务产生的数据。 24 | * 规定 2:正在进行的事务,不会修改其他任何未提交的事务修改或产生的数据。 25 | */ 26 | public class Recover { 27 | private static final byte LOG_TYPE_INSERT = 0; 28 | private static final byte LOG_TYPE_UPDATE = 1; 29 | 30 | private static final int REDO = 0; 31 | private static final int UNDO = 1; 32 | 33 | static class InsertLogInfo { 34 | long xid; 35 | int pageNum; 36 | short offset; 37 | byte[] raw; 38 | } 39 | 40 | static class UpdateLogInfo { 41 | long xid; 42 | int pageNum; 43 | short offset; 44 | byte[] oldRaw; 45 | byte[] newRaw; 46 | } 47 | 48 | public static void recover(TransactionManager tm, Logger logger, PageCache pageCache){ 49 | System.out.println("Recovering ..."); 50 | logger.rewind(); 51 | int maxPageNum = 0; 52 | while (true) { 53 | byte[] log = logger.next(); 54 | if(log == null) break; 55 | int pageNum; 56 | if(isInsertLog(log)){ 57 | InsertLogInfo insertLogInfo = parseInsertLog(log); 58 | pageNum = insertLogInfo.pageNum; 59 | } else { 60 | UpdateLogInfo updateLogInfo = parseUpdateLog(log); 61 | pageNum = updateLogInfo.pageNum; 62 | } 63 | if(pageNum > maxPageNum){ 64 | maxPageNum = pageNum; 65 | } 66 | } 67 | if(maxPageNum == 0){ 68 | maxPageNum = 1; 69 | } 70 | pageCache.truncateByPageNum(maxPageNum); 71 | System.out.println("Truncate to " + maxPageNum + " pages."); 72 | 73 | redoTransactions(tm, logger, pageCache); 74 | System.out.println("Redo Transactions Over."); 75 | 76 | undoTransactions(tm, logger, pageCache); 77 | System.out.println("Undo Transactions Over."); 78 | 79 | System.out.println("Recovery Over."); 80 | } 81 | 82 | private static void redoTransactions(TransactionManager tm, Logger logger, PageCache pageCache) { 83 | logger.rewind(); 84 | while (true) { 85 | byte[] log = logger.next(); 86 | if(log == null) break; 87 | if(isInsertLog(log)){ 88 | InsertLogInfo insertLogInfo = parseInsertLog(log); 89 | long xid = insertLogInfo.xid; 90 | if(!tm.isActive(xid)){ 91 | doInsertLog(pageCache, log, REDO); 92 | } 93 | } else{ 94 | UpdateLogInfo updateLogInfo = parseUpdateLog(log); 95 | long xid = updateLogInfo.xid; 96 | if(!tm.isAborted(xid)){ 97 | doUpdateLog(pageCache, log, REDO); 98 | } 99 | } 100 | } 101 | } 102 | 103 | private static void undoTransactions(TransactionManager tm, Logger logger, PageCache pageCache) { 104 | Map> logCache = new HashMap<>(); 105 | logger.rewind(); 106 | while (true) { 107 | byte[] log = logger.next(); 108 | if(log == null) break; 109 | if(isInsertLog(log)){ 110 | InsertLogInfo insertLogInfo = parseInsertLog(log); 111 | long xid = insertLogInfo.xid; 112 | if(tm.isActive(xid)) { 113 | if(!logCache.containsKey(xid)){ 114 | logCache.put(xid, new ArrayList<>()); 115 | } 116 | logCache.get(xid).add(log); 117 | } 118 | } else { 119 | UpdateLogInfo updateLogInfo = parseUpdateLog(log); 120 | long xid = updateLogInfo.xid; 121 | if(tm.isActive(xid)){ 122 | if(!logCache.containsKey(xid)){ 123 | logCache.put(xid, new ArrayList<>()); 124 | } 125 | logCache.get(xid).add(log); 126 | } 127 | } 128 | } 129 | 130 | // 对所有 active log 进行倒序 undo 131 | for (Map.Entry> entry : logCache.entrySet()) { 132 | List logs = entry.getValue(); 133 | for (int i = logs.size() - 1; i >= 0 ; i--) { 134 | byte[] log = logs.get(i); 135 | if(isInsertLog(log)){ 136 | doInsertLog(pageCache, log, UNDO); 137 | } else { 138 | doUpdateLog(pageCache, log, UNDO); 139 | } 140 | } 141 | tm.abort(entry.getKey()); 142 | } 143 | } 144 | 145 | // [LogType] [XID] [UID] [OldRaw] [NewRaw] 146 | private static final int OF_TYPE = 0; 147 | private static final int OF_XID = OF_TYPE + 1; 148 | private static final int OF_UPDATE_UID = OF_XID + 8; 149 | private static final int OF_UPDATE_RAW = OF_UPDATE_UID + 8; 150 | 151 | public static byte[] updateLog(long xid, DataItem dataItem){ 152 | byte[] logType = {LOG_TYPE_UPDATE}; 153 | byte[] xidRaw = Parser.long2Byte(xid); 154 | byte[] uidRaw = Parser.long2Byte(dataItem.getUid()); 155 | byte[] oldRaw = dataItem.getOldRaw(); 156 | SubArray raw = dataItem.getRaw(); 157 | byte[] newRaw = Arrays.copyOfRange(raw.raw, raw.start, raw.end); 158 | return Bytes.concat(logType, xidRaw, uidRaw, oldRaw, newRaw); 159 | } 160 | 161 | private static UpdateLogInfo parseUpdateLog(byte[] log) { 162 | UpdateLogInfo updateLogInfo = new UpdateLogInfo(); 163 | updateLogInfo.xid = Parser.parseLong(Arrays.copyOfRange(log, OF_XID, OF_UPDATE_UID)); 164 | long uid = Parser.parseLong(Arrays.copyOfRange(log, OF_UPDATE_UID, OF_UPDATE_RAW)); 165 | updateLogInfo.offset = (short) (uid & ((1L << 16) - 1)); 166 | uid >>>= 32; 167 | updateLogInfo.pageNum = (int)(uid & ((1L << 32) - 1)); 168 | int length = (log.length - OF_UPDATE_RAW) / 2; 169 | updateLogInfo.oldRaw = Arrays.copyOfRange(log, OF_UPDATE_RAW, OF_UPDATE_RAW + length); 170 | updateLogInfo.newRaw = Arrays.copyOfRange(log, OF_UPDATE_RAW + length, OF_UPDATE_RAW + length * 2); 171 | return updateLogInfo; 172 | } 173 | 174 | private static void doUpdateLog(PageCache pageCache, byte[] log, int flag) { 175 | int pageNum; 176 | short offset; 177 | byte[] raw; 178 | if(flag == REDO){ 179 | UpdateLogInfo updateLogInfo = parseUpdateLog(log); 180 | pageNum = updateLogInfo.pageNum; 181 | offset = updateLogInfo.offset; 182 | raw = updateLogInfo.newRaw; 183 | } else { 184 | UpdateLogInfo updateLogInfo = parseUpdateLog(log); 185 | pageNum = updateLogInfo.pageNum; 186 | offset = updateLogInfo.offset; 187 | raw = updateLogInfo.oldRaw; 188 | } 189 | Page page = null; 190 | try { 191 | page = pageCache.getPage(pageNum); 192 | } catch (Exception e){ 193 | Panic.panic(e); 194 | } 195 | try { 196 | PageX.recoverUpdate(page, raw, offset); 197 | } finally { 198 | page.release(); 199 | } 200 | } 201 | 202 | // [LogType] [XID] [PageNum] [Offset] [Raw] 203 | private static final int OF_INSERT_PAGE_NUM = OF_XID + 8; 204 | private static final int OF_INSERT_OFFSET = OF_INSERT_PAGE_NUM + 4; 205 | private static final int OF_INSERT_RAW = OF_INSERT_OFFSET + 2; 206 | 207 | public static byte[] insertLog(long xid, Page page, byte[] raw){ 208 | byte[] logTypeRaw = {LOG_TYPE_INSERT}; 209 | byte[] xidRaw = Parser.long2Byte(xid); 210 | byte[] pageNumRaw = Parser.int2Byte(page.getPageNumber()); 211 | byte[] offsetRaw = Parser.short2Byte(PageX.getFSO(page)); 212 | return Bytes.concat(logTypeRaw, xidRaw, pageNumRaw, offsetRaw, raw); 213 | } 214 | 215 | private static InsertLogInfo parseInsertLog(byte[] log) { 216 | InsertLogInfo insertLogInfo = new InsertLogInfo(); 217 | insertLogInfo.xid = Parser.parseLong(Arrays.copyOfRange(log, OF_XID, OF_INSERT_PAGE_NUM)); 218 | insertLogInfo.pageNum = Parser.parseInt(Arrays.copyOfRange(log, OF_INSERT_PAGE_NUM, OF_INSERT_OFFSET)); 219 | insertLogInfo.offset = Parser.parseShort(Arrays.copyOfRange(log, OF_INSERT_OFFSET, OF_INSERT_RAW)); 220 | insertLogInfo.raw = Arrays.copyOfRange(log, OF_INSERT_RAW, log.length); 221 | return insertLogInfo; 222 | } 223 | 224 | private static boolean isInsertLog(byte[] log) { 225 | return log[0] == LOG_TYPE_INSERT; 226 | } 227 | 228 | private static void doInsertLog(PageCache pageCache, byte[] log, int flag) { 229 | InsertLogInfo insertLogInfo = parseInsertLog(log); 230 | Page page = null; 231 | try { 232 | page = pageCache.getPage(insertLogInfo.pageNum); 233 | } catch (Exception e){ 234 | Panic.panic(e); 235 | } 236 | try { 237 | if(flag == UNDO){ 238 | DataItem.setDataItemRawInvalid(insertLogInfo.raw); 239 | } 240 | PageX.recoverInsert(page, insertLogInfo.raw, insertLogInfo.offset); 241 | } finally { 242 | page.release(); 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/dataItem/DataItem.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm.dataItem; 2 | 3 | import cn.edu.gzhu.backend.common.SubArray; 4 | import cn.edu.gzhu.backend.dm.DataManagerImpl; 5 | import cn.edu.gzhu.backend.dm.dataItem.impl.DataItemImpl; 6 | import cn.edu.gzhu.backend.dm.page.Page; 7 | import cn.edu.gzhu.backend.utils.Parser; 8 | import cn.edu.gzhu.backend.utils.Types; 9 | import com.google.common.primitives.Bytes; 10 | 11 | import java.util.Arrays; 12 | 13 | public interface DataItem { 14 | SubArray data(); 15 | 16 | void before(); 17 | 18 | void unBefore(); 19 | 20 | void after(long xid); 21 | 22 | void release(); 23 | 24 | void lock(); 25 | 26 | void unlock(); 27 | 28 | void rLock(); 29 | 30 | void rUnLock(); 31 | 32 | Page page(); 33 | 34 | long getUid(); 35 | 36 | byte[] getOldRaw(); 37 | 38 | SubArray getRaw(); 39 | 40 | public static byte[] wrapDataItemRaw(byte[] raw){ 41 | byte[] valid = new byte[1]; 42 | byte[] size = Parser.short2Byte((short) raw.length); 43 | return Bytes.concat(valid, size, raw); 44 | } 45 | 46 | // 从页面的 offset 处解析出 dataItem 47 | public static DataItem parserDataItem(Page page, short offset, DataManagerImpl dataManager){ 48 | byte[] raw = page.getData(); 49 | short size = Parser.parseShort(Arrays.copyOfRange(raw, offset+DataItemImpl.OF_SIZE, offset+DataItemImpl.OF_DATA)); 50 | short length = (short)(size + DataItemImpl.OF_DATA); 51 | long uid = Types.addressToUid(page.getPageNumber(), offset); 52 | return new DataItemImpl(new SubArray(raw, offset, offset+length), new byte[length], page, uid, dataManager); 53 | } 54 | 55 | public static void setDataItemRawInvalid(byte[] raw){ 56 | raw[DataItemImpl.OF_VALID] = (byte) 1; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/dataItem/impl/DataItemImpl.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm.dataItem.impl; 2 | 3 | import cn.edu.gzhu.backend.common.SubArray; 4 | import cn.edu.gzhu.backend.dm.DataManagerImpl; 5 | import cn.edu.gzhu.backend.dm.dataItem.DataItem; 6 | import cn.edu.gzhu.backend.dm.page.Page; 7 | 8 | import java.util.concurrent.locks.Lock; 9 | import java.util.concurrent.locks.ReadWriteLock; 10 | import java.util.concurrent.locks.ReentrantReadWriteLock; 11 | 12 | /** 13 | * dataItem 结构如下: 14 | * [ValidFlag] [DataSize] [Data] 15 | * ValidFlag 1字节,0为合法,1为非法 16 | * DataSize 2字节,标识Data的长度 17 | */ 18 | public class DataItemImpl implements DataItem { 19 | public static final int OF_VALID = 0; 20 | public static final int OF_SIZE = 1; 21 | public static final int OF_DATA = 3; 22 | 23 | private SubArray raw; 24 | private byte[] oldRaw; 25 | private Lock rLock; 26 | private Lock wLock; 27 | 28 | private DataManagerImpl dm; 29 | 30 | private long uid; 31 | 32 | private Page page; 33 | 34 | public DataItemImpl(SubArray raw, byte[] oldRaw, Page page, long uid, DataManagerImpl dm){ 35 | this.raw = raw; 36 | this.oldRaw = oldRaw; 37 | ReadWriteLock lock = new ReentrantReadWriteLock(); 38 | rLock = lock.readLock(); 39 | wLock = lock.writeLock(); 40 | this.dm = dm; 41 | this.uid = uid; 42 | this.page = page; 43 | } 44 | 45 | @Override 46 | public SubArray data() { 47 | return new SubArray(raw.raw, raw.start + OF_DATA, raw.end); 48 | } 49 | 50 | @Override 51 | public void before() { 52 | wLock.lock(); 53 | page.setDirty(true); 54 | System.arraycopy(raw.raw, raw.start, oldRaw, 0, oldRaw.length); 55 | } 56 | 57 | @Override 58 | public void unBefore() { 59 | System.arraycopy(oldRaw, 0, raw.raw, raw.start, oldRaw.length); 60 | wLock.unlock(); 61 | } 62 | 63 | @Override 64 | public void after(long xid) { 65 | dm.logDataItem(xid, this); 66 | } 67 | 68 | @Override 69 | public void release() { 70 | dm.releaseDataItem(this); 71 | } 72 | 73 | @Override 74 | public void lock() { 75 | wLock.lock(); 76 | } 77 | 78 | @Override 79 | public void unlock() { 80 | wLock.unlock(); 81 | } 82 | 83 | @Override 84 | public void rLock() { 85 | rLock.lock(); 86 | } 87 | 88 | @Override 89 | public void rUnLock() { 90 | rLock.unlock(); 91 | } 92 | 93 | @Override 94 | public Page page() { 95 | return page; 96 | } 97 | 98 | @Override 99 | public long getUid() { 100 | return uid; 101 | } 102 | 103 | @Override 104 | public byte[] getOldRaw() { 105 | return oldRaw; 106 | } 107 | 108 | @Override 109 | public SubArray getRaw() { 110 | return raw; 111 | } 112 | 113 | public boolean isValid() { 114 | return raw.raw[raw.start + OF_VALID] == (byte) 0; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/logger/Logger.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm.logger; 2 | 3 | import cn.edu.gzhu.backend.dm.logger.impl.LoggerImpl; 4 | import cn.edu.gzhu.backend.utils.Panic; 5 | 6 | import java.io.File; 7 | import java.io.FileNotFoundException; 8 | import java.io.IOException; 9 | import java.io.RandomAccessFile; 10 | import java.nio.ByteBuffer; 11 | import java.nio.channels.FileChannel; 12 | import java.util.Random; 13 | 14 | import cn.edu.gzhu.backend.utils.Parser; 15 | import cn.edu.gzhu.common.Error; 16 | 17 | public interface Logger { 18 | void log(byte[] data); 19 | void truncate(long x) throws Exception; 20 | byte[] next(); 21 | void rewind(); 22 | void close(); 23 | 24 | public static Logger create(String path){ 25 | File file = new File(path + LoggerImpl.LOG_SUFFIX); 26 | try{ 27 | if(!file.createNewFile()){ 28 | Panic.panic(Error.FileExistsException); 29 | } 30 | } catch (Exception e){ 31 | Panic.panic(e); 32 | } 33 | if(!file.canRead() || !file.canWrite()){ 34 | Panic.panic(Error.FileCannotRWException); 35 | } 36 | FileChannel fc = null; 37 | RandomAccessFile raf = null; 38 | try{ 39 | raf = new RandomAccessFile(file, "rw"); 40 | fc = raf.getChannel(); 41 | } catch (FileNotFoundException e){ 42 | Panic.panic(e); 43 | } 44 | ByteBuffer buf = ByteBuffer.wrap(Parser.int2Byte(0)); 45 | try { 46 | fc.position(0); 47 | fc.write(buf); 48 | fc.force(false); 49 | } catch (IOException e){ 50 | Panic.panic(e); 51 | } 52 | return new LoggerImpl(raf, fc, 0); 53 | } 54 | 55 | public static Logger open(String path){ 56 | File file = new File(path + LoggerImpl.LOG_SUFFIX); 57 | if(!file.exists()){ 58 | Panic.panic(Error.FileNotExistsException); 59 | } 60 | if(!file.canRead() || !file.canWrite()){ 61 | Panic.panic(Error.FileCannotRWException); 62 | } 63 | FileChannel fc = null; 64 | RandomAccessFile raf = null; 65 | try{ 66 | raf = new RandomAccessFile(file, "rw"); 67 | fc = raf.getChannel(); 68 | } catch (FileNotFoundException e){ 69 | Panic.panic(e); 70 | } 71 | LoggerImpl lg = new LoggerImpl(raf, fc); 72 | lg.init(); 73 | return lg; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/logger/impl/LoggerImpl.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm.logger.impl; 2 | 3 | import cn.edu.gzhu.backend.dm.logger.Logger; 4 | import cn.edu.gzhu.backend.utils.Panic; 5 | 6 | import java.io.IOException; 7 | import java.io.RandomAccessFile; 8 | import java.nio.ByteBuffer; 9 | import java.nio.channels.FileChannel; 10 | import java.util.Arrays; 11 | import java.util.concurrent.locks.Lock; 12 | import java.util.concurrent.locks.ReentrantLock; 13 | 14 | import cn.edu.gzhu.backend.utils.Parser; 15 | import cn.edu.gzhu.common.Error; 16 | import com.google.common.primitives.Bytes; 17 | 18 | /** 19 | * 日志文件读写 20 | * 21 | * 日志文件标准格式为: 22 | * [XChecksum] [Log1] [Log2] ... [LogN] [BadTail] 23 | * XChecksum 为后续所有日志计算的Checksum,int类型 24 | * 25 | * 每条正确日志的格式为: 26 | * [Size] [Checksum] [Data] 27 | * Size 4字节int 标识Data长度 28 | * Checksum 4字节int 29 | */ 30 | public class LoggerImpl implements Logger { 31 | private static final int SEED = 13331; 32 | 33 | private static final int OF_SIZE = 0; 34 | private static final int OF_CHECKSUM = OF_SIZE + 4; 35 | private static final int OF_DATA = OF_CHECKSUM + 4; 36 | 37 | public static final String LOG_SUFFIX = ".log"; 38 | 39 | private RandomAccessFile file; 40 | private FileChannel fc; 41 | private Lock lock; 42 | 43 | // 当前日志指针的位置 44 | private long position; 45 | // 初始化时记录,log操作不断更新 46 | private long fileSize; 47 | private int xCheckSum; 48 | 49 | public LoggerImpl(RandomAccessFile raf, FileChannel fc){ 50 | this.file = raf; 51 | this.fc = fc; 52 | lock = new ReentrantLock(); 53 | } 54 | 55 | public LoggerImpl(RandomAccessFile raf, FileChannel fc, int xCheckSum){ 56 | this.file = raf; 57 | this.fc = fc; 58 | this.xCheckSum = xCheckSum; 59 | lock = new ReentrantLock(); 60 | } 61 | 62 | public void init(){ 63 | long size = 0; 64 | try { 65 | size = file.length(); 66 | } catch (IOException e){ 67 | Panic.panic(e); 68 | } 69 | if(size < 4){ 70 | Panic.panic(Error.BadLogFileException); 71 | } 72 | ByteBuffer raw = ByteBuffer.allocate(4); 73 | try { 74 | fc.position(0); 75 | fc.read(raw); 76 | } catch (IOException e){ 77 | Panic.panic(e); 78 | } 79 | int xCheckSum = Parser.parseInt(raw.array()); 80 | this.fileSize = size; 81 | this.xCheckSum = xCheckSum; 82 | 83 | checkAndRemoveTail(); 84 | } 85 | 86 | // 检查并移除 bad tail 87 | private void checkAndRemoveTail() { 88 | rewind(); 89 | int xCheck = 0; 90 | while (true) { 91 | byte[] log = internNext(); 92 | if(log == null) break; 93 | xCheck = calCheckSum(xCheck, log); 94 | } 95 | if(xCheck != xCheckSum){ 96 | Panic.panic(Error.BadLogFileException); 97 | } 98 | try { 99 | truncate(position); 100 | } catch (Exception e) { 101 | Panic.panic(e); 102 | } 103 | try { 104 | file.seek(position); 105 | } catch (IOException e) { 106 | Panic.panic(e); 107 | } 108 | rewind(); 109 | } 110 | 111 | private int calCheckSum(int xCheck, byte[] log) { 112 | for (byte b : log) { 113 | xCheck = xCheckSum * SEED + b; 114 | } 115 | return xCheck; 116 | } 117 | 118 | private byte[] internNext() { 119 | if(position + OF_DATA >= fileSize){ 120 | return null; 121 | } 122 | // 读取 size 123 | ByteBuffer tmp = ByteBuffer.allocate(4); 124 | try { 125 | fc.position(position); 126 | fc.read(tmp); 127 | } catch (IOException e){ 128 | Panic.panic(e); 129 | } 130 | int size = Parser.parseInt(tmp.array()); 131 | if(position + size + OF_DATA > fileSize){ 132 | return null; 133 | } 134 | // 读取 checkSum + data 135 | ByteBuffer buf = ByteBuffer.allocate(OF_DATA + size); 136 | try { 137 | fc.position(position); 138 | fc.read(buf); 139 | } catch (IOException e){ 140 | Panic.panic(e); 141 | } 142 | // 校验 checkSum 143 | byte[] log = buf.array(); 144 | int checkSum1 = calCheckSum(0, Arrays.copyOfRange(log, OF_DATA, log.length)); 145 | int checkSum2 = Parser.parseInt(Arrays.copyOfRange(log, OF_CHECKSUM, OF_DATA)); 146 | if(checkSum1 != checkSum2){ 147 | return null; 148 | } 149 | position += log.length; 150 | return log; 151 | } 152 | 153 | @Override 154 | public void log(byte[] data) { 155 | byte[] log = warpLog(data); 156 | ByteBuffer buf = ByteBuffer.wrap(log); 157 | lock.lock(); 158 | try { 159 | fc.position(fc.size()); 160 | fc.write(buf); 161 | } catch (IOException e){ 162 | Panic.panic(e); 163 | } finally { 164 | lock.unlock(); 165 | } 166 | updateXCheckSum(log); 167 | } 168 | 169 | private void updateXCheckSum(byte[] log) { 170 | this.xCheckSum = calCheckSum(this.xCheckSum, log); 171 | try { 172 | fc.position(0); 173 | fc.write(ByteBuffer.wrap(Parser.int2Byte(xCheckSum))); 174 | fc.force(false); 175 | } catch(IOException e) { 176 | Panic.panic(e); 177 | } 178 | } 179 | 180 | private byte[] warpLog(byte[] data) { 181 | byte[] checkSum = Parser.int2Byte(calCheckSum(0, data)); 182 | byte[] size = Parser.int2Byte(data.length); 183 | return Bytes.concat(size, checkSum, data); 184 | } 185 | 186 | @Override 187 | public void truncate(long x) throws Exception { 188 | lock.lock(); 189 | try { 190 | fc.truncate(x); 191 | } finally { 192 | lock.unlock(); 193 | } 194 | } 195 | 196 | @Override 197 | public byte[] next() { 198 | lock.lock(); 199 | try{ 200 | byte[] log = internNext(); 201 | if(log == null) return null; 202 | return Arrays.copyOfRange(log, OF_DATA, log.length); 203 | } finally { 204 | lock.unlock(); 205 | } 206 | } 207 | 208 | @Override 209 | public void rewind() { 210 | position = 4; 211 | } 212 | 213 | @Override 214 | public void close() { 215 | try { 216 | fc.close(); 217 | file.close(); 218 | } catch(IOException e) { 219 | Panic.panic(e); 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/page/Page.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm.page; 2 | 3 | public interface Page { 4 | void lock(); 5 | void unlock(); 6 | void release(); 7 | void setDirty(boolean dirty); 8 | boolean isDirty(); 9 | int getPageNumber(); 10 | byte[] getData(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/page/PageCache.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm.page; 2 | 3 | import cn.edu.gzhu.backend.dm.page.impl.PageCacheImpl; 4 | import cn.edu.gzhu.backend.utils.Panic; 5 | 6 | import java.io.File; 7 | import java.io.FileNotFoundException; 8 | import java.io.RandomAccessFile; 9 | import java.nio.channels.FileChannel; 10 | 11 | import cn.edu.gzhu.common.Error; 12 | 13 | public interface PageCache { 14 | public static final int PAGE_SIZE = 1 << 13; 15 | 16 | int newPage(byte[] initData); 17 | Page getPage(int pageNum) throws Exception; 18 | void close(); 19 | void release(Page page); 20 | 21 | void truncateByPageNum(int maxPageNum); 22 | int getPageNumber(); 23 | void flushPage(Page page); 24 | 25 | public static PageCacheImpl create(String path, long memory){ 26 | File file = new File(path + PageCacheImpl.DB_SUFFIX); 27 | try { 28 | if(!file.createNewFile()) { 29 | Panic.panic(Error.FileExistsException); 30 | } 31 | } catch (Exception e) { 32 | Panic.panic(e); 33 | } 34 | if(!file.canRead() || !file.canWrite()) { 35 | Panic.panic(Error.FileCannotRWException); 36 | } 37 | 38 | FileChannel fc = null; 39 | RandomAccessFile raf = null; 40 | try { 41 | raf = new RandomAccessFile(file, "rw"); 42 | fc = raf.getChannel(); 43 | } catch (FileNotFoundException e) { 44 | Panic.panic(e); 45 | } 46 | return new PageCacheImpl(raf, fc, (int)memory/PAGE_SIZE); 47 | } 48 | 49 | public static PageCacheImpl open(String path, long memory) { 50 | File file = new File(path+PageCacheImpl.DB_SUFFIX); 51 | if(!file.exists()) { 52 | Panic.panic(Error.FileNotExistsException); 53 | } 54 | if(!file.canRead() || !file.canWrite()) { 55 | Panic.panic(Error.FileCannotRWException); 56 | } 57 | 58 | FileChannel fc = null; 59 | RandomAccessFile raf = null; 60 | try { 61 | raf = new RandomAccessFile(file, "rw"); 62 | fc = raf.getChannel(); 63 | } catch (FileNotFoundException e) { 64 | Panic.panic(e); 65 | } 66 | return new PageCacheImpl(raf, fc, (int)memory/PAGE_SIZE); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/page/PageOne.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm.page; 2 | 3 | import cn.edu.gzhu.backend.utils.RandomUtil; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * 特殊管理第一页 9 | * ValidCheck 10 | * db启动时给100~107字节处填入一个随机字节,db关闭时将其拷贝到108~115字节 11 | * 用于判断上一次数据库是否正常关闭,如果是异常关闭,就需要执行数据的恢复流程。 12 | */ 13 | public class PageOne { 14 | private static final int OF_VC = 100; 15 | private static final int LEN_VC = 8; 16 | 17 | public static byte[] initRaw() { 18 | byte[] raw = new byte[PageCache.PAGE_SIZE]; 19 | setVcOpen(raw); 20 | return raw; 21 | } 22 | 23 | public static void setVcOpen(Page page){ 24 | page.setDirty(true); 25 | setVcOpen(page.getData()); 26 | } 27 | 28 | private static void setVcOpen(byte[] raw) { 29 | System.arraycopy(RandomUtil.randomBytes(LEN_VC), 0, raw, OF_VC, LEN_VC); 30 | } 31 | 32 | public static void setVcClose(Page page){ 33 | page.setDirty(true); 34 | setVcClose(page.getData()); 35 | } 36 | 37 | private static void setVcClose(byte[] raw){ 38 | System.arraycopy(raw, OF_VC, raw, OF_VC + LEN_VC, LEN_VC); 39 | } 40 | 41 | public static boolean checkVc(Page page){ 42 | return checkVc(page.getData()); 43 | } 44 | 45 | private static boolean checkVc(byte[] raw){ 46 | return Arrays.equals(Arrays.copyOfRange(raw, OF_VC, OF_VC+LEN_VC), Arrays.copyOfRange(raw, OF_VC+LEN_VC, OF_VC+2*LEN_VC)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/page/PageX.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm.page; 2 | 3 | import cn.edu.gzhu.backend.utils.Parser; 4 | 5 | import java.util.Arrays; 6 | 7 | /** 8 | * PageX管理普通页 9 | * 普通页结构 10 | * [FreeSpaceOffset] [Data] 11 | * FreeSpaceOffset: 2字节 空闲位置开始偏移 12 | */ 13 | public class PageX { 14 | private static final short OF_FREE = 0; 15 | private static final short OF_DATA = 2; 16 | public static final int MAX_FREE_SPACE = PageCache.PAGE_SIZE - OF_DATA; 17 | 18 | public static byte[] initRaw(){ 19 | byte[] raw = new byte[PageCache.PAGE_SIZE]; 20 | setFSO(raw, OF_DATA); 21 | return raw; 22 | } 23 | 24 | private static void setFSO(byte[] raw, short ofData) { 25 | System.arraycopy(Parser.short2Byte(ofData), 0, raw, OF_FREE, OF_DATA); 26 | } 27 | 28 | // 获取page的FSO 29 | public static short getFSO(Page page){ 30 | return getFSO(page.getData()); 31 | } 32 | 33 | private static short getFSO(byte[] raw) { 34 | return Parser.parseShort(Arrays.copyOfRange(raw, 0, 2)); 35 | } 36 | 37 | // 将 raw 插入 page 中,返回插入位置 38 | public static short insert(Page page, byte[] raw){ 39 | page.setDirty(true); 40 | short offset = getFSO(page.getData()); 41 | System.arraycopy(raw, 0, page.getData(), offset, raw.length); 42 | setFSO(page.getData(), (short) (offset + raw.length)); 43 | return offset; 44 | } 45 | 46 | // 获取页面的空闲空间大小 47 | public static int getFreeSpace(Page page){ 48 | return PageCache.PAGE_SIZE - (int)getFSO(page.getData()); 49 | } 50 | 51 | // 将 raw 插入 page 中的 offset 位置,并将 page 中 offset 设置为较大的 offset 52 | public static void recoverInsert(Page page, byte[] raw, short offset){ 53 | page.setDirty(true); 54 | System.arraycopy(raw, 0, page.getData(), offset, raw.length); 55 | short rawFSO = getFSO(page.getData()); 56 | if(rawFSO < offset + raw.length){ 57 | setFSO(page.getData(), (short) (offset + raw.length)); 58 | } 59 | } 60 | 61 | // 将raw插入page中的offset,不更新update 62 | public static void recoverUpdate(Page page, byte[] raw, short offset) { 63 | page.setDirty(true); 64 | System.arraycopy(raw, 0, page.getData(), offset, raw.length); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/page/impl/PageCacheImpl.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm.page.impl; 2 | 3 | import cn.edu.gzhu.backend.common.AbstractCache; 4 | import cn.edu.gzhu.backend.dm.page.Page; 5 | import cn.edu.gzhu.backend.dm.page.PageCache; 6 | import cn.edu.gzhu.backend.utils.Panic; 7 | 8 | import java.io.IOException; 9 | import java.io.RandomAccessFile; 10 | import java.nio.ByteBuffer; 11 | import java.nio.channels.FileChannel; 12 | import java.util.concurrent.atomic.AtomicInteger; 13 | import java.util.concurrent.locks.Lock; 14 | import java.util.concurrent.locks.ReentrantLock; 15 | 16 | import cn.edu.gzhu.common.Error; 17 | 18 | public class PageCacheImpl extends AbstractCache implements PageCache { 19 | private static final int MEM_MIN_LIM = 10; 20 | public static final String DB_SUFFIX = ".db"; 21 | 22 | private RandomAccessFile file; 23 | private FileChannel fc; 24 | private Lock fileLock; 25 | 26 | private AtomicInteger pageNumbers; 27 | 28 | public PageCacheImpl(RandomAccessFile file, FileChannel fileChannel, int maxResource) { 29 | super(maxResource); 30 | if(maxResource < MEM_MIN_LIM){ 31 | Panic.panic(Error.MemTooSmallException); 32 | } 33 | long length = 0; 34 | try { 35 | length = file.length(); 36 | } catch (IOException e) { 37 | Panic.panic(e); 38 | } 39 | this.file = file; 40 | this.fc = fileChannel; 41 | this.fileLock = new ReentrantLock(); 42 | this.pageNumbers = new AtomicInteger((int) length / PAGE_SIZE); 43 | } 44 | 45 | /** 46 | * 根据 pageNumber 从数据库文件中读取页数据,并包裹成 Page 47 | * @param key 48 | * @return 49 | * @throws Exception 50 | */ 51 | @Override 52 | protected Page getForCache(long key) throws Exception { 53 | int pageNum = (int)key; 54 | long offset = pageOffset(pageNum); 55 | 56 | ByteBuffer buf = ByteBuffer.allocate(PAGE_SIZE); 57 | fileLock.lock(); 58 | try{ 59 | fc.position(offset); 60 | fc.read(buf); 61 | } catch (IOException e){ 62 | Panic.panic(e); 63 | } 64 | fileLock.unlock(); 65 | return new PageImpl(pageNum, buf.array(), this); 66 | } 67 | 68 | private static long pageOffset(int pageNum) { 69 | return (long) (pageNum - 1) * PAGE_SIZE; 70 | } 71 | 72 | @Override 73 | protected void releaseForCache(Page page) { 74 | if(page.isDirty()){ 75 | flush(page); 76 | page.setDirty(false); 77 | } 78 | } 79 | 80 | @Override 81 | public int newPage(byte[] initData) { 82 | int pageNum = pageNumbers.incrementAndGet(); 83 | Page page = new PageImpl(pageNum, initData, null); 84 | flush(page); 85 | return pageNum; 86 | } 87 | 88 | private void flush(Page page) { 89 | int pageNum = page.getPageNumber(); 90 | long offset = pageOffset(pageNum); 91 | fileLock.lock(); 92 | try{ 93 | ByteBuffer buf = ByteBuffer.wrap(page.getData()); 94 | fc.position(offset); 95 | fc.write(buf); 96 | fc.force(false); 97 | } catch (IOException e){ 98 | Panic.panic(e); 99 | } finally { 100 | fileLock.unlock(); 101 | } 102 | } 103 | 104 | @Override 105 | public Page getPage(int pageNum) throws Exception { 106 | return get((long) pageNum); 107 | } 108 | 109 | @Override 110 | public void close() { 111 | super.close(); 112 | try { 113 | fc.close(); 114 | file.close(); 115 | } catch (IOException e){ 116 | Panic.panic(e); 117 | } 118 | } 119 | 120 | @Override 121 | public void release(Page page) { 122 | release((long) page.getPageNumber()); 123 | } 124 | 125 | @Override 126 | public void truncateByPageNum(int maxPageNum) { 127 | long size = pageOffset(maxPageNum + 1); 128 | try { 129 | file.setLength(size); 130 | } catch (IOException e){ 131 | Panic.panic(e); 132 | } 133 | pageNumbers.set(maxPageNum); 134 | } 135 | 136 | @Override 137 | public int getPageNumber() { 138 | return pageNumbers.intValue(); 139 | } 140 | 141 | @Override 142 | public void flushPage(Page page) { 143 | flush(page); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/page/impl/PageImpl.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm.page.impl; 2 | 3 | import cn.edu.gzhu.backend.dm.page.Page; 4 | import cn.edu.gzhu.backend.dm.page.PageCache; 5 | 6 | import java.util.concurrent.locks.Lock; 7 | import java.util.concurrent.locks.ReentrantLock; 8 | 9 | public class PageImpl implements Page { 10 | // 页面的页号 11 | private int pageNumber; 12 | 13 | // 实际包含的字节数据 14 | private byte[] data; 15 | 16 | // 是否为脏页面 17 | private boolean dirty; 18 | 19 | private Lock lock; 20 | 21 | private PageCache pc; 22 | 23 | public PageImpl(int pageNumber, byte[] data, PageCache pc){ 24 | this.pageNumber = pageNumber; 25 | this.data = data; 26 | this.pc = pc; 27 | lock = new ReentrantLock(); 28 | } 29 | 30 | @Override 31 | public void lock() { 32 | lock.lock(); 33 | } 34 | 35 | @Override 36 | public void unlock() { 37 | lock.unlock(); 38 | } 39 | 40 | @Override 41 | public void release() { 42 | pc.release(this); 43 | } 44 | 45 | @Override 46 | public void setDirty(boolean dirty) { 47 | this.dirty = dirty; 48 | } 49 | 50 | @Override 51 | public boolean isDirty() { 52 | return dirty; 53 | } 54 | 55 | @Override 56 | public int getPageNumber() { 57 | return pageNumber; 58 | } 59 | 60 | @Override 61 | public byte[] getData() { 62 | return data; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/pageIndex/PageIndex.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm.pageIndex; 2 | 3 | import cn.edu.gzhu.backend.dm.page.PageCache; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.locks.Lock; 8 | import java.util.concurrent.locks.ReentrantLock; 9 | 10 | public class PageIndex { 11 | // 将一页划分成40个区间 12 | private static final int INTERVALS_NO = 40; 13 | 14 | private static final int THRESHOLD = PageCache.PAGE_SIZE / INTERVALS_NO; 15 | 16 | private Lock lock; 17 | 18 | private List[] lists; 19 | 20 | @SuppressWarnings("unchecked") 21 | public PageIndex(){ 22 | lock = new ReentrantLock(); 23 | lists = new List[INTERVALS_NO + 1]; 24 | for (int i = 0; i < INTERVALS_NO + 1; i++) { 25 | lists[i] = new ArrayList<>(); 26 | } 27 | } 28 | 29 | public void add(int pageNum, int freeSpace){ 30 | lock.lock(); 31 | try { 32 | int number = freeSpace / THRESHOLD; 33 | lists[number].add(new PageInfo(pageNum, freeSpace)); 34 | } finally { 35 | lock.unlock(); 36 | } 37 | } 38 | 39 | public PageInfo select(int spaceSize){ 40 | lock.lock(); 41 | try { 42 | int number = spaceSize / THRESHOLD; 43 | if(number < INTERVALS_NO) number ++; 44 | while (number <= INTERVALS_NO) { 45 | if(lists[number].size() == 0){ 46 | number ++; 47 | continue; 48 | } 49 | return lists[number].remove(0); 50 | } 51 | return null; 52 | } finally { 53 | lock.unlock(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/dm/pageIndex/PageInfo.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.dm.pageIndex; 2 | 3 | public class PageInfo { 4 | public int pageNum; 5 | public int freeSpace; 6 | 7 | public PageInfo(int pageNum, int freeSpace) { 8 | this.pageNum = pageNum; 9 | this.freeSpace = freeSpace; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/im/BPlusTree.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.im; 2 | 3 | import cn.edu.gzhu.backend.common.SubArray; 4 | import cn.edu.gzhu.backend.dm.DataManager; 5 | import cn.edu.gzhu.backend.dm.dataItem.DataItem; 6 | import cn.edu.gzhu.backend.tm.impl.TransactionManagerImpl; 7 | import cn.edu.gzhu.backend.utils.Parser; 8 | import cn.edu.gzhu.backend.im.Node.SearchNextRes; 9 | import cn.edu.gzhu.backend.im.Node.LeafSearchRangeRes; 10 | import cn.edu.gzhu.backend.im.Node.InsertAndSplitRes; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.concurrent.locks.Lock; 16 | import java.util.concurrent.locks.ReentrantLock; 17 | 18 | public class BPlusTree { 19 | DataManager dm; 20 | long bootUid; 21 | DataItem bootDataItem; 22 | Lock bootLock; 23 | 24 | public static long create(DataManager dm) throws Exception { 25 | byte[] rawRoot = Node.newNilRootRaw(); 26 | long rootUid = dm.insert(TransactionManagerImpl.SUPER_XID, rawRoot); 27 | return dm.insert(TransactionManagerImpl.SUPER_XID, Parser.long2Byte(rootUid)); 28 | } 29 | 30 | public static BPlusTree load(long bootUid, DataManager dm) throws Exception { 31 | DataItem bootDataItem = dm.read(bootUid); 32 | assert bootDataItem != null; 33 | BPlusTree t = new BPlusTree(); 34 | t.bootUid = bootUid; 35 | t.dm = dm; 36 | t.bootDataItem = bootDataItem; 37 | t.bootLock = new ReentrantLock(); 38 | return t; 39 | } 40 | 41 | private long rootUid() { 42 | bootLock.lock(); 43 | try { 44 | SubArray subArray = bootDataItem.data(); 45 | return Parser.parseLong(Arrays.copyOfRange(subArray.raw, subArray.start, subArray.start + 8)); 46 | } finally { 47 | bootLock.unlock(); 48 | } 49 | } 50 | 51 | private void updateRootUid(long left, long right, long rightKey) throws Exception { 52 | bootLock.lock(); 53 | try { 54 | byte[] rootRaw = Node.newRootRaw(left, right, rightKey); 55 | long newRootUid = dm.insert(TransactionManagerImpl.SUPER_XID, rootRaw); 56 | bootDataItem.before(); 57 | SubArray diRaw = bootDataItem.data(); 58 | System.arraycopy(Parser.long2Byte(newRootUid), 0, diRaw.raw, diRaw.start, 8); 59 | bootDataItem.after(TransactionManagerImpl.SUPER_XID); 60 | } finally { 61 | bootLock.unlock(); 62 | } 63 | } 64 | 65 | private long searchLeaf(long nodeUid, long key) throws Exception { 66 | Node node = Node.loadNode(this, nodeUid); 67 | boolean isLeaf = node.isLeaf(); 68 | node.release(); 69 | if(isLeaf) { 70 | return nodeUid; 71 | } else { 72 | long next = searchNext(nodeUid, key); 73 | return searchLeaf(next, key); 74 | } 75 | } 76 | 77 | private long searchNext(long nodeUid, long key) throws Exception { 78 | while (true) { 79 | Node node = Node.loadNode(this, nodeUid); 80 | SearchNextRes res = node.searchNext(key); 81 | node.release(); 82 | if(res.uid != 0) return res.uid; 83 | nodeUid = res.siblingUid; 84 | } 85 | } 86 | 87 | public List search(long key) throws Exception { 88 | return searchRange(key, key); 89 | } 90 | 91 | public List searchRange(long leftKey, long rightKey) throws Exception { 92 | long rootUid = rootUid(); 93 | long leafUid = searchLeaf(rootUid, leftKey); 94 | List uids = new ArrayList<>(); 95 | while (true) { 96 | Node leaf = Node.loadNode(this, leafUid); 97 | LeafSearchRangeRes res = leaf.leafSearchRange(leftKey, rightKey); 98 | leaf.release(); 99 | uids.addAll(res.uids); 100 | if(res.siblingUid == 0) { 101 | break; 102 | } else { 103 | leafUid = res.siblingUid; 104 | } 105 | } 106 | return uids; 107 | } 108 | 109 | static class InsertRes { 110 | long newNode; 111 | long newKey; 112 | } 113 | 114 | public void insert(long key, long uid) throws Exception { 115 | long rootUid = rootUid(); 116 | InsertRes res = insert(rootUid, uid, key); 117 | assert res != null; 118 | if(res.newNode != 0){ 119 | updateRootUid(rootUid, res.newNode, res.newKey); 120 | } 121 | } 122 | 123 | private InsertRes insert(long nodeUid, long uid, long key) throws Exception { 124 | Node node = Node.loadNode(this, nodeUid); 125 | boolean isLeaf = node.isLeaf(); 126 | node.release(); 127 | InsertRes res = null; 128 | if(isLeaf) { 129 | res = insertAndSplit(nodeUid, uid, key); 130 | } else { 131 | long next = searchNext(nodeUid, key); 132 | InsertRes insertRes = insert(next, uid, key); 133 | if(insertRes.newNode != 0) { 134 | res = insertAndSplit(nodeUid, insertRes.newNode, insertRes.newKey); 135 | } else { 136 | res = new InsertRes(); 137 | } 138 | } 139 | return res; 140 | } 141 | 142 | private InsertRes insertAndSplit(long nodeUid, long uid, long key) throws Exception { 143 | while (true) { 144 | Node node = Node.loadNode(this, nodeUid); 145 | InsertAndSplitRes insertAndSplitRes = node.insertAndSplit(uid, key); 146 | node.release(); 147 | if(insertAndSplitRes.siblingUid != 0) { 148 | nodeUid = insertAndSplitRes.siblingUid; 149 | } else { 150 | InsertRes res = new InsertRes(); 151 | res.newNode = insertAndSplitRes.newSon; 152 | res.newKey = insertAndSplitRes.newKey; 153 | return res; 154 | } 155 | } 156 | } 157 | 158 | public void close() { 159 | bootDataItem.release(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/im/Node.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.im; 2 | 3 | import cn.edu.gzhu.backend.common.SubArray; 4 | import cn.edu.gzhu.backend.dm.dataItem.DataItem; 5 | import cn.edu.gzhu.backend.tm.impl.TransactionManagerImpl; 6 | import cn.edu.gzhu.backend.utils.Parser; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | /** 13 | * Node结构如下: 14 | * [LeafFlag][KeyNumber][SiblingUid] 15 | * [Son0][Key0][Son1][Key1]...[SonN][KeyN] 16 | * 其中 LeafFlag 标记了该节点是否是叶子节点, KeyNumber 为该节点中 key 的个数, SiblingUid 是其兄弟节点存储在 DM 中的 UID。 17 | * 后续是穿插的子节点 SonN 和 KeyN。最后的一个 KeyN 始终为 MAX_VALUE,以此方便查找。 18 | */ 19 | public class Node { 20 | static final int IS_LEAF_OFFSET = 0; 21 | static final int NO_KEYS_OFFSET = IS_LEAF_OFFSET + 1; 22 | static final int SIBLING_OFFSET = NO_KEYS_OFFSET + 2; 23 | static final int NODE_HEADER_SIZE = SIBLING_OFFSET + 8; 24 | static final int BALANCE_NUMBER = 32; 25 | static final int NODE_SIZE = NODE_HEADER_SIZE + (2 * 8) * (BALANCE_NUMBER * 2 + 2); 26 | 27 | BPlusTree tree; 28 | DataItem dataItem; 29 | SubArray raw; 30 | long uid; 31 | 32 | static void setRawIsLeaf(SubArray raw, boolean isLeaf){ 33 | if(isLeaf){ 34 | raw.raw[raw.start + IS_LEAF_OFFSET] = (byte) 1; 35 | } else{ 36 | raw.raw[raw.start + IS_LEAF_OFFSET] = (byte) 0; 37 | } 38 | } 39 | 40 | static boolean getRawIfLeaf(SubArray raw){ 41 | return raw.raw[raw.start + IS_LEAF_OFFSET] == (byte) 1; 42 | } 43 | 44 | static void setRawNoKeys(SubArray raw, int noKeys){ 45 | System.arraycopy(Parser.short2Byte((short) noKeys), 0, raw.raw, raw.start + NO_KEYS_OFFSET, 2); 46 | } 47 | 48 | static int getRawNoKeys(SubArray raw){ 49 | return (int) Parser.parseShort(Arrays.copyOfRange(raw.raw, raw.start + NO_KEYS_OFFSET, raw.start + NO_KEYS_OFFSET + 2)); 50 | } 51 | 52 | static void setRawSibling(SubArray raw, long sibling){ 53 | System.arraycopy(Parser.long2Byte(sibling), 0, raw.raw, raw.start + SIBLING_OFFSET, 8); 54 | } 55 | 56 | static long getRawSibling(SubArray raw){ 57 | return Parser.parseLong(Arrays.copyOfRange(raw.raw, raw.start + SIBLING_OFFSET, raw.start + SIBLING_OFFSET + 8)); 58 | } 59 | 60 | static void setRawKthSon(SubArray raw, long uid, int kth){ 61 | int offset = raw.start + NODE_HEADER_SIZE + kth * (8 * 2); 62 | System.arraycopy(Parser.long2Byte(uid), 0, raw.raw, offset, 8); 63 | } 64 | 65 | static long getRawKthSon(SubArray raw, int kth) { 66 | int offset = raw.start+NODE_HEADER_SIZE+kth*(8*2); 67 | return Parser.parseLong(Arrays.copyOfRange(raw.raw, offset, offset+8)); 68 | } 69 | 70 | static void setRawKthKey(SubArray raw, long key, int kth){ 71 | int offset = raw.start + NODE_HEADER_SIZE + kth * (8 * 2) + 8; 72 | System.arraycopy(Parser.long2Byte(key), 0, raw.raw, offset, 8); 73 | } 74 | 75 | static long getRawKthKey(SubArray raw, int kth) { 76 | int offset = raw.start + NODE_HEADER_SIZE + kth * (8 * 2) + 8; 77 | return Parser.parseLong(Arrays.copyOfRange(raw.raw, offset, offset+8)); 78 | } 79 | 80 | static void copyRawFromKth(SubArray from, SubArray to, int kth) { 81 | int offset = from.start + NODE_HEADER_SIZE + kth * (8 * 2); 82 | System.arraycopy(from.raw, offset, to.raw, to.start + NODE_HEADER_SIZE, from.end - offset); 83 | } 84 | 85 | static void shiftRawKth(SubArray raw, int kth) { 86 | int begin = raw.start+NODE_HEADER_SIZE + (kth + 1) * (8 * 2); 87 | int end = raw.start+NODE_SIZE - 1; 88 | for(int i = end; i >= begin; i --) { 89 | raw.raw[i] = raw.raw[i - (8 * 2)]; 90 | } 91 | } 92 | 93 | static byte[] newRootRaw(long left, long right, long key){ 94 | SubArray raw = new SubArray(new byte[NODE_SIZE], 0, NODE_SIZE); 95 | setRawIsLeaf(raw, false); 96 | setRawNoKeys(raw, 2); 97 | setRawSibling(raw, 0); 98 | setRawKthSon(raw, left, 0); 99 | setRawKthKey(raw, key, 0); 100 | setRawKthSon(raw, right, 1); 101 | setRawKthKey(raw, Long.MAX_VALUE, 1); 102 | return raw.raw; 103 | } 104 | 105 | static byte[] newNilRootRaw(){ 106 | SubArray raw = new SubArray(new byte[NODE_SIZE], 0, NODE_SIZE); 107 | 108 | setRawIsLeaf(raw, true); 109 | setRawNoKeys(raw, 0); 110 | setRawSibling(raw, 0); 111 | 112 | return raw.raw; 113 | } 114 | 115 | static Node loadNode(BPlusTree bTree, long uid) throws Exception { 116 | DataItem dataItem = bTree.dm.read(uid); 117 | assert dataItem != null; 118 | Node node = new Node(); 119 | node.tree = bTree; 120 | node.dataItem = dataItem; 121 | node.raw = dataItem.data(); 122 | node.uid = uid; 123 | return node; 124 | } 125 | 126 | public void release() { 127 | dataItem.release(); 128 | } 129 | 130 | public boolean isLeaf() { 131 | dataItem.rLock(); 132 | try { 133 | return getRawIfLeaf(raw); 134 | } finally { 135 | dataItem.rUnLock(); 136 | } 137 | } 138 | 139 | static class SearchNextRes { 140 | long uid; 141 | long siblingUid; 142 | } 143 | 144 | /** 145 | * searchNext 寻找对于 key 的 UID,如果找不到,则返回兄弟节点的 UID。 146 | * @param key 147 | * @return 148 | */ 149 | public SearchNextRes searchNext(long key){ 150 | dataItem.rLock(); 151 | try { 152 | SearchNextRes res = new SearchNextRes(); 153 | int noKeys = getRawNoKeys(raw); 154 | for (int i = 0; i < noKeys; i++) { 155 | long ik = getRawKthKey(raw, i); 156 | if(key < ik) { 157 | res.uid = getRawKthSon(raw, i); 158 | res.siblingUid = 0; 159 | return res; 160 | } 161 | } 162 | res.uid = 0; 163 | res.siblingUid = getRawSibling(raw); 164 | return res; 165 | } finally { 166 | dataItem.rUnLock(); 167 | } 168 | } 169 | 170 | static class LeafSearchRangeRes { 171 | List uids; 172 | long siblingUid; 173 | } 174 | 175 | public LeafSearchRangeRes leafSearchRange(long leftKey, long rightKey) { 176 | dataItem.rLock(); 177 | try { 178 | int noKeys = getRawNoKeys(raw); 179 | int kth = 0; 180 | while (kth < noKeys) { 181 | long ik = getRawKthKey(raw, kth); 182 | if(ik >= leftKey) { 183 | break; 184 | } 185 | kth ++; 186 | } 187 | List uids = new ArrayList<>(); 188 | while (kth < noKeys) { 189 | long ik = getRawKthKey(raw, kth); 190 | if(ik <= rightKey){ 191 | uids.add(getRawKthSon(raw, kth)); 192 | kth ++; 193 | } else { 194 | break; 195 | } 196 | } 197 | long siblingUid = 0; 198 | if (kth == noKeys) { 199 | siblingUid = getRawSibling(raw); 200 | } 201 | LeafSearchRangeRes res = new LeafSearchRangeRes(); 202 | res.uids = uids; 203 | res.siblingUid = siblingUid; 204 | return res; 205 | } finally { 206 | dataItem.rUnLock(); 207 | } 208 | } 209 | 210 | static class InsertAndSplitRes { 211 | long siblingUid; 212 | long newSon; 213 | long newKey; 214 | } 215 | 216 | public InsertAndSplitRes insertAndSplit(long uid, long key) throws Exception { 217 | boolean success = false; 218 | Exception err = null; 219 | InsertAndSplitRes res = new InsertAndSplitRes(); 220 | 221 | dataItem.before(); 222 | try { 223 | success = insert(uid, key); 224 | if(!success) { 225 | res.siblingUid = getRawSibling(raw); 226 | return res; 227 | } 228 | if(needSplit()){ 229 | try { 230 | SplitRes r = split(); 231 | res.newSon = r.newSon; 232 | res.newKey = r.newKey; 233 | return res; 234 | } catch (Exception e) { 235 | err = e; 236 | throw e; 237 | } 238 | } else { 239 | return res; 240 | } 241 | } finally { 242 | if(err == null && success) { 243 | dataItem.after(TransactionManagerImpl.SUPER_XID); 244 | } else { 245 | dataItem.unBefore(); 246 | } 247 | } 248 | } 249 | 250 | private boolean needSplit() { 251 | return BALANCE_NUMBER * 2 == getRawNoKeys(raw); 252 | } 253 | 254 | static class SplitRes { 255 | long newSon; 256 | long newKey; 257 | } 258 | 259 | private SplitRes split() throws Exception { 260 | SubArray nodeRaw = new SubArray(new byte[NODE_SIZE], 0, NODE_SIZE); 261 | setRawIsLeaf(nodeRaw, getRawIfLeaf(raw)); 262 | setRawNoKeys(nodeRaw, BALANCE_NUMBER); 263 | setRawSibling(nodeRaw, getRawSibling(raw)); 264 | copyRawFromKth(raw, nodeRaw, BALANCE_NUMBER); 265 | long son = tree.dm.insert(TransactionManagerImpl.SUPER_XID, nodeRaw.raw); 266 | setRawNoKeys(raw, BALANCE_NUMBER); 267 | setRawSibling(raw, son); 268 | 269 | SplitRes res = new SplitRes(); 270 | res.newSon = son; 271 | res.newKey = getRawKthKey(nodeRaw, 0); 272 | return res; 273 | } 274 | 275 | private boolean insert(long uid, long key) { 276 | int noKeys = getRawNoKeys(raw); 277 | int kth = 0; 278 | while (kth < noKeys) { 279 | long ik = getRawKthKey(raw, kth); 280 | if(ik < key) { 281 | kth ++; 282 | } else { 283 | break; 284 | } 285 | } 286 | if(kth == noKeys && getRawSibling(raw) != 0) return false; 287 | if(getRawIfLeaf(raw)) { 288 | shiftRawKth(raw, kth); 289 | setRawKthKey(raw, key, kth); 290 | setRawKthSon(raw, uid, kth); 291 | setRawNoKeys(raw, noKeys + 1); 292 | } else { 293 | long k = getRawKthKey(raw, kth); 294 | setRawKthKey(raw, key, kth); 295 | shiftRawKth(raw, kth + 1); 296 | setRawKthKey(raw, k, kth + 1); 297 | setRawKthSon(raw, uid, kth + 1); 298 | setRawNoKeys(raw, noKeys + 1); 299 | } 300 | return true; 301 | } 302 | 303 | @Override 304 | public String toString() { 305 | StringBuilder sb = new StringBuilder(); 306 | sb.append("Is leaf: ").append(getRawIfLeaf(raw)).append("\n"); 307 | int KeyNumber = getRawNoKeys(raw); 308 | sb.append("KeyNumber: ").append(KeyNumber).append("\n"); 309 | sb.append("sibling: ").append(getRawSibling(raw)).append("\n"); 310 | for(int i = 0; i < KeyNumber; i ++) { 311 | sb.append("son: ").append(getRawKthSon(raw, i)).append(", key: ").append(getRawKthKey(raw, i)).append("\n"); 312 | } 313 | return sb.toString(); 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/Parser.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser; 2 | 3 | import cn.edu.gzhu.backend.parser.statement.*; 4 | import cn.edu.gzhu.common.Error; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class Parser { 10 | public static Object parse(byte[] statement) throws Exception { 11 | Tokenizer tokenizer = new Tokenizer(statement); 12 | String token = tokenizer.peek(); 13 | tokenizer.pop(); 14 | Object state = null; 15 | Exception stateErr = null; 16 | try { 17 | switch (token) { 18 | case "begin": 19 | state = parseBegin(tokenizer); 20 | break; 21 | case "commit": 22 | state = parseCommit(tokenizer); 23 | break; 24 | case "abort": 25 | state = parseAbort(tokenizer); 26 | break; 27 | case "create": 28 | state = parseCreate(tokenizer); 29 | break; 30 | case "drop": 31 | state = parseDrop(tokenizer); 32 | break; 33 | case "select": 34 | state = parseSelect(tokenizer); 35 | break; 36 | case "insert": 37 | state = parseInsert(tokenizer); 38 | break; 39 | case "delete": 40 | state = parseDelete(tokenizer); 41 | break; 42 | case "update": 43 | state = parseUpdate(tokenizer); 44 | break; 45 | case "show": 46 | state = parseShow(tokenizer); 47 | break; 48 | default: 49 | throw Error.InvalidCommandException; 50 | } 51 | } catch (Exception e){ 52 | stateErr = e; 53 | } 54 | try { 55 | String next = tokenizer.peek(); 56 | if(!"".equals(next)) { 57 | byte[] errStat = tokenizer.errState(); 58 | stateErr = new RuntimeException("Invalid statement: " + new String(errStat)); 59 | } 60 | } catch(Exception e) { 61 | e.printStackTrace(); 62 | byte[] errStat = tokenizer.errState(); 63 | stateErr = new RuntimeException("Invalid statement: " + new String(errStat)); 64 | } 65 | if(stateErr != null) { 66 | throw stateErr; 67 | } 68 | return state; 69 | } 70 | 71 | private static Drop parseDrop(Tokenizer tokenizer) throws Exception { 72 | if(!"table".equals(tokenizer.peek())) { 73 | throw Error.InvalidCommandException; 74 | } 75 | tokenizer.pop(); 76 | 77 | String tableName = tokenizer.peek(); 78 | if(!isName(tableName)) { 79 | throw Error.InvalidCommandException; 80 | } 81 | tokenizer.pop(); 82 | 83 | if(!"".equals(tokenizer.peek())) { 84 | throw Error.InvalidCommandException; 85 | } 86 | 87 | Drop drop = new Drop(); 88 | drop.tableName = tableName; 89 | return drop; 90 | } 91 | 92 | private static Select parseSelect(Tokenizer tokenizer) throws Exception { 93 | Select read = new Select(); 94 | 95 | List fields = new ArrayList<>(); 96 | String asterisk = tokenizer.peek(); 97 | if("*".equals(asterisk)) { 98 | fields.add(asterisk); 99 | tokenizer.pop(); 100 | } else { 101 | while (true) { 102 | String field = tokenizer.peek(); 103 | if(!isName(field)) { 104 | throw Error.InvalidCommandException; 105 | } 106 | fields.add(field); 107 | tokenizer.pop(); 108 | if(",".equals(tokenizer.peek())) { 109 | tokenizer.pop(); 110 | } else { 111 | break; 112 | } 113 | } 114 | } 115 | read.fields = fields.toArray(new String[fields.size()]); 116 | 117 | if(!"from".equals(tokenizer.peek())) { 118 | throw Error.InvalidCommandException; 119 | } 120 | tokenizer.pop(); 121 | 122 | String tableName = tokenizer.peek(); 123 | if(!isName(tableName)) { 124 | throw Error.InvalidCommandException; 125 | } 126 | read.tableName = tableName; 127 | tokenizer.pop(); 128 | 129 | String temp = tokenizer.peek(); 130 | if("".equals(temp)) { 131 | read.where = null; 132 | return read; 133 | } 134 | read.where = parseWhere(tokenizer); 135 | return read; 136 | } 137 | 138 | private static Insert parseInsert(Tokenizer tokenizer) throws Exception { 139 | Insert insert = new Insert(); 140 | if(!"into".equals(tokenizer.peek())) { 141 | throw Error.InvalidCommandException; 142 | } 143 | tokenizer.pop(); 144 | 145 | String tableName = tokenizer.peek(); 146 | if(!isName(tableName)) { 147 | throw Error.InvalidCommandException; 148 | } 149 | insert.tableName = tableName; 150 | tokenizer.pop(); 151 | 152 | if(!"values".equals(tokenizer.peek())) { 153 | throw Error.InvalidCommandException; 154 | } 155 | 156 | List values = new ArrayList<>(); 157 | while (true) { 158 | tokenizer.pop(); 159 | String value = tokenizer.peek(); 160 | if("".equals(value)) { 161 | break; 162 | } else { 163 | values.add(value); 164 | } 165 | } 166 | insert.values = values.toArray(new String[values.size()]); 167 | return insert; 168 | } 169 | 170 | private static Delete parseDelete(Tokenizer tokenizer) throws Exception { 171 | Delete delete = new Delete(); 172 | 173 | if(!"from".equals(tokenizer.peek())) { 174 | throw Error.InvalidCommandException; 175 | } 176 | tokenizer.pop(); 177 | 178 | String tableName = tokenizer.peek(); 179 | if(!isName(tableName)) { 180 | throw Error.InvalidCommandException; 181 | } 182 | delete.tableName = tableName; 183 | tokenizer.pop(); 184 | 185 | delete.where = parseWhere(tokenizer); 186 | return delete; 187 | } 188 | 189 | private static Update parseUpdate(Tokenizer tokenizer) throws Exception { 190 | Update update = new Update(); 191 | update.tableName = tokenizer.peek(); 192 | 193 | tokenizer.pop(); 194 | if(!"set".equals(tokenizer.peek())) { 195 | throw Error.InvalidCommandException; 196 | } 197 | 198 | tokenizer.pop(); 199 | update.fieldName = tokenizer.peek(); 200 | 201 | tokenizer.pop(); 202 | if(!"=".equals(tokenizer.peek())) { 203 | throw Error.InvalidCommandException; 204 | } 205 | 206 | tokenizer.pop(); 207 | update.value = tokenizer.peek(); 208 | 209 | tokenizer.pop(); 210 | String temp = tokenizer.peek(); 211 | if("".equals(temp)) { 212 | update.where = null; 213 | return update; 214 | } 215 | 216 | update.where = parseWhere(tokenizer); 217 | return update; 218 | } 219 | 220 | private static Where parseWhere(Tokenizer tokenizer) throws Exception { 221 | Where where = new Where(); 222 | 223 | if(!"where".equals(tokenizer.peek())) { 224 | throw Error.InvalidCommandException; 225 | } 226 | tokenizer.pop(); 227 | 228 | SingleExpression exp1 = parseSingleExp(tokenizer); 229 | where.singleExp1 = exp1; 230 | 231 | String logicOperation = tokenizer.peek(); 232 | if("".equals(logicOperation)) { 233 | where.logicOp = logicOperation; 234 | return where; 235 | } 236 | if(!isLogicOperation(logicOperation)) { 237 | throw Error.InvalidCommandException; 238 | } 239 | where.logicOp = logicOperation; 240 | tokenizer.pop(); 241 | 242 | SingleExpression exp2 = parseSingleExp(tokenizer); 243 | where.singleExp2 = exp2; 244 | 245 | if(!"".equals(tokenizer.peek())) { 246 | throw Error.InvalidCommandException; 247 | } 248 | return where; 249 | } 250 | 251 | private static boolean isLogicOperation(String operation) { 252 | return ("and".equals(operation) || "or".equals(operation)); 253 | } 254 | 255 | private static SingleExpression parseSingleExp(Tokenizer tokenizer) throws Exception { 256 | SingleExpression exp = new SingleExpression(); 257 | 258 | String field = tokenizer.peek(); 259 | if(!isName(field)) { 260 | throw Error.InvalidCommandException; 261 | } 262 | exp.field = field; 263 | tokenizer.pop(); 264 | 265 | String operation = tokenizer.peek(); 266 | if(!isCompareOperation(operation)) { 267 | throw Error.InvalidCommandException; 268 | } 269 | exp.compareOp = operation; 270 | tokenizer.pop(); 271 | 272 | exp.value = tokenizer.peek(); 273 | tokenizer.pop(); 274 | return exp; 275 | } 276 | 277 | private static boolean isCompareOperation(String operation) { 278 | return ("=".equals(operation) || ">".equals(operation) || "<".equals(operation)); 279 | } 280 | 281 | private static Show parseShow(Tokenizer tokenizer) throws Exception { 282 | String temp = tokenizer.peek(); 283 | if("".equals(temp)) { 284 | return new Show(); 285 | } 286 | throw Error.InvalidCommandException; 287 | } 288 | 289 | private static Create parseCreate(Tokenizer tokenizer) throws Exception { 290 | if(!"table".equals(tokenizer.peek())) { 291 | throw Error.InvalidCommandException; 292 | } 293 | tokenizer.pop(); 294 | 295 | Create create = new Create(); 296 | String name = tokenizer.peek(); 297 | if(!isName(name)) { 298 | throw Error.InvalidCommandException; 299 | } 300 | create.tableName = name; 301 | 302 | List fNames = new ArrayList<>(); 303 | List fTypes = new ArrayList<>(); 304 | while (true) { 305 | tokenizer.pop(); 306 | String field = tokenizer.peek(); 307 | if("(".equals(field)) { 308 | break; 309 | } 310 | if(!isName(field)) { 311 | throw Error.InvalidCommandException; 312 | } 313 | tokenizer.pop(); 314 | String filedType = tokenizer.peek(); 315 | if(!isType(filedType)) { 316 | throw Error.InvalidCommandException; 317 | } 318 | fNames.add(field); 319 | fTypes.add(filedType); 320 | tokenizer.pop(); 321 | 322 | String next = tokenizer.peek(); 323 | if(",".equals(next)){ 324 | continue; 325 | } else if ("".equals(next)) { 326 | throw Error.TableNoIndexException; 327 | } else if ("(".equals(next)) { 328 | break; 329 | } else { 330 | throw Error.InvalidCommandException; 331 | } 332 | } 333 | create.fieldName = fNames.toArray(new String[fNames.size()]); 334 | create.fieldType = fTypes.toArray(new String[fTypes.size()]); 335 | 336 | tokenizer.pop(); 337 | if(!"index".equals(tokenizer.peek())) { 338 | throw Error.InvalidCommandException; 339 | } 340 | 341 | List indexes = new ArrayList<>(); 342 | while (true) { 343 | tokenizer.pop(); 344 | String field = tokenizer.peek(); 345 | if(")".equals(field)) { 346 | break; 347 | } 348 | if (!isName(field)) { 349 | throw Error.InvalidCommandException; 350 | } else { 351 | indexes.add(field); 352 | } 353 | } 354 | create.index = indexes.toArray(new String[indexes.size()]); 355 | tokenizer.pop(); 356 | 357 | if(!"".equals(tokenizer.peek())) { 358 | throw Error.InvalidCommandException; 359 | } 360 | return create; 361 | } 362 | 363 | private static boolean isType(String filedType) { 364 | return ("int32".equals(filedType) || "int64".equals(filedType) || "string".equals(filedType)); 365 | } 366 | 367 | private static Abort parseAbort(Tokenizer tokenizer) throws Exception { 368 | if(!"".equals(tokenizer.peek())) { 369 | throw Error.InvalidCommandException; 370 | } 371 | return new Abort(); 372 | } 373 | 374 | private static Commit parseCommit(Tokenizer tokenizer) throws Exception { 375 | if(!"".equals(tokenizer.peek())) { 376 | throw Error.InvalidCommandException; 377 | } 378 | return new Commit(); 379 | } 380 | 381 | private static Begin parseBegin(Tokenizer tokenizer) throws Exception { 382 | String isolation = tokenizer.peek(); 383 | Begin begin = new Begin(); 384 | if("".equals(isolation)) { 385 | return begin; 386 | } 387 | if(!"isolation".equals(isolation)) { 388 | throw Error.InvalidCommandException; 389 | } 390 | 391 | tokenizer.pop(); 392 | String level = tokenizer.peek(); 393 | if(!"level".equals(level)) { 394 | throw Error.InvalidCommandException; 395 | } 396 | tokenizer.pop(); 397 | 398 | String temp1 = tokenizer.peek(); 399 | if("read".equals(temp1)) { 400 | tokenizer.pop(); 401 | String temp2 = tokenizer.peek(); 402 | if("committed".equals(temp2)) { 403 | tokenizer.pop(); 404 | if(!"".equals(tokenizer.peek())) { 405 | throw Error.InvalidCommandException; 406 | } 407 | return begin; 408 | } else { 409 | throw Error.InvalidCommandException; 410 | } 411 | } else if ("repeatable".equals(temp1)) { 412 | tokenizer.pop(); 413 | String temp2 = tokenizer.peek(); 414 | if("read".equals(temp2)) { 415 | begin.isRepeatableRead = true; 416 | tokenizer.pop(); 417 | if(!"".equals(tokenizer.peek())) { 418 | throw Error.InvalidCommandException; 419 | } 420 | return begin; 421 | } else { 422 | throw Error.InvalidCommandException; 423 | } 424 | } else { 425 | throw Error.InvalidCommandException; 426 | } 427 | } 428 | 429 | private static boolean isName(String name) { 430 | return !(name.length() == 1 && !Tokenizer.isAlphaBeta(name.getBytes()[0])); 431 | } 432 | 433 | } 434 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/Tokenizer.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser; 2 | 3 | import cn.edu.gzhu.common.Error; 4 | 5 | public class Tokenizer { 6 | private byte[] statement; 7 | private int position; 8 | private String currentToken; 9 | private boolean flushToken; 10 | private Exception err; 11 | 12 | public Tokenizer(byte[] statement){ 13 | this.statement = statement; 14 | this.position = 0; 15 | this.currentToken = ""; 16 | this.flushToken = true; 17 | } 18 | 19 | public String peek() throws Exception { 20 | if(err != null) { 21 | throw err; 22 | } 23 | if(flushToken) { 24 | String token = null; 25 | try { 26 | token = next(); 27 | } catch (Exception e){ 28 | err = e; 29 | throw e; 30 | } 31 | currentToken = token; 32 | flushToken = false; 33 | } 34 | return currentToken; 35 | } 36 | 37 | public void pop() { 38 | flushToken = true; 39 | } 40 | 41 | public byte[] errState(){ 42 | byte[] res = new byte[statement.length + 3]; 43 | System.arraycopy(statement, 0, res, 0, position); 44 | System.arraycopy("<< ".getBytes(), 0, res, position, 3); 45 | System.arraycopy(statement, position, res, position + 3, statement.length - position); 46 | return res; 47 | } 48 | 49 | private void popByte() { 50 | position ++; 51 | if(position > statement.length) { 52 | position = statement.length; 53 | } 54 | } 55 | 56 | private Byte peekByte() { 57 | if(position == statement.length) { 58 | return null; 59 | } 60 | return statement[position]; 61 | } 62 | 63 | private String next() throws Exception { 64 | if(err != null){ 65 | throw err; 66 | } 67 | return nextMetaState(); 68 | } 69 | 70 | private String nextMetaState() throws Exception { 71 | while (true) { 72 | Byte b = peekByte(); 73 | if(b == null) { 74 | return ""; 75 | } 76 | if(!isBlank(b)){ 77 | break; 78 | } 79 | popByte(); 80 | } 81 | byte b = peekByte(); 82 | if(isSymbol(b)){ 83 | popByte(); 84 | return new String(new byte[]{b}); 85 | } else if(b == '"' || b == '\''){ 86 | return nextQuoteState(); 87 | } else if(isAlphaBeta(b) || isDigit(b)){ 88 | return nextTokenState(); 89 | } else { 90 | err = Error.InvalidCommandException; 91 | throw err; 92 | } 93 | } 94 | 95 | private String nextTokenState() throws Exception { 96 | StringBuilder sb = new StringBuilder(); 97 | while (true) { 98 | Byte b = peekByte(); 99 | if(b == null || !(isAlphaBeta(b) || isDigit(b) || b == '_')){ 100 | if(b != null && isBlank(b)) { 101 | popByte(); 102 | } 103 | return sb.toString(); 104 | } 105 | sb.append(new String(new byte[]{b})); 106 | popByte(); 107 | } 108 | } 109 | 110 | static boolean isDigit(byte b){ 111 | return (b >= '0' && b <= '9'); 112 | } 113 | 114 | static boolean isAlphaBeta(byte b) { 115 | return ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')); 116 | } 117 | 118 | private String nextQuoteState() throws Exception { 119 | byte quote = peekByte(); 120 | popByte(); 121 | StringBuilder sb = new StringBuilder(); 122 | while (true) { 123 | Byte b = peekByte(); 124 | if(b == null){ 125 | err = Error.InvalidCommandException; 126 | throw err; 127 | } 128 | if(b == quote) { 129 | popByte(); 130 | break; 131 | } 132 | sb.append(new String(new byte[]{b})); 133 | popByte(); 134 | } 135 | return sb.toString(); 136 | } 137 | 138 | static boolean isSymbol(byte b){ 139 | return (b == '>' || b == '<' || b == '=' || b =='*' || b == ',' || b == '(' || b == ')'); 140 | } 141 | 142 | static boolean isBlank(byte b) { 143 | return (b == '\n' || b == ' ' || b == '\t'); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/statement/Abort.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser.statement; 2 | 3 | public class Abort { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/statement/Begin.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser.statement; 2 | 3 | public class Begin { 4 | public boolean isRepeatableRead; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/statement/Commit.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser.statement; 2 | 3 | public class Commit { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/statement/Create.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser.statement; 2 | 3 | public class Create { 4 | public String tableName; 5 | public String[] fieldName; 6 | public String[] fieldType; 7 | public String[] index; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/statement/Delete.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser.statement; 2 | 3 | public class Delete { 4 | public String tableName; 5 | public Where where; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/statement/Drop.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser.statement; 2 | 3 | public class Drop { 4 | public String tableName; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/statement/Insert.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser.statement; 2 | 3 | public class Insert { 4 | public String tableName; 5 | public String[] values; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/statement/Select.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser.statement; 2 | 3 | public class Select { 4 | public String tableName; 5 | public String[] fields; 6 | public Where where; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/statement/Show.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser.statement; 2 | 3 | public class Show { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/statement/SingleExpression.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser.statement; 2 | 3 | public class SingleExpression { 4 | public String field; 5 | public String compareOp; 6 | public String value; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/statement/Update.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser.statement; 2 | 3 | public class Update { 4 | public String tableName; 5 | public String fieldName; 6 | public String value; 7 | public Where where; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/parser/statement/Where.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.parser.statement; 2 | 3 | public class Where { 4 | public SingleExpression singleExp1; 5 | public String logicOp; 6 | public SingleExpression singleExp2; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/tbm/BeginRes.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.tbm; 2 | 3 | public class BeginRes { 4 | public long xid; 5 | public byte[] result; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/tbm/Booter.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.tbm; 2 | 3 | import cn.edu.gzhu.backend.utils.Panic; 4 | 5 | import cn.edu.gzhu.common.Error; 6 | 7 | import java.io.File; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.StandardCopyOption; 12 | 13 | // 记录第一个表的 uid 14 | public class Booter { 15 | public static final String BOOTER_SUFFIX = ".bt"; 16 | public static final String BOOTER_TMP_SUFFIX = ".bt_tmp"; 17 | 18 | private String path; 19 | private File file; 20 | 21 | public Booter(String path, File file) { 22 | this.path = path; 23 | this.file = file; 24 | } 25 | 26 | public static Booter create(String path) { 27 | removeBadTmp(path); 28 | File f = new File(path + BOOTER_SUFFIX); 29 | try { 30 | if(!f.createNewFile()) { 31 | Panic.panic(Error.FileExistsException); 32 | } 33 | } catch (Exception e) { 34 | Panic.panic(e); 35 | } 36 | if(!f.canRead() || !f.canWrite()) { 37 | Panic.panic(Error.FileCannotRWException); 38 | } 39 | return new Booter(path, f); 40 | } 41 | 42 | private static void removeBadTmp(String path) { 43 | new File(path + BOOTER_TMP_SUFFIX).delete(); 44 | } 45 | 46 | public static Booter open(String path) { 47 | removeBadTmp(path); 48 | File f = new File(path + BOOTER_SUFFIX); 49 | if(!f.exists()) { 50 | Panic.panic(Error.FileNotExistsException); 51 | } 52 | if(!f.canRead() || !f.canWrite()) { 53 | Panic.panic(Error.FileCannotRWException); 54 | } 55 | return new Booter(path ,f); 56 | } 57 | 58 | public byte[] load() { 59 | byte[] buf = null; 60 | try { 61 | buf = Files.readAllBytes(file.toPath()); 62 | } catch (IOException e) { 63 | Panic.panic(e); 64 | } 65 | return buf; 66 | } 67 | 68 | public void update(byte[] data) { 69 | File tmp = new File(path + BOOTER_TMP_SUFFIX); 70 | try { 71 | tmp.createNewFile(); 72 | } catch (Exception e) { 73 | Panic.panic(e); 74 | } 75 | if(!tmp.canRead() || !tmp.canWrite()) { 76 | Panic.panic(Error.FileCannotRWException); 77 | } 78 | try (FileOutputStream out = new FileOutputStream(tmp)) { 79 | out.write(data); 80 | out.flush(); 81 | } catch (IOException e) { 82 | Panic.panic(e); 83 | } 84 | try { 85 | Files.move(tmp.toPath(), new File(path + BOOTER_SUFFIX).toPath(), StandardCopyOption.REPLACE_EXISTING); 86 | } catch (IOException e) { 87 | Panic.panic(e); 88 | } 89 | file = new File(path + BOOTER_SUFFIX); 90 | if(!file.canRead() || !file.canWrite()) { 91 | Panic.panic(Error.FileCannotRWException); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/tbm/Field.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.tbm; 2 | 3 | import cn.edu.gzhu.backend.im.BPlusTree; 4 | import cn.edu.gzhu.backend.parser.statement.SingleExpression; 5 | import cn.edu.gzhu.backend.tbm.impl.TableManagerImpl; 6 | import cn.edu.gzhu.backend.tm.impl.TransactionManagerImpl; 7 | import cn.edu.gzhu.backend.utils.Panic; 8 | import cn.edu.gzhu.backend.utils.ParseStringRes; 9 | import cn.edu.gzhu.backend.utils.Parser; 10 | 11 | import cn.edu.gzhu.common.Error; 12 | import com.google.common.primitives.Bytes; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | /** 18 | * field 表示字段信息 19 | * 二进制格式为: 20 | * [FieldName][TypeName][IndexUid] 管理表和字段的数据结构:表名、表字段信息和字段索引等。 21 | * 如果field无索引,IndexUid为0 22 | */ 23 | public class Field { 24 | long uid; 25 | Table table; 26 | String fieldName; 27 | String fieldType; 28 | long index; 29 | BPlusTree bTree; 30 | 31 | public Field(long uid, Table table) { 32 | this.uid = uid; 33 | this.table = table; 34 | } 35 | 36 | public Field(Table table, String fieldName, String fieldType, long index) { 37 | this.table = table; 38 | this.fieldName = fieldName; 39 | this.fieldType = fieldType; 40 | this.index = index; 41 | } 42 | 43 | public static Field loadField(Table table, long uid) { 44 | byte[] raw = null; 45 | try { 46 | raw = ((TableManagerImpl) table.tableManager).vm.read(TransactionManagerImpl.SUPER_XID, uid); 47 | } catch (Exception e){ 48 | Panic.panic(e); 49 | } 50 | assert raw != null; 51 | return new Field(uid, table).parseSelf(raw); 52 | } 53 | 54 | private Field parseSelf(byte[] raw) { 55 | int position = 0; 56 | ParseStringRes res = Parser.parseString(raw); 57 | fieldName = res.str; 58 | position += res.next; 59 | res = Parser.parseString(Arrays.copyOfRange(raw, position, raw.length)); 60 | fieldType = res.str; 61 | position += res.next; 62 | this.index = Parser.parseLong(Arrays.copyOfRange(raw, position, position + 8)); 63 | if(index != 0) { 64 | try { 65 | bTree = BPlusTree.load(index, ((TableManagerImpl)(table.tableManager)).dm); 66 | } catch (Exception e) { 67 | Panic.panic(e); 68 | } 69 | } 70 | return this; 71 | } 72 | 73 | public static Field createField(Table table, long xid, String filedName, String filedType, boolean indexed) throws Exception { 74 | typeCheck(filedType); 75 | Field f = new Field(table, filedName, filedType, 0); 76 | if(indexed) { 77 | long index = BPlusTree.create(((TableManagerImpl)table.tableManager).dm); 78 | BPlusTree bt = BPlusTree.load(index, ((TableManagerImpl)table.tableManager).dm); 79 | f.index = index; 80 | f.bTree = bt; 81 | } 82 | f.persistSelf(xid); 83 | return f; 84 | } 85 | 86 | private void persistSelf(long xid) throws Exception { 87 | byte[] nameRaw = Parser.string2Byte(fieldName); 88 | byte[] typeRaw = Parser.string2Byte(fieldType); 89 | byte[] indexRaw = Parser.long2Byte(index); 90 | this.uid = ((TableManagerImpl)table.tableManager).vm.insert(xid, Bytes.concat(nameRaw, typeRaw, indexRaw)); 91 | } 92 | 93 | private static void typeCheck(String fieldType) throws Exception { 94 | if(!"int32".equals(fieldType) && !"int64".equals(fieldType) && !"string".equals(fieldType)) { 95 | throw Error.InvalidFieldException; 96 | } 97 | } 98 | 99 | public boolean isIndexed() { 100 | return index != 0; 101 | } 102 | 103 | public void insert(Object key, long uid) throws Exception { 104 | long uKey = value2Uid(key); 105 | bTree.insert(uKey, uid); 106 | } 107 | 108 | public List search(long left, long right) throws Exception { 109 | return bTree.searchRange(left, right); 110 | } 111 | 112 | public Object string2Value(String str) { 113 | switch (fieldType) { 114 | case "int32": 115 | return Integer.parseInt(str); 116 | case "int64": 117 | return Long.parseLong(str); 118 | case "string": 119 | return str; 120 | } 121 | return null; 122 | } 123 | 124 | public long value2Uid(Object key) { 125 | long uid = 0; 126 | switch(fieldType) { 127 | case "string": 128 | uid = Parser.str2Uid((String)key); 129 | break; 130 | case "int32": 131 | int uint = (int)key; 132 | return (long)uint; 133 | case "int64": 134 | uid = (long)key; 135 | break; 136 | } 137 | return uid; 138 | } 139 | 140 | public byte[] value2Raw(Object v) { 141 | byte[] raw = null; 142 | switch(fieldType) { 143 | case "int32": 144 | raw = Parser.int2Byte((int)v); 145 | break; 146 | case "int64": 147 | raw = Parser.long2Byte((long)v); 148 | break; 149 | case "string": 150 | raw = Parser.string2Byte((String)v); 151 | break; 152 | } 153 | return raw; 154 | } 155 | 156 | static class ParseValueRes { 157 | Object v; 158 | int shift; 159 | } 160 | 161 | public ParseValueRes parserValue(byte[] raw) { 162 | ParseValueRes res = new ParseValueRes(); 163 | switch(fieldType) { 164 | case "int32": 165 | res.v = Parser.parseInt(Arrays.copyOf(raw, 4)); 166 | res.shift = 4; 167 | break; 168 | case "int64": 169 | res.v = Parser.parseLong(Arrays.copyOf(raw, 8)); 170 | res.shift = 8; 171 | break; 172 | case "string": 173 | ParseStringRes r = Parser.parseString(raw); 174 | res.v = r.str; 175 | res.shift = r.next; 176 | break; 177 | } 178 | return res; 179 | } 180 | 181 | public String printValue(Object v) { 182 | String str = null; 183 | switch(fieldType) { 184 | case "int32": 185 | str = String.valueOf((int)v); 186 | break; 187 | case "int64": 188 | str = String.valueOf((long)v); 189 | break; 190 | case "string": 191 | str = (String)v; 192 | break; 193 | } 194 | return str; 195 | } 196 | 197 | @Override 198 | public String toString() { 199 | return new StringBuilder("(") 200 | .append(fieldName) 201 | .append(", ") 202 | .append(fieldType) 203 | .append(index!=0?", Index":", NoIndex") 204 | .append(")") 205 | .toString(); 206 | } 207 | 208 | public FieldCalRes calExp(SingleExpression exp) throws Exception { 209 | Object v = null; 210 | FieldCalRes res = new FieldCalRes(); 211 | switch(exp.compareOp) { 212 | case "<": 213 | res.left = 0; 214 | v = string2Value(exp.value); 215 | res.right = value2Uid(v); 216 | if(res.right > 0) { 217 | res.right --; 218 | } 219 | break; 220 | case "=": 221 | v = string2Value(exp.value); 222 | res.left = value2Uid(v); 223 | res.right = res.left; 224 | break; 225 | case ">": 226 | res.right = Long.MAX_VALUE; 227 | v = string2Value(exp.value); 228 | res.left = value2Uid(v) + 1; 229 | break; 230 | } 231 | return res; 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/tbm/FieldCalRes.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.tbm; 2 | 3 | public class FieldCalRes { 4 | public long left; 5 | public long right; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/tbm/Table.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.tbm; 2 | 3 | import cn.edu.gzhu.backend.parser.statement.*; 4 | import cn.edu.gzhu.backend.tbm.impl.TableManagerImpl; 5 | import cn.edu.gzhu.backend.tm.impl.TransactionManagerImpl; 6 | import cn.edu.gzhu.backend.utils.Panic; 7 | import cn.edu.gzhu.backend.utils.ParseStringRes; 8 | import cn.edu.gzhu.backend.utils.Parser; 9 | import cn.edu.gzhu.backend.tbm.Field.ParseValueRes; 10 | import com.google.common.primitives.Bytes; 11 | 12 | import cn.edu.gzhu.common.Error; 13 | 14 | import java.util.*; 15 | 16 | /** 17 | * Table 维护了表结构 18 | * 二进制结构如下: 19 | * [TableName][NextTable] 20 | * [Field1Uid][Field2Uid]...[FieldNUid] 21 | */ 22 | public class Table { 23 | TableManager tableManager; 24 | public long uid; 25 | public String name; 26 | private byte status; 27 | public long nextUid; 28 | private List fields = new ArrayList<>(); 29 | 30 | public Table(TableManager tableManager, long uid) { 31 | this.tableManager = tableManager; 32 | this.uid = uid; 33 | } 34 | 35 | public Table(TableManager tableManager, String tableName, long nextUid) { 36 | this.tableManager = tableManager; 37 | this.name = tableName; 38 | this.nextUid = nextUid; 39 | } 40 | 41 | private Table parseSelf(byte[] raw) { 42 | int position = 0; 43 | ParseStringRes res = Parser.parseString(raw); 44 | name = res.str; 45 | position += res.next; 46 | nextUid = Parser.parseLong(Arrays.copyOfRange(raw, position, position + 8)); 47 | position += 8; 48 | while (position < raw.length) { 49 | long uid = Parser.parseLong(Arrays.copyOfRange(raw, position, position + 8)); 50 | position += 8; 51 | fields.add(Field.loadField(this, uid)); 52 | } 53 | return this; 54 | } 55 | 56 | public static Table loadTable(TableManager tableManager, long uid) { 57 | byte[] raw = null; 58 | try { 59 | raw = ((TableManagerImpl) tableManager).vm.read(TransactionManagerImpl.SUPER_XID, uid); 60 | } catch (Exception e) { 61 | Panic.panic(e); 62 | } 63 | assert raw != null; 64 | Table table = new Table(tableManager, uid); 65 | return table.parseSelf(raw); 66 | } 67 | 68 | public static Table createTable(TableManager tableManager, long nextUid, long xid, Create create) throws Exception { 69 | Table table = new Table(tableManager, create.tableName, nextUid); 70 | for (int i = 0; i < create.fieldName.length; i++) { 71 | String fieldName = create.fieldName[i]; 72 | String fieldType = create.fieldType[i]; 73 | boolean indexed = false; 74 | for (int j = 0; j < create.index.length; j++) { 75 | if(fieldName.equals(create.index[j])) { 76 | indexed = true; 77 | break; 78 | } 79 | } 80 | table.fields.add(Field.createField(table, xid, fieldName,fieldType, indexed)); 81 | } 82 | return table.persistSelf(xid); 83 | } 84 | 85 | private Table persistSelf(long xid) throws Exception { 86 | byte[] nameRaw = Parser.string2Byte(name); 87 | byte[] nextRaw = Parser.long2Byte(nextUid); 88 | byte[] fieldRaw = new byte[0]; 89 | for (Field field : fields) { 90 | fieldRaw = Bytes.concat(fieldRaw, Parser.long2Byte(field.uid)); 91 | } 92 | uid = ((TableManagerImpl) tableManager).vm.insert(xid, Bytes.concat(nameRaw, nextRaw, fieldRaw)); 93 | return this; 94 | } 95 | 96 | public int delete(long xid, Delete delete) throws Exception { 97 | List uids = parseWhere(delete.where); 98 | int count = 0; 99 | for (Long uid : uids) { 100 | if(((TableManagerImpl)tableManager).vm.delete(xid, uid)) { 101 | count ++; 102 | } 103 | } 104 | return count; 105 | } 106 | 107 | public int update(long xid, Update update) throws Exception { 108 | List uids = parseWhere(update.where); 109 | Field fd = null; 110 | for (Field f : fields) { 111 | if(f.fieldName.equals(update.fieldName)) { 112 | fd = f; 113 | break; 114 | } 115 | } 116 | if(fd == null) { 117 | throw Error.FieldNotFoundException; 118 | } 119 | Object value = fd.string2Value(update.value); 120 | int count = 0; 121 | for (Long uid : uids) { 122 | byte[] raw = ((TableManagerImpl)tableManager).vm.read(xid, uid); 123 | if(raw == null) continue; 124 | 125 | ((TableManagerImpl)tableManager).vm.delete(xid, uid); 126 | 127 | Map entry = parseEntry(raw); 128 | entry.put(fd.fieldName, value); 129 | raw = entry2Raw(entry); 130 | long uuid = ((TableManagerImpl)tableManager).vm.insert(xid, raw); 131 | 132 | count ++; 133 | 134 | for (Field field : fields) { 135 | if(field.isIndexed()) { 136 | field.insert(entry.get(field.fieldName), uuid); 137 | } 138 | } 139 | } 140 | return count; 141 | } 142 | 143 | public String read(long xid, Select read) throws Exception { 144 | List uids = parseWhere(read.where); 145 | StringBuilder sb = new StringBuilder(); 146 | for (Long uid : uids) { 147 | byte[] raw = ((TableManagerImpl)tableManager).vm.read(xid, uid); 148 | if(raw == null) continue; 149 | Map entry = parseEntry(raw); 150 | sb.append(printEntry(entry)).append("\n"); 151 | } 152 | return sb.toString(); 153 | } 154 | 155 | public void insert(long xid, Insert insert) throws Exception { 156 | Map entry = string2Entry(insert.values); 157 | byte[] raw = entry2Raw(entry); 158 | long uid = ((TableManagerImpl)tableManager).vm.insert(xid, raw); 159 | for (Field field : fields) { 160 | if(field.isIndexed()) { 161 | field.insert(entry.get(field.fieldName), uid); 162 | } 163 | } 164 | } 165 | 166 | private Map string2Entry(String[] values) throws Exception { 167 | if(values.length != fields.size()) { 168 | throw Error.InvalidValuesException; 169 | } 170 | Map entry = new HashMap<>(); 171 | for (int i = 0; i < fields.size(); i++) { 172 | Field f = fields.get(i); 173 | Object v = f.string2Value(values[i]); 174 | entry.put(f.fieldName, v); 175 | } 176 | return entry; 177 | } 178 | 179 | private List parseWhere(Where where) throws Exception { 180 | long l0=0, r0=0, l1=0, r1=0; 181 | boolean single = false; 182 | Field fd = null; 183 | if(where == null) { 184 | for (Field field : fields) { 185 | if(field.isIndexed()) { 186 | fd = field; 187 | break; 188 | } 189 | } 190 | l0 = 0; 191 | r0 = Long.MAX_VALUE; 192 | single = true; 193 | } else { 194 | for (Field field : fields) { 195 | if(field.fieldName.equals(where.singleExp1.field)) { 196 | if(!field.isIndexed()) { 197 | throw Error.FieldNotIndexedException; 198 | } 199 | fd = field; 200 | break; 201 | } 202 | } 203 | if(fd == null) { 204 | throw Error.FieldNotFoundException; 205 | } 206 | CalWhereRes res = calWhere(fd, where); 207 | l0 = res.l0; r0 = res.r0; 208 | l1 = res.l1; r1 = res.r1; 209 | single = res.single; 210 | } 211 | List uids = fd.search(l0, r0); 212 | if(!single) { 213 | List tmp = fd.search(l1, r1); 214 | uids.addAll(tmp); 215 | } 216 | return uids; 217 | } 218 | 219 | class CalWhereRes { 220 | long l0, r0, l1, r1; 221 | boolean single; 222 | } 223 | 224 | private CalWhereRes calWhere(Field fd, Where where) throws Exception { 225 | CalWhereRes res = new CalWhereRes(); 226 | switch(where.logicOp) { 227 | case "": 228 | res.single = true; 229 | FieldCalRes r = fd.calExp(where.singleExp1); 230 | res.l0 = r.left; res.r0 = r.right; 231 | break; 232 | case "or": 233 | res.single = false; 234 | r = fd.calExp(where.singleExp1); 235 | res.l0 = r.left; res.r0 = r.right; 236 | r = fd.calExp(where.singleExp2); 237 | res.l1 = r.left; res.r1 = r.right; 238 | break; 239 | case "and": 240 | res.single = true; 241 | r = fd.calExp(where.singleExp1); 242 | res.l0 = r.left; res.r0 = r.right; 243 | r = fd.calExp(where.singleExp2); 244 | res.l1 = r.left; res.r1 = r.right; 245 | if(res.l1 > res.l0) res.l0 = res.l1; 246 | if(res.r1 < res.r0) res.r0 = res.r1; 247 | break; 248 | default: 249 | throw Error.InvalidLogOpException; 250 | } 251 | return res; 252 | } 253 | 254 | private String printEntry(Map entry) { 255 | StringBuilder sb = new StringBuilder("["); 256 | for (int i = 0; i < fields.size(); i++) { 257 | Field field = fields.get(i); 258 | sb.append(field.printValue(entry.get(field.fieldName))); 259 | if(i == fields.size()-1) { 260 | sb.append("]"); 261 | } else { 262 | sb.append(", "); 263 | } 264 | } 265 | return sb.toString(); 266 | } 267 | 268 | private Map parseEntry(byte[] raw) { 269 | int pos = 0; 270 | Map entry = new HashMap<>(); 271 | for (Field field : fields) { 272 | ParseValueRes r = field.parserValue(Arrays.copyOfRange(raw, pos, raw.length)); 273 | entry.put(field.fieldName, r.v); 274 | pos += r.shift; 275 | } 276 | return entry; 277 | } 278 | 279 | private byte[] entry2Raw(Map entry) { 280 | byte[] raw = new byte[0]; 281 | for (Field field : fields) { 282 | raw = Bytes.concat(raw, field.value2Raw(entry.get(field.fieldName))); 283 | } 284 | return raw; 285 | } 286 | 287 | @Override 288 | public String toString() { 289 | StringBuilder sb = new StringBuilder("{"); 290 | sb.append(name).append(": "); 291 | for(Field field : fields) { 292 | sb.append(field.toString()); 293 | if(field == fields.get(fields.size()-1)) { 294 | sb.append("}"); 295 | } else { 296 | sb.append(", "); 297 | } 298 | } 299 | return sb.toString(); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/tbm/TableManager.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.tbm; 2 | 3 | import cn.edu.gzhu.backend.dm.DataManager; 4 | import cn.edu.gzhu.backend.parser.statement.*; 5 | import cn.edu.gzhu.backend.tbm.impl.TableManagerImpl; 6 | import cn.edu.gzhu.backend.utils.Parser; 7 | import cn.edu.gzhu.backend.vm.VersionManager; 8 | 9 | public interface TableManager { 10 | BeginRes begin(Begin begin); 11 | byte[] commit(long xid) throws Exception; 12 | byte[] abort(long xid); 13 | byte[] show(long xid); 14 | byte[] create(long xid, Create create) throws Exception; 15 | byte[] insert(long xid, Insert insert) throws Exception; 16 | byte[] read(long xid, Select select) throws Exception; 17 | byte[] update(long xid, Update update) throws Exception; 18 | byte[] delete(long xid, Delete delete) throws Exception; 19 | 20 | public static TableManager create(String path, VersionManager vm, DataManager dm) { 21 | Booter booter = Booter.create(path); 22 | booter.update(Parser.long2Byte(0)); 23 | return new TableManagerImpl(vm, dm, booter); 24 | } 25 | 26 | public static TableManager open(String path, VersionManager vm, DataManager dm) { 27 | Booter booter = Booter.open(path); 28 | return new TableManagerImpl(vm, dm, booter); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/tbm/impl/TableManagerImpl.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.tbm.impl; 2 | 3 | import cn.edu.gzhu.backend.dm.DataManager; 4 | import cn.edu.gzhu.backend.utils.Parser; 5 | import cn.edu.gzhu.backend.parser.statement.*; 6 | import cn.edu.gzhu.backend.tbm.BeginRes; 7 | import cn.edu.gzhu.backend.tbm.Booter; 8 | import cn.edu.gzhu.backend.tbm.Table; 9 | import cn.edu.gzhu.backend.tbm.TableManager; 10 | import cn.edu.gzhu.backend.vm.VersionManager; 11 | 12 | import cn.edu.gzhu.common.Error; 13 | 14 | import java.util.ArrayList; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.concurrent.locks.Lock; 19 | import java.util.concurrent.locks.ReentrantLock; 20 | 21 | public class TableManagerImpl implements TableManager { 22 | public VersionManager vm; 23 | public DataManager dm; 24 | private Booter booter; 25 | private Map tableCache; 26 | private Map> xidTableCache; 27 | private Lock lock; 28 | 29 | public TableManagerImpl(VersionManager vm, DataManager dm, Booter booter) { 30 | this.vm = vm; 31 | this.dm = dm; 32 | this.booter = booter; 33 | this.tableCache = new HashMap<>(); 34 | this.xidTableCache = new HashMap<>(); 35 | lock = new ReentrantLock(); 36 | loadTables(); 37 | } 38 | 39 | private void loadTables() { 40 | long uid = firstTableUid(); 41 | while (uid != 0) { 42 | Table table = Table.loadTable(this, uid); 43 | uid = table.nextUid; 44 | tableCache.put(table.name, table); 45 | } 46 | } 47 | 48 | private long firstTableUid() { 49 | byte[] raw = booter.load(); 50 | return Parser.parseLong(raw); 51 | } 52 | 53 | private void updateFirstTableUid(long uid) { 54 | byte[] raw = Parser.long2Byte(uid); 55 | booter.update(raw); 56 | } 57 | 58 | @Override 59 | public BeginRes begin(Begin begin) { 60 | BeginRes res = new BeginRes(); 61 | int level = begin.isRepeatableRead ? 1 : 0; 62 | res.xid = vm.begin(level); 63 | res.result = "begin".getBytes(); 64 | return res; 65 | } 66 | 67 | @Override 68 | public byte[] commit(long xid) throws Exception { 69 | vm.commit(xid); 70 | return "commit".getBytes(); 71 | } 72 | 73 | @Override 74 | public byte[] abort(long xid) { 75 | vm.abort(xid); 76 | return "abort".getBytes(); 77 | } 78 | 79 | @Override 80 | public byte[] show(long xid) { 81 | lock.lock(); 82 | try { 83 | StringBuilder sb = new StringBuilder(); 84 | for (Table tb : tableCache.values()) { 85 | sb.append(tb.toString()).append("\n"); 86 | } 87 | List t = xidTableCache.get(xid); 88 | if(t == null) { 89 | return "\n".getBytes(); 90 | } 91 | for (Table tb : t) { 92 | sb.append(tb.toString()).append("\n"); 93 | } 94 | return sb.toString().getBytes(); 95 | } finally { 96 | lock.unlock(); 97 | } 98 | } 99 | 100 | @Override 101 | public byte[] create(long xid, Create create) throws Exception { 102 | lock.lock(); 103 | try { 104 | if(tableCache.containsKey(create.tableName)) { 105 | throw Error.DuplicatedTableException; 106 | } 107 | Table table = Table.createTable(this, firstTableUid(), xid, create); 108 | updateFirstTableUid(table.uid); 109 | tableCache.put(create.tableName, table); 110 | if(!xidTableCache.containsKey(xid)) { 111 | xidTableCache.put(xid, new ArrayList<>()); 112 | } 113 | xidTableCache.get(xid).add(table); 114 | return ("create " + create.tableName).getBytes(); 115 | } finally { 116 | lock.unlock(); 117 | } 118 | } 119 | 120 | @Override 121 | public byte[] insert(long xid, Insert insert) throws Exception { 122 | lock.lock(); 123 | Table table = tableCache.get(insert.tableName); 124 | lock.unlock(); 125 | if(table == null) { 126 | throw Error.TableNotFoundException; 127 | } 128 | table.insert(xid, insert); 129 | return "insert".getBytes(); 130 | } 131 | 132 | @Override 133 | public byte[] read(long xid, Select read) throws Exception { 134 | lock.lock(); 135 | Table table = tableCache.get(read.tableName); 136 | lock.unlock(); 137 | if(table == null) { 138 | throw Error.TableNotFoundException; 139 | } 140 | return table.read(xid, read).getBytes(); 141 | } 142 | 143 | @Override 144 | public byte[] update(long xid, Update update) throws Exception { 145 | lock.lock(); 146 | Table table = tableCache.get(update.tableName); 147 | lock.unlock(); 148 | if(table == null) { 149 | throw Error.TableNotFoundException; 150 | } 151 | int count = table.update(xid, update); 152 | return ("update " + count).getBytes(); 153 | } 154 | 155 | @Override 156 | public byte[] delete(long xid, Delete delete) throws Exception { 157 | lock.lock(); 158 | Table table = tableCache.get(delete.tableName); 159 | lock.unlock(); 160 | if(table == null) { 161 | throw Error.TableNotFoundException; 162 | } 163 | int count = table.delete(xid, delete); 164 | return ("delete " + count).getBytes(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/tm/TransactionManager.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.tm; 2 | 3 | import cn.edu.gzhu.backend.tm.impl.TransactionManagerImpl; 4 | import cn.edu.gzhu.backend.utils.Panic; 5 | 6 | import java.io.File; 7 | import java.io.FileNotFoundException; 8 | import java.io.IOException; 9 | import java.io.RandomAccessFile; 10 | import java.nio.ByteBuffer; 11 | import java.nio.channels.FileChannel; 12 | 13 | import cn.edu.gzhu.common.Error; 14 | 15 | /** 16 | * 每个事务都有一个XID,这个 ID 唯一标识了这个事务。事务的 XID 从 1 开始标号,并自增,不可重复。 17 | * 并特殊规定 XID 0 是一个超级事务(Super Transaction)。 18 | * 当一些操作在没有申请事务的情况下进行,那么可以将操作的 XID 设置为 0。XID 为 0 的事务的状态永远是 committed。 19 | * 20 | * 每个事务都有下面三种状态:1.active,正在进行,尚未结束。 2.committed,已提交。 3.aborted,已撤销(回滚)。 21 | */ 22 | public interface TransactionManager { 23 | // 开启一个新事物 24 | long begin(); 25 | // 提交一个事务 26 | void commit(long xid); 27 | // 取消一个事务 28 | void abort(long xid); 29 | // 查询一个事务的状态是否是正在进行的状态 30 | boolean isActive(long xid); 31 | // 查询一个事务的状态是否是已提交 32 | boolean isCommitted(long xid); 33 | // 查询一个事务的状态是否是已取消 34 | boolean isAborted(long xid); 35 | // 关闭TM 36 | void close(); 37 | 38 | public static TransactionManagerImpl create(String path){ 39 | File file = new File(path + TransactionManagerImpl.XID_SUFFIX); 40 | try { 41 | if(!file.createNewFile()){ 42 | Panic.panic(Error.FileExistsException); 43 | } 44 | } catch (Exception e){ 45 | Panic.panic(e); 46 | } 47 | if(!file.canRead() || !file.canWrite()){ 48 | Panic.panic(Error.FileCannotRWException); 49 | } 50 | FileChannel fc = null; 51 | RandomAccessFile raf = null; 52 | try { 53 | raf = new RandomAccessFile(file, "rw"); 54 | fc = raf.getChannel(); 55 | } catch (FileNotFoundException e){ 56 | Panic.panic(e); 57 | } 58 | // 写空 XID 文件头 59 | ByteBuffer buf = ByteBuffer.wrap(new byte[TransactionManagerImpl.LEN_XID_HEADER_LENGTH]); 60 | try { 61 | fc.position(0); 62 | fc.write(buf); 63 | } catch (IOException e) { 64 | Panic.panic(e); 65 | } 66 | 67 | return new TransactionManagerImpl(raf, fc); 68 | } 69 | 70 | public static TransactionManagerImpl open(String path){ 71 | File f = new File(path+TransactionManagerImpl.XID_SUFFIX); 72 | if(!f.exists()) { 73 | Panic.panic(Error.FileNotExistsException); 74 | } 75 | if(!f.canRead() || !f.canWrite()) { 76 | Panic.panic(Error.FileCannotRWException); 77 | } 78 | 79 | FileChannel fc = null; 80 | RandomAccessFile raf = null; 81 | try { 82 | raf = new RandomAccessFile(f, "rw"); 83 | fc = raf.getChannel(); 84 | } catch (FileNotFoundException e) { 85 | Panic.panic(e); 86 | } 87 | 88 | return new TransactionManagerImpl(raf, fc); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/tm/impl/TransactionManagerImpl.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.tm.impl; 2 | 3 | import cn.edu.gzhu.backend.tm.TransactionManager; 4 | import cn.edu.gzhu.backend.utils.Panic; 5 | import cn.edu.gzhu.backend.utils.Parser; 6 | import cn.edu.gzhu.common.Error; 7 | 8 | import java.io.IOException; 9 | import java.io.RandomAccessFile; 10 | import java.nio.ByteBuffer; 11 | import java.nio.channels.FileChannel; 12 | import java.util.concurrent.locks.Lock; 13 | import java.util.concurrent.locks.ReentrantLock; 14 | 15 | public class TransactionManagerImpl implements TransactionManager { 16 | // XID文件头长度 17 | public static final int LEN_XID_HEADER_LENGTH = 8; 18 | // 每个事务的占用长度 19 | private static final int XID_FIELD_SIZE = 1; 20 | 21 | // 事务的三种状态 22 | private static final byte FIELD_TRAN_ACTIVE = 0; 23 | private static final byte FIELD_TRAN_COMMITTED = 1; 24 | private static final byte FIELD_TRAN_ABORTED = 2; 25 | 26 | // 超级事务,永远为committed状态 27 | public static final long SUPER_XID = 0; 28 | 29 | public static final String XID_SUFFIX = ".xid"; 30 | 31 | private RandomAccessFile file; 32 | private FileChannel fc; 33 | private long xidCounter; 34 | private Lock counterLock; 35 | 36 | public TransactionManagerImpl(RandomAccessFile raf, FileChannel fc){ 37 | this.file = raf; 38 | this.fc = fc; 39 | counterLock = new ReentrantLock(); 40 | checkXIDCounter(); 41 | } 42 | 43 | /** 44 | * 检查XID文件是否合法 45 | * 读取XID_FILE_HEADER中的XIDCounter,根据它计算文件的理论长度,对比实际长度 46 | */ 47 | private void checkXIDCounter() { 48 | long fileLength = 0; 49 | try{ 50 | fileLength = file.length(); 51 | } catch (IOException e) { 52 | Panic.panic(Error.BadXIDFileException); 53 | } 54 | if(fileLength < LEN_XID_HEADER_LENGTH){ 55 | Panic.panic(Error.BadXIDFileException); 56 | } 57 | ByteBuffer buf = ByteBuffer.allocate(LEN_XID_HEADER_LENGTH); 58 | try{ 59 | fc.position(0); 60 | fc.read(buf); 61 | } catch (IOException e) { 62 | Panic.panic(e); 63 | } 64 | this.xidCounter = Parser.parseLong(buf.array()); 65 | long end = getXidPosition(this.xidCounter + 1); 66 | if(end != fileLength){ 67 | Panic.panic(Error.BadXIDFileException); 68 | } 69 | } 70 | 71 | // 根据事务 xid 取得其在 xid 文件中对应的位置 72 | private long getXidPosition(long xid) { 73 | return LEN_XID_HEADER_LENGTH + (xid - 1) * XID_FIELD_SIZE; 74 | } 75 | 76 | 77 | // 将 xid 加一,并更新 XID Header 78 | private void IncreaseXIDCounter() { 79 | xidCounter ++; 80 | ByteBuffer buf = ByteBuffer.wrap(Parser.long2Byte(xidCounter)); 81 | try { 82 | fc.position(0); 83 | fc.write(buf); 84 | } catch (IOException e){ 85 | Panic.panic(e); 86 | } 87 | try { 88 | // 将数据刷到磁盘,但不包括元数据 89 | fc.force(false); 90 | } catch (IOException e){ 91 | Panic.panic(e); 92 | } 93 | } 94 | 95 | // 更新 xid 事务的状态为 status 96 | private void updateXID(long xid, byte status) { 97 | long offset = getXidPosition(xid); 98 | byte[] tmp = new byte[XID_FIELD_SIZE]; 99 | tmp[0] = status; 100 | ByteBuffer buf = ByteBuffer.wrap(tmp); 101 | try { 102 | fc.position(offset); 103 | fc.write(buf); 104 | } catch (IOException e) { 105 | Panic.panic(e); 106 | } 107 | try { 108 | fc.force(false); 109 | } catch (IOException e) { 110 | Panic.panic(e); 111 | } 112 | } 113 | 114 | // 开始一个事务,并返回 XID 115 | @Override 116 | public long begin() { 117 | counterLock.lock(); 118 | try{ 119 | long xid = xidCounter + 1; 120 | updateXID(xid, FIELD_TRAN_ACTIVE); 121 | IncreaseXIDCounter(); 122 | return xid; 123 | }finally { 124 | counterLock.unlock(); 125 | } 126 | } 127 | 128 | // 提交 XID 事务 129 | @Override 130 | public void commit(long xid) { 131 | updateXID(xid, FIELD_TRAN_COMMITTED); 132 | } 133 | 134 | // 回滚 XID 事务 135 | @Override 136 | public void abort(long xid) { 137 | updateXID(xid, FIELD_TRAN_ABORTED); 138 | } 139 | 140 | // 检查 XID 事务是否处于 status 状态 141 | private boolean checkXID(long xid, byte status){ 142 | long offset = getXidPosition(xid); 143 | ByteBuffer buf = ByteBuffer.wrap(new byte[XID_FIELD_SIZE]); 144 | try { 145 | fc.position(offset); 146 | fc.read(buf); 147 | } catch (IOException e) { 148 | Panic.panic(e); 149 | } 150 | return buf.array()[0] == status; 151 | } 152 | 153 | @Override 154 | public boolean isActive(long xid) { 155 | if(xid == SUPER_XID){ 156 | return false; 157 | } 158 | return checkXID(xid, FIELD_TRAN_ACTIVE); 159 | } 160 | 161 | @Override 162 | public boolean isCommitted(long xid) { 163 | if(xid == SUPER_XID){ 164 | return true; 165 | } 166 | return checkXID(xid, FIELD_TRAN_COMMITTED); 167 | } 168 | 169 | @Override 170 | public boolean isAborted(long xid) { 171 | if(xid == SUPER_XID){ 172 | return false; 173 | } 174 | return checkXID(xid, FIELD_TRAN_ABORTED); 175 | } 176 | 177 | @Override 178 | public void close() { 179 | try { 180 | fc.close(); 181 | file.close(); 182 | } catch (IOException e) { 183 | Panic.panic(e); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/utils/Panic.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.utils; 2 | 3 | public class Panic { 4 | public static void panic(Exception err) { 5 | err.printStackTrace(); 6 | System.exit(1); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/utils/ParseStringRes.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.utils; 2 | 3 | public class ParseStringRes { 4 | public String str; 5 | public int next; 6 | 7 | public ParseStringRes(String str, int next) { 8 | this.str = str; 9 | this.next = next; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/utils/Parser.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.utils; 2 | 3 | import com.google.common.primitives.Bytes; 4 | 5 | import java.nio.ByteBuffer; 6 | import java.util.Arrays; 7 | 8 | public class Parser { 9 | public static byte[] short2Byte(short value) { 10 | return ByteBuffer.allocate(Short.SIZE / Byte.SIZE).putShort(value).array(); 11 | } 12 | 13 | public static short parseShort(byte[] buf) { 14 | ByteBuffer buffer = ByteBuffer.wrap(buf, 0, 2); 15 | return buffer.getShort(); 16 | } 17 | 18 | public static byte[] int2Byte(int value) { 19 | return ByteBuffer.allocate(Integer.SIZE / Byte.SIZE).putInt(value).array(); 20 | } 21 | 22 | public static int parseInt(byte[] buf) { 23 | ByteBuffer buffer = ByteBuffer.wrap(buf, 0, 4); 24 | return buffer.getInt(); 25 | } 26 | 27 | public static long parseLong(byte[] buf) { 28 | ByteBuffer buffer = ByteBuffer.wrap(buf, 0, 8); 29 | return buffer.getLong(); 30 | } 31 | 32 | public static byte[] long2Byte(long value) { 33 | return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(value).array(); 34 | } 35 | 36 | public static ParseStringRes parseString(byte[] raw) { 37 | int length = parseInt(Arrays.copyOf(raw, 4)); 38 | String str = new String(Arrays.copyOfRange(raw, 4, 4+length)); 39 | return new ParseStringRes(str, length+4); 40 | } 41 | 42 | public static byte[] string2Byte(String str) { 43 | byte[] l = int2Byte(str.length()); 44 | return Bytes.concat(l, str.getBytes()); 45 | } 46 | 47 | public static long str2Uid(String key) { 48 | long seed = 13331; 49 | long res = 0; 50 | for(byte b : key.getBytes()) { 51 | res = res * seed + (long)b; 52 | } 53 | return res; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/utils/RandomUtil.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.utils; 2 | 3 | import java.security.SecureRandom; 4 | import java.util.Random; 5 | 6 | public class RandomUtil { 7 | public static byte[] randomBytes(int length) { 8 | Random r = new SecureRandom(); 9 | byte[] buf = new byte[length]; 10 | r.nextBytes(buf); 11 | return buf; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/utils/Types.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.utils; 2 | 3 | public class Types { 4 | public static long addressToUid(int pgno, short offset) { 5 | long u0 = (long)pgno; 6 | long u1 = (long)offset; 7 | return u0 << 32 | u1; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/vm/Entry.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.vm; 2 | 3 | import cn.edu.gzhu.backend.common.SubArray; 4 | import cn.edu.gzhu.backend.dm.dataItem.DataItem; 5 | import cn.edu.gzhu.backend.utils.Parser; 6 | import cn.edu.gzhu.backend.vm.impl.VersionManagerImpl; 7 | import com.google.common.primitives.Bytes; 8 | 9 | import java.util.Arrays; 10 | 11 | /** 12 | * VM 想上层抽象出 Entry 13 | * Entry 结构: [XMIN] [XMAX] [DATA] 14 | * XMIN 是创建该条记录(版本)的事务编号,而 XMAX 则是删除该条记录(版本)的事务编号。 15 | * DATA 就是这条记录持有的数据。 16 | */ 17 | public class Entry { 18 | private static final int OF_XMIN = 0; 19 | private static final int OF_XMAX = OF_XMIN + 8; 20 | private static final int OF_DATA = OF_XMAX + 8; 21 | 22 | private long uid; 23 | private DataItem dataItem; 24 | private VersionManager vm; 25 | 26 | public static Entry newEntry(VersionManager vm, DataItem dataItem, long uid) { 27 | Entry entry = new Entry(); 28 | entry.uid = uid; 29 | entry.dataItem = dataItem; 30 | entry.vm = vm; 31 | return entry; 32 | } 33 | 34 | public static Entry loadEntry(VersionManager vm, long uid) throws Exception { 35 | DataItem dataItem = ((VersionManagerImpl) vm).dm.read(uid); 36 | return newEntry(vm, dataItem, uid); 37 | } 38 | 39 | public static byte[] wrapEntryRaw (long xid, byte[] data){ 40 | byte[] xMin = Parser.long2Byte(xid); 41 | byte[] xMax = new byte[8]; 42 | return Bytes.concat(xMin, xMax, data); 43 | } 44 | 45 | public void release() { 46 | ((VersionManagerImpl) vm).releaseEntry(this); 47 | } 48 | 49 | public void remove() { 50 | dataItem.release(); 51 | } 52 | 53 | // 以拷贝的形式返回内容 54 | public byte[] data() { 55 | dataItem.rLock(); 56 | try { 57 | SubArray subArray = dataItem.data(); 58 | byte[] data = new byte[subArray.end - subArray.start - OF_DATA]; 59 | System.arraycopy(subArray.raw, subArray.start + OF_DATA, data, 0, data.length); 60 | return data; 61 | } finally { 62 | dataItem.rUnLock(); 63 | } 64 | } 65 | 66 | public long getXMin() { 67 | dataItem.rLock(); 68 | try { 69 | SubArray subArray = dataItem.data(); 70 | return Parser.parseLong(Arrays.copyOfRange(subArray.raw, subArray.start + OF_XMIN, subArray.start + OF_XMAX)); 71 | } finally { 72 | dataItem.rUnLock(); 73 | } 74 | } 75 | 76 | public long getXMax() { 77 | dataItem.rLock(); 78 | try { 79 | SubArray subArray = dataItem.data(); 80 | return Parser.parseLong(Arrays.copyOfRange(subArray.raw, subArray.start + OF_XMAX, subArray.start + OF_DATA)); 81 | } finally { 82 | dataItem.rUnLock(); 83 | } 84 | } 85 | 86 | public void setXMax(long xid) { 87 | dataItem.before(); 88 | try { 89 | SubArray subArray = dataItem.data(); 90 | System.arraycopy(Parser.long2Byte(xid), 0, subArray.raw, subArray.start + OF_XMAX, 8); 91 | } finally { 92 | dataItem.after(xid); 93 | } 94 | } 95 | 96 | public long getUid() { 97 | return uid; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/vm/LockTable.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.vm; 2 | 3 | import java.util.*; 4 | import java.util.concurrent.locks.Lock; 5 | import java.util.concurrent.locks.ReentrantLock; 6 | 7 | import cn.edu.gzhu.common.Error; 8 | 9 | /** 10 | * 检测这个图中是否存在环(构成死锁) 11 | * 维护了一个依赖等待图,以进行死锁检测 12 | */ 13 | public class LockTable { 14 | private Map> x2u; // 某个 XID 已经获得的资源的 UID 列表 15 | private Map u2x; // UID 被某个 XID 持有 16 | private Map> wait; // 正在等待 UID 的 XID 列表 17 | private Map waitLock; // 正在等待资源的 XID 的锁 18 | private Map waitU; // XID 正在等待的 UID 19 | private Lock lock; 20 | 21 | public LockTable() { 22 | x2u = new HashMap<>(); 23 | u2x = new HashMap<>(); 24 | wait = new HashMap<>(); 25 | waitLock = new HashMap<>(); 26 | waitU = new HashMap<>(); 27 | lock = new ReentrantLock(); 28 | } 29 | 30 | // 不需要等待则返回 null,否则返回锁对象 31 | // 会造成死锁则抛出异常 32 | // 在每次出现等待的情况时,就尝试向图中增加一条边,并进行死锁检测。如果检测到死锁,就撤销这条边,不允许添加,并撤销该事务。 33 | public Lock add(long xid, long uid) throws Exception { 34 | lock.lock(); 35 | try{ 36 | // uid 是否在 xid 已经获得的资源的 uid 列表里, 如果已经有该资源则不需要等待,直接返回null 37 | if(isInList(x2u, xid, uid)){ 38 | return null; 39 | } 40 | // 如果 uid 不被某个 xid 持有,则返回null 41 | if(!u2x.containsKey(uid)){ 42 | u2x.put(uid, xid); 43 | putIntoList(x2u, xid, uid); 44 | return null; 45 | } 46 | waitU.put(xid, uid); 47 | putIntoList(wait, xid, uid); 48 | if(hasDeadLock()){ 49 | waitU.remove(xid); 50 | removeFromList(wait, uid, xid); 51 | throw Error.DeadlockException; 52 | } 53 | Lock l = new ReentrantLock(); 54 | l.lock(); 55 | waitLock.put(xid, l); 56 | return l; 57 | } finally { 58 | lock.unlock(); 59 | } 60 | } 61 | 62 | public void remove(long xid) { 63 | lock.lock(); 64 | try { 65 | List list = x2u.get(xid); 66 | if(list != null){ 67 | while (list.size() > 0){ 68 | Long uid = list.remove(0); 69 | selectNewXID(uid); 70 | } 71 | } 72 | waitU.remove(xid); 73 | x2u.remove(xid); 74 | waitLock.remove(xid); 75 | } finally { 76 | lock.unlock(); 77 | } 78 | } 79 | 80 | // 从等待队列中选择一个 xid 来占用 uid 81 | private void selectNewXID(Long uid) { 82 | u2x.remove(uid); 83 | List list = wait.get(uid); 84 | if(list == null) return; 85 | assert list.size() > 0; 86 | while (list.size() > 0){ 87 | long xid = list.remove(0); 88 | if(!waitLock.containsKey(xid)){ 89 | continue; 90 | } else{ 91 | u2x.put(uid, xid); 92 | Lock l = waitLock.remove(xid); 93 | waitU.remove(xid); 94 | l.unlock(); 95 | break; 96 | } 97 | } 98 | if(list.size() == 0) wait.remove(uid); 99 | } 100 | 101 | private void removeFromList(Map> wait, long uid, long xid) { 102 | List list = wait.get(uid); 103 | if(list == null) return; 104 | Iterator iterator = list.iterator(); 105 | while(iterator.hasNext()) { 106 | long e = iterator.next(); 107 | if(e == xid) { 108 | iterator.remove(); 109 | break; 110 | } 111 | } 112 | if(list.size() == 0) { 113 | wait.remove(uid); 114 | } 115 | } 116 | 117 | private Map xidStamp; 118 | private int stamp; 119 | 120 | /** 121 | * 查找图中是否有环,通过深搜的方式,需要注意这个图不一定是连通图。 122 | * 思路:为每个节点设置一个访问戳,都初始化为 -1, 随后遍历所有节点,以每个非 -1 的节点作为根进行深搜, 123 | * 并将深搜该连通图中遇到的所有节点都设置为同一个数字,不同的连通图数字不同。 124 | * 这样,如果在遍历某个图时,遇到了之前遍历过的节点,说明出现了环。 125 | * @return 126 | */ 127 | private boolean hasDeadLock(){ 128 | xidStamp = new HashMap<>(); 129 | stamp = 1; 130 | for (Long xid : x2u.keySet()) { 131 | Integer s = xidStamp.get(xid); 132 | if(s != null && s > 0){ 133 | continue; 134 | } 135 | stamp ++; 136 | if(dfs(xid)) { 137 | return true; 138 | } 139 | } 140 | return false; 141 | } 142 | 143 | private boolean dfs(Long xid) { 144 | Integer stp = xidStamp.get(xid); 145 | if(stp != null && stp == stamp) { 146 | return true; 147 | } 148 | if(stp != null && stp < stamp) { 149 | return false; 150 | } 151 | xidStamp.put(xid, stamp); 152 | Long uid = waitU.get(xid); 153 | if(uid == null) return false; 154 | Long x = u2x.get(uid); 155 | assert x != null; 156 | return dfs(x); 157 | } 158 | 159 | private void putIntoList(Map> listMap, long xid, long uid) { 160 | if(!listMap.containsKey(xid)){ 161 | listMap.put(xid, new ArrayList<>()); 162 | } 163 | listMap.get(xid).add(0, uid); 164 | } 165 | 166 | private boolean isInList(Map> x2u, long xid, long uid){ 167 | // 某个 XID 已经获得的资源的 UID 列表 168 | List list = x2u.get(xid); 169 | if(list == null) return false; 170 | for (long e : list) { 171 | if (e == uid) { 172 | return true; 173 | } 174 | } 175 | return false; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/vm/Transaction.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.vm; 2 | 3 | import cn.edu.gzhu.backend.tm.impl.TransactionManagerImpl; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | // vm 对一个事务的抽象 9 | 10 | /** 11 | * 为了实现可重复读 12 | * primary: 事务只能读取它开始时,就已经结束的那些事务产生的数据版本 13 | * 这条规定,增加了事务需要忽略: 14 | * 1. 在本事务后开始的事务的数据 15 | * 2. 本事务开始时还是 active 状态的事务的数据 16 | * 对于第一条,只需要比较事务 ID,即可确定。 17 | * 而对于第二条,则需要在事务 Ti 开始时,记录下当前或缺的所有事务 SP(Ti), 如果记录的某个版本,XMIN 在 SP[Ti]中,也应当对 Ti 不可见。 18 | */ 19 | public class Transaction { 20 | public long xid; 21 | public int level; 22 | public Map snapshot; 23 | public Exception err; 24 | public boolean autoAborted; 25 | 26 | public static Transaction newTransaction(long xid, int level, Map active){ 27 | Transaction transaction = new Transaction(); 28 | transaction.xid = xid; 29 | transaction.level = level; 30 | if(level != 0){ 31 | transaction.snapshot = new HashMap<>(); 32 | for (Long x : active.keySet()) { 33 | transaction.snapshot.put(x, true); 34 | } 35 | } 36 | return transaction; 37 | } 38 | 39 | public boolean isInSnapshot(long xid){ 40 | if(xid == TransactionManagerImpl.SUPER_XID){ 41 | return false; 42 | } 43 | return snapshot.containsKey(xid); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/vm/VersionManager.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.vm; 2 | 3 | import cn.edu.gzhu.backend.dm.DataManager; 4 | import cn.edu.gzhu.backend.tm.TransactionManager; 5 | import cn.edu.gzhu.backend.vm.impl.VersionManagerImpl; 6 | 7 | public interface VersionManager { 8 | byte[] read(long xid, long uid) throws Exception; 9 | long insert(long xid, byte[] data) throws Exception; 10 | boolean delete(long xid, long uid) throws Exception; 11 | 12 | long begin(int level); 13 | void commit(long xid) throws Exception; 14 | void abort(long xid); 15 | 16 | public static VersionManager newVersionManager(TransactionManager tm, DataManager dm){ 17 | return new VersionManagerImpl(tm, dm); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/vm/Visibility.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.vm; 2 | 3 | import cn.edu.gzhu.backend.tm.TransactionManager; 4 | 5 | public class Visibility { 6 | /** 7 | * 取出要修改的数据 X 的最新提交版本,并检查该最新版本的创建者对当前事务是否可见 8 | * @param tm 9 | * @param t 10 | * @param entry 11 | * @return 12 | */ 13 | public static boolean isVersionSkip(TransactionManager tm, Transaction t, Entry entry){ 14 | long xMax = entry.getXMax(); 15 | if(t.level == 0) { 16 | return false; 17 | } else { 18 | return tm.isCommitted(xMax) && (xMax > t.xid || t.isInSnapshot(xMax)); 19 | } 20 | } 21 | 22 | public static boolean isVisible(TransactionManager tm, Transaction t, Entry e){ 23 | if(t.level == 0){ 24 | return readCommitted(tm, t, e); 25 | } else { 26 | return repeatableRead(tm, t, e); 27 | } 28 | } 29 | 30 | /** 31 | * 为了读提交的隔离级别设计 32 | * 若条件为 true,则版本对 Ti 可见。那么获取 Ti 适合的版本,只需要从最新版本开始,依次向前检查可见性,如果为 true,就可以直接返回。 33 | * @param tm 34 | * @param t 35 | * @param entry 36 | * @return 37 | */ 38 | private static boolean readCommitted(TransactionManager tm, Transaction t, Entry entry){ 39 | long xid = t.xid; 40 | long xMin = entry.getXMin(); 41 | long xMax = entry.getXMax(); 42 | if(xMin == xid && xMax == 0) { // 由 Ti 创建且还未被删除 43 | return true; 44 | } 45 | if(tm.isCommitted(xMin)){ // 由一个已提交的事务创建且 46 | if(xMax == 0) return true; // 尚未删除或 47 | if(xMax != xid) { // 由一个未提交的事务删除 48 | if(!tm.isCommitted(xMax)) { 49 | return true; 50 | } 51 | } 52 | } 53 | return false; 54 | } 55 | 56 | /** 57 | * 可重复读 58 | * @param tm 59 | * @param t 60 | * @param entry 61 | * @return 62 | */ 63 | private static boolean repeatableRead(TransactionManager tm, Transaction t, Entry entry) { 64 | long xid = t.xid; 65 | long xMin = entry.getXMin(); 66 | long xMax = entry.getXMax(); 67 | if(xMin == xid && xMax == 0) return true; // 由 Ti 创建且尚未被删除 68 | if(tm.isCommitted(xMin) && xMin < xid && !t.isInSnapshot(xMin)){ // 由一个已提交的事务创建且这个事务小于 Ti 且这个事务在 Ti 开始前提交 69 | if(xMax == 0) return true; // 且尚未被删除或 70 | if(xMax != xid){ // 由其他事务删除但是 71 | if(!tm.isCommitted(xMax) || xMax > xid || t.isInSnapshot(xMax)){ // 这个事务尚未提交或这个事务在 Ti 开始之后才开始或这个事务在 Ti 开始前还未提交 72 | return true; 73 | } 74 | } 75 | } 76 | return false; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/backend/vm/impl/VersionManagerImpl.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.backend.vm.impl; 2 | 3 | import cn.edu.gzhu.backend.common.AbstractCache; 4 | import cn.edu.gzhu.backend.dm.DataManager; 5 | import cn.edu.gzhu.backend.tm.TransactionManager; 6 | import cn.edu.gzhu.backend.tm.impl.TransactionManagerImpl; 7 | import cn.edu.gzhu.backend.utils.Panic; 8 | import cn.edu.gzhu.backend.vm.*; 9 | import cn.edu.gzhu.common.Error; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.concurrent.locks.Lock; 14 | import java.util.concurrent.locks.ReentrantLock; 15 | 16 | public class VersionManagerImpl extends AbstractCache implements VersionManager { 17 | public TransactionManager tm; 18 | public DataManager dm; 19 | private Map activeTransaction; 20 | private Lock lock; 21 | private LockTable lockTable; 22 | 23 | public VersionManagerImpl(TransactionManager tm, DataManager dm) { 24 | super(0); 25 | this.tm = tm; 26 | this.dm = dm; 27 | this.activeTransaction = new HashMap<>(); 28 | activeTransaction.put(TransactionManagerImpl.SUPER_XID, Transaction.newTransaction(TransactionManagerImpl.SUPER_XID, 0, null)); 29 | this.lock = new ReentrantLock(); 30 | this.lockTable = new LockTable(); 31 | } 32 | 33 | /** 34 | * 读取一个 entry,注意判断下可见性即可 35 | * @param xid 36 | * @param uid 37 | * @return 38 | * @throws Exception 39 | */ 40 | @Override 41 | public byte[] read(long xid, long uid) throws Exception { 42 | lock.lock(); 43 | Transaction transaction = activeTransaction.get(xid); 44 | lock.unlock(); 45 | if(transaction.err != null){ 46 | throw transaction.err; 47 | } 48 | Entry entry = null; 49 | try { 50 | entry = super.get(uid); 51 | } catch (Exception e){ 52 | if(e == Error.NullEntryException){ 53 | return null; 54 | } else { 55 | throw e; 56 | } 57 | } 58 | try { 59 | if(Visibility.isVisible(tm, transaction, entry)){ 60 | return entry.data(); 61 | } else { 62 | return null; 63 | } 64 | } finally { 65 | entry.release(); 66 | } 67 | } 68 | 69 | /** 70 | * 将数据包裹成 Entry,交给 DM 插入即可 71 | * @param xid 72 | * @param data 73 | * @return 74 | * @throws Exception 75 | */ 76 | @Override 77 | public long insert(long xid, byte[] data) throws Exception { 78 | lock.lock(); 79 | Transaction transaction = activeTransaction.get(xid); 80 | lock.unlock(); 81 | if(transaction.err != null){ 82 | throw transaction.err; 83 | } 84 | byte[] raw = Entry.wrapEntryRaw(xid, data); 85 | return dm.insert(xid, raw); 86 | } 87 | 88 | /** 89 | * 1. 可见性判断。 2. 获取资源的锁。 3. 版本跳跃判断。 删除的操作只有一个设置 XMAX。 90 | * @param xid 91 | * @param uid 92 | * @return 93 | * @throws Exception 94 | */ 95 | @Override 96 | public boolean delete(long xid, long uid) throws Exception { 97 | lock.lock(); 98 | Transaction transaction = activeTransaction.get(xid); 99 | lock.unlock(); 100 | if(transaction.err != null){ 101 | throw transaction.err; 102 | } 103 | Entry entry = null; 104 | try { 105 | entry = super.get(uid); 106 | } catch (Exception e){ 107 | if(e == Error.NullEntryException){ 108 | return false; 109 | } else { 110 | throw e; 111 | } 112 | } 113 | try{ 114 | if(!Visibility.isVersionSkip(tm, transaction, entry)){ 115 | return false; 116 | } 117 | Lock l = null; 118 | try{ 119 | l = lockTable.add(xid, uid); 120 | } catch (Exception e){ 121 | transaction.err = Error.ConcurrentUpdateException; 122 | internAbort(xid, true); 123 | transaction.autoAborted = true; 124 | throw transaction.err; 125 | } 126 | if(l != null){ 127 | l.lock(); 128 | l.unlock(); 129 | } 130 | if(entry.getXMax() == xid){ 131 | return false; 132 | } 133 | if(Visibility.isVersionSkip(tm, transaction, entry)){ 134 | transaction.err = Error.ConcurrentUpdateException; 135 | internAbort(xid, true); 136 | transaction.autoAborted = true; 137 | throw transaction.err; 138 | } 139 | entry.setXMax(xid); 140 | return true; 141 | } finally { 142 | entry.release(); 143 | } 144 | } 145 | 146 | /** 147 | * 开启一个事务,并初始化事务的结构,并将其存放在 activeTransaction 中,用于检查和快照使用: 148 | * @param level 149 | * @return 150 | */ 151 | @Override 152 | public long begin(int level) { 153 | lock.lock(); 154 | try { 155 | long xid = tm.begin(); 156 | Transaction transaction = Transaction.newTransaction(xid, level, activeTransaction); 157 | activeTransaction.put(xid, transaction); 158 | return xid; 159 | } finally { 160 | lock.unlock(); 161 | } 162 | } 163 | 164 | /** 165 | * 提交一个事务,主要就是 free 掉相关的结构,并且释放持有的锁,并修改 TM 状态 166 | * @param xid 167 | * @throws Exception 168 | */ 169 | @Override 170 | public void commit(long xid) throws Exception { 171 | lock.lock(); 172 | Transaction transaction = activeTransaction.get(xid); 173 | lock.unlock(); 174 | try{ 175 | if(transaction.err != null) { 176 | throw transaction.err; 177 | } 178 | } catch (NullPointerException e){ 179 | System.out.println(xid); 180 | System.out.println(activeTransaction.keySet()); 181 | Panic.panic(e); 182 | } 183 | lock.lock(); 184 | activeTransaction.remove(xid); 185 | lock.unlock(); 186 | lockTable.remove(xid); 187 | tm.commit(xid); 188 | } 189 | 190 | /** 191 | * abort 事务的方法有两种:手动和自动。 192 | * 手动指的是调用 abort() 方法,而自动则是在事务被检测出死锁时,会自动撤销回滚事务;或者出现版本跳跃时,也会自动回滚。 193 | * @param xid 194 | */ 195 | @Override 196 | public void abort(long xid) { 197 | internAbort(xid, false); 198 | } 199 | 200 | private void internAbort(long xid, boolean autoAborted) { 201 | lock.lock(); 202 | Transaction transaction = activeTransaction.get(xid); 203 | if(!autoAborted){ 204 | activeTransaction.remove(xid); 205 | } 206 | lock.unlock(); 207 | if(transaction.autoAborted) return; 208 | lockTable.remove(xid); 209 | tm.abort(xid); 210 | } 211 | 212 | @Override 213 | protected Entry getForCache(long uid) throws Exception { 214 | Entry entry = Entry.loadEntry(this, uid); 215 | if(entry == null){ 216 | throw Error.NullEntryException; 217 | } 218 | return entry; 219 | } 220 | 221 | @Override 222 | protected void releaseForCache(Entry entry) { 223 | entry.remove(); 224 | } 225 | 226 | public void releaseEntry(Entry entry) { 227 | super.release(entry.getUid()); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/client/Client.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.client; 2 | 3 | import cn.edu.gzhu.transport.Package; 4 | import cn.edu.gzhu.transport.Packager; 5 | 6 | public class Client { 7 | private RoundTripper rt; 8 | 9 | public Client(Packager packager) { 10 | this.rt = new RoundTripper(packager); 11 | } 12 | 13 | public byte[] execute(byte[] stat) throws Exception { 14 | Package pkg = new Package(stat, null); 15 | Package resPkg = rt.roundTrip(pkg); 16 | if(resPkg.getErr() != null) { 17 | throw resPkg.getErr(); 18 | } 19 | return resPkg.getData(); 20 | } 21 | 22 | public void close() { 23 | try { 24 | rt.close(); 25 | } catch (Exception e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/client/Launcher.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.client; 2 | 3 | import cn.edu.gzhu.transport.Encoder; 4 | import cn.edu.gzhu.transport.Packager; 5 | import cn.edu.gzhu.transport.Transporter; 6 | 7 | import java.io.IOException; 8 | import java.net.Socket; 9 | 10 | public class Launcher { 11 | public static void main(String[] args) throws IOException { 12 | Socket socket = new Socket("127.0.0.1", 9999); 13 | Encoder e = new Encoder(); 14 | Transporter t = new Transporter(socket); 15 | Packager packager = new Packager(t, e); 16 | 17 | Client client = new Client(packager); 18 | Shell shell = new Shell(client); 19 | shell.run(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/client/RoundTripper.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.client; 2 | 3 | import cn.edu.gzhu.transport.Package; 4 | import cn.edu.gzhu.transport.Packager; 5 | 6 | public class RoundTripper { 7 | private Packager packager; 8 | 9 | public RoundTripper(Packager packager) { 10 | this.packager = packager; 11 | } 12 | 13 | public Package roundTrip(Package pkg) throws Exception { 14 | packager.send(pkg); 15 | return packager.receive(); 16 | } 17 | 18 | public void close() throws Exception { 19 | packager.close(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/client/Shell.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.client; 2 | 3 | import java.util.Scanner; 4 | 5 | public class Shell { 6 | private Client client; 7 | 8 | public Shell(Client client) { 9 | this.client = client; 10 | } 11 | 12 | public void run() { 13 | Scanner sc = new Scanner(System.in); 14 | try { 15 | while(true) { 16 | System.out.print(":> "); 17 | String statStr = sc.nextLine(); 18 | if("exit".equals(statStr) || "quit".equals(statStr)) { 19 | break; 20 | } 21 | try { 22 | byte[] res = client.execute(statStr.getBytes()); 23 | System.out.println(new String(res)); 24 | } catch(Exception e) { 25 | System.out.println(e.getMessage()); 26 | } 27 | 28 | } 29 | } finally { 30 | sc.close(); 31 | client.close(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/common/Error.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.common; 2 | 3 | public class Error { 4 | // common 5 | public static final Exception CacheFullException = new RuntimeException("Cache is full!"); 6 | public static final Exception FileExistsException = new RuntimeException("File already exists!"); 7 | public static final Exception FileNotExistsException = new RuntimeException("File does not exists!"); 8 | public static final Exception FileCannotRWException = new RuntimeException("File cannot read or write!"); 9 | 10 | // dm 11 | public static final Exception BadLogFileException = new RuntimeException("Bad log file!"); 12 | public static final Exception MemTooSmallException = new RuntimeException("Memory too small!"); 13 | public static final Exception DataTooLargeException = new RuntimeException("Data too large!"); 14 | public static final Exception DatabaseBusyException = new RuntimeException("Database is busy!"); 15 | 16 | // tm 17 | public static final Exception BadXIDFileException = new RuntimeException("Bad XID file!"); 18 | 19 | // vm 20 | public static final Exception DeadlockException = new RuntimeException("Deadlock!"); 21 | public static final Exception ConcurrentUpdateException = new RuntimeException("Concurrent update issue!"); 22 | public static final Exception NullEntryException = new RuntimeException("Null entry!"); 23 | 24 | // tbm 25 | public static final Exception InvalidFieldException = new RuntimeException("Invalid field type!"); 26 | public static final Exception FieldNotFoundException = new RuntimeException("Field not found!"); 27 | public static final Exception FieldNotIndexedException = new RuntimeException("Field not indexed!"); 28 | public static final Exception InvalidLogOpException = new RuntimeException("Invalid logic operation!"); 29 | public static final Exception InvalidValuesException = new RuntimeException("Invalid values!"); 30 | public static final Exception DuplicatedTableException = new RuntimeException("Duplicated table!"); 31 | public static final Exception TableNotFoundException = new RuntimeException("Table not found!"); 32 | 33 | // parser 34 | public static final Exception InvalidCommandException = new RuntimeException("Invalid command!"); 35 | public static final Exception TableNoIndexException = new RuntimeException("Table has no index!"); 36 | 37 | // transport 38 | public static final Exception InvalidPkgDataException = new RuntimeException("Invalid package data!"); 39 | 40 | // server 41 | public static final Exception NestedTransactionException = new RuntimeException("Nested transaction not supported!"); 42 | public static final Exception NoTransactionException = new RuntimeException("Not in transaction!"); 43 | 44 | // launcher 45 | public static final Exception InvalidMemException = new RuntimeException("Invalid memory!"); 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/server/Executor.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.server; 2 | 3 | import cn.edu.gzhu.backend.parser.Parser; 4 | import cn.edu.gzhu.backend.parser.statement.*; 5 | import cn.edu.gzhu.backend.tbm.BeginRes; 6 | import cn.edu.gzhu.backend.tbm.TableManager; 7 | 8 | import cn.edu.gzhu.common.Error; 9 | 10 | public class Executor { 11 | private long xid; 12 | private TableManager tableManager; 13 | 14 | public Executor(TableManager tableManager) { 15 | this.tableManager = tableManager; 16 | this.xid = 0; 17 | } 18 | 19 | public void close() { 20 | if(xid != 0) { 21 | System.out.println("Abnormal Abort: " + xid); 22 | tableManager.abort(xid); 23 | } 24 | } 25 | 26 | public byte[] execute(byte[] sql) throws Exception { 27 | System.out.println("Execute: " + new String(sql)); 28 | Object stat = Parser.parse(sql); 29 | if(Begin.class.isInstance(stat)) { 30 | if(xid != 0) { 31 | throw Error.NestedTransactionException; 32 | } 33 | BeginRes res = tableManager.begin((Begin) stat); 34 | xid = res.xid; 35 | return res.result; 36 | } else if(Commit.class.isInstance(stat)) { 37 | if(xid == 0){ 38 | throw Error.NoTransactionException; 39 | } 40 | byte[] res = tableManager.commit(xid); 41 | xid = 0; 42 | return res; 43 | } else if (Abort.class.isInstance(stat)) { 44 | if(xid == 0) { 45 | throw Error.NoTransactionException; 46 | } 47 | byte[] res = tableManager.abort(xid); 48 | xid = 0; 49 | return res; 50 | } else { 51 | return execute2(stat); 52 | } 53 | } 54 | 55 | private byte[] execute2(Object stat) throws Exception { 56 | boolean tmpTransaction = false; 57 | Exception err = null; 58 | if(xid == 0) { 59 | tmpTransaction = true; 60 | BeginRes res = tableManager.begin(new Begin()); 61 | xid = res.xid; 62 | } 63 | try { 64 | byte[] res = null; 65 | if(Show.class.isInstance(stat)) { 66 | res = tableManager.show(xid); 67 | } else if(Create.class.isInstance(stat)) { 68 | res = tableManager.create(xid, (Create)stat); 69 | } else if(Select.class.isInstance(stat)) { 70 | res = tableManager.read(xid, (Select)stat); 71 | } else if(Insert.class.isInstance(stat)) { 72 | res = tableManager.insert(xid, (Insert)stat); 73 | } else if(Delete.class.isInstance(stat)) { 74 | res = tableManager.delete(xid, (Delete)stat); 75 | } else if(Update.class.isInstance(stat)) { 76 | res = tableManager.update(xid, (Update)stat); 77 | } 78 | return res; 79 | } catch (Exception e){ 80 | err = e; 81 | throw err; 82 | } finally { 83 | if(tmpTransaction) { 84 | if(err != null) { 85 | tableManager.abort(xid); 86 | } else { 87 | tableManager.commit(xid); 88 | } 89 | xid = 0; 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/server/Launcher.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.server; 2 | 3 | import cn.edu.gzhu.backend.dm.DataManager; 4 | import cn.edu.gzhu.backend.tbm.TableManager; 5 | import cn.edu.gzhu.backend.tm.TransactionManager; 6 | import cn.edu.gzhu.backend.utils.Panic; 7 | 8 | import cn.edu.gzhu.backend.vm.VersionManager; 9 | import cn.edu.gzhu.backend.vm.impl.VersionManagerImpl; 10 | import cn.edu.gzhu.common.Error; 11 | 12 | import org.apache.commons.cli.*; 13 | 14 | public class Launcher { 15 | public static final int port = 9999; 16 | 17 | public static final long DEFAULT_MEM = (1 << 20) * 64; 18 | public static final long KB = 1 << 10; 19 | public static final long MB = 1 << 20; 20 | public static final long GB = 1 << 30; 21 | 22 | public static void main(String[] args) throws ParseException { 23 | Options options = new Options(); 24 | options.addOption("open", true, "-open DBPath"); 25 | options.addOption("create", true, "-create DBPath"); 26 | options.addOption("mem", true, "-mem 64MB"); 27 | CommandLineParser parser = new DefaultParser(); 28 | CommandLine cmd = parser.parse(options, args); 29 | 30 | if(cmd.hasOption("open")) { 31 | openDB(cmd.getOptionValue("open"), parseMem(cmd.getOptionValue("mem"))); 32 | return; 33 | } 34 | if(cmd.hasOption("create")) { 35 | createDB(cmd.getOptionValue("create")); 36 | return; 37 | } 38 | System.out.println("Usage: launcher (open|create) DBPath"); 39 | } 40 | 41 | private static void createDB(String path) { 42 | TransactionManager tm = TransactionManager.create(path); 43 | DataManager dm = DataManager.create(path, DEFAULT_MEM, tm); 44 | VersionManager vm = new VersionManagerImpl(tm, dm); 45 | TableManager.create(path, vm, dm); 46 | tm.close(); 47 | dm.close(); 48 | } 49 | 50 | private static void openDB(String path, long mem) { 51 | TransactionManager tm = TransactionManager.open(path); 52 | DataManager dm = DataManager.open(path, mem, tm); 53 | VersionManager vm = new VersionManagerImpl(tm, dm); 54 | TableManager tableManager = TableManager.open(path, vm, dm); 55 | new Server(port, tableManager).start(); 56 | } 57 | 58 | private static long parseMem(String memStr) { 59 | if(memStr == null || "".equals(memStr)) { 60 | return DEFAULT_MEM; 61 | } 62 | if(memStr.length() < 2) { 63 | Panic.panic(Error.InvalidMemException); 64 | } 65 | String unit = memStr.substring(memStr.length() - 2); 66 | long memNum = Long.parseLong(memStr.substring(0, memStr.length() - 2)); 67 | switch (unit) { 68 | case "KB": 69 | return memNum*KB; 70 | case "MB": 71 | return memNum*MB; 72 | case "GB": 73 | return memNum*GB; 74 | default: 75 | Panic.panic(Error.InvalidMemException); 76 | } 77 | return DEFAULT_MEM; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/server/Server.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.server; 2 | 3 | import cn.edu.gzhu.backend.tbm.TableManager; 4 | import cn.edu.gzhu.transport.Encoder; 5 | import cn.edu.gzhu.transport.Package; 6 | import cn.edu.gzhu.transport.Packager; 7 | import cn.edu.gzhu.transport.Transporter; 8 | 9 | import java.io.IOException; 10 | import java.net.InetSocketAddress; 11 | import java.net.ServerSocket; 12 | import java.net.Socket; 13 | import java.util.concurrent.ArrayBlockingQueue; 14 | import java.util.concurrent.ThreadPoolExecutor; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | public class Server { 18 | private int port; 19 | TableManager tableManager; 20 | 21 | public Server(int port, TableManager tableManager) { 22 | this.port = port; 23 | this.tableManager = tableManager; 24 | } 25 | 26 | public void start() { 27 | ServerSocket ss = null; 28 | try { 29 | ss = new ServerSocket(port); 30 | } catch (IOException e) { 31 | e.printStackTrace(); 32 | return; 33 | } 34 | System.out.println("Server listen to port: " + port); 35 | ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 20, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.CallerRunsPolicy()); 36 | try { 37 | while (true) { 38 | Socket socket = ss.accept(); 39 | Runnable worker = new HandleSocket(socket, tableManager); 40 | poolExecutor.execute(worker); 41 | } 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } finally { 45 | try{ 46 | ss.close(); 47 | } catch (IOException e){ 48 | e.printStackTrace(); 49 | } 50 | } 51 | } 52 | } 53 | 54 | class HandleSocket implements Runnable { 55 | private Socket socket; 56 | private TableManager tableManager; 57 | 58 | public HandleSocket(Socket socket, TableManager tableManager) { 59 | this.socket = socket; 60 | this.tableManager = tableManager; 61 | } 62 | 63 | @Override 64 | public void run() { 65 | InetSocketAddress address = (InetSocketAddress) socket.getRemoteSocketAddress(); 66 | System.out.println("Establish connection: " + address.getAddress().getHostAddress()+":"+address.getPort()); 67 | Packager packager = null; 68 | try { 69 | Transporter transporter = new Transporter(socket); 70 | Encoder encoder = new Encoder(); 71 | packager = new Packager(transporter, encoder); 72 | } catch (IOException e) { 73 | e.printStackTrace(); 74 | try { 75 | socket.close(); 76 | } catch (IOException ioException) { 77 | ioException.printStackTrace(); 78 | } 79 | return; 80 | } 81 | Executor executor = new Executor(tableManager); 82 | while (true) { 83 | Package pkg = null; 84 | try { 85 | pkg = packager.receive(); 86 | } catch (Exception e) { 87 | break; 88 | } 89 | byte[] sql = pkg.getData(); 90 | byte[] result = null; 91 | Exception err = null; 92 | try { 93 | result = executor.execute(sql); 94 | } catch (Exception e) { 95 | err = e; 96 | err.printStackTrace(); 97 | } 98 | pkg = new Package(result, err); 99 | try { 100 | packager.send(pkg); 101 | } catch (Exception e) { 102 | e.printStackTrace(); 103 | break; 104 | } 105 | } 106 | executor.close(); 107 | try { 108 | packager.close(); 109 | } catch (Exception e) { 110 | e.printStackTrace(); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/transport/Encoder.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.transport; 2 | 3 | import com.google.common.primitives.Bytes; 4 | 5 | import cn.edu.gzhu.common.Error; 6 | 7 | import java.util.Arrays; 8 | 9 | /** 10 | * 编码规则如下:[Flag][data] 11 | * 若 flag 为 0,表示发送的是数据,那么 data 即为这份数据本身;如果 flag 为 1,表示发送的是错误,data 是 Exception.getMessage() 的错误提示信息。 12 | */ 13 | public class Encoder { 14 | public byte[] encode(Package pkg) { 15 | if (pkg.getErr() != null) { 16 | Exception err = pkg.getErr(); 17 | String msg = "Intern server error!"; 18 | if (err.getMessage() != null) { 19 | msg = err.getMessage(); 20 | } 21 | return Bytes.concat(new byte[]{1}, msg.getBytes()); 22 | } else { 23 | return Bytes.concat(new byte[]{0}, pkg.getData()); 24 | } 25 | } 26 | 27 | public Package decode(byte[] data) throws Exception { 28 | if(data.length < 1) { 29 | throw Error.InvalidPkgDataException; 30 | } 31 | if(data[0] == 0) { 32 | return new Package(Arrays.copyOfRange(data, 1, data.length), null); 33 | } else if(data[0] == 1){ 34 | return new Package(null, new RuntimeException(new String(Arrays.copyOfRange(data, 1, data.length)))); 35 | } else { 36 | throw Error.InvalidPkgDataException; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/transport/Package.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.transport; 2 | 3 | public class Package { 4 | byte[] data; 5 | Exception err; 6 | 7 | public Package(byte[] data, Exception err) { 8 | this.data = data; 9 | this.err = err; 10 | } 11 | 12 | public byte[] getData() { 13 | return data; 14 | } 15 | 16 | public Exception getErr() { 17 | return err; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/transport/Packager.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.transport; 2 | 3 | public class Packager { 4 | private Transporter transporter; 5 | private Encoder encoder; 6 | 7 | public Packager(Transporter transporter, Encoder encoder) { 8 | this.transporter = transporter; 9 | this.encoder = encoder; 10 | } 11 | 12 | public void send(Package pkg) throws Exception { 13 | byte[] data = encoder.encode(pkg); 14 | transporter.send(data); 15 | } 16 | 17 | public Package receive() throws Exception { 18 | byte[] data = transporter.receive(); 19 | return encoder.decode(data); 20 | } 21 | 22 | public void close() throws Exception { 23 | transporter.close(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cn/edu/gzhu/transport/Transporter.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu.transport; 2 | 3 | import org.apache.commons.codec.DecoderException; 4 | import org.apache.commons.codec.binary.Hex; 5 | 6 | import java.io.*; 7 | import java.net.Socket; 8 | 9 | public class Transporter { 10 | private Socket socket; 11 | private BufferedReader reader; 12 | private BufferedWriter writer; 13 | 14 | public Transporter(Socket socket) throws IOException { 15 | this.socket = socket; 16 | this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 17 | this.writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); 18 | } 19 | 20 | public void send(byte[] data) throws Exception { 21 | String raw = hexEncode(data); 22 | writer.write(raw); 23 | writer.flush(); 24 | } 25 | 26 | public byte[] receive() throws Exception { 27 | String line = reader.readLine(); 28 | if(line == null){ 29 | clone(); 30 | } 31 | return hexDecode(line); 32 | } 33 | 34 | public void close() throws IOException { 35 | writer.close(); 36 | reader.close(); 37 | socket.close(); 38 | } 39 | 40 | private String hexEncode(byte[] buf) { 41 | return Hex.encodeHexString(buf, true) + "\n"; 42 | } 43 | 44 | 45 | private byte[] hexDecode(String buf) throws DecoderException { 46 | return Hex.decodeHex(buf); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/cn/edu/gzhu/MainTest.java: -------------------------------------------------------------------------------- 1 | package cn.edu.gzhu; 2 | 3 | 4 | import junit.framework.Test; 5 | import junit.framework.TestCase; 6 | import junit.framework.TestSuite; 7 | 8 | /** 9 | * Unit test for simple App. 10 | */ 11 | public class MainTest 12 | extends TestCase 13 | { 14 | /** 15 | * Create the test case 16 | * 17 | * @param testName name of the test case 18 | */ 19 | public MainTest( String testName ) 20 | { 21 | super( testName ); 22 | } 23 | 24 | /** 25 | * @return the suite of tests being tested 26 | */ 27 | public static Test suite() 28 | { 29 | return new TestSuite( cn.edu.gzhu.MainTest.class ); 30 | } 31 | 32 | /** 33 | * Rigourous Test :-) 34 | */ 35 | public void testApp() 36 | { 37 | assertTrue( true ); 38 | } 39 | } 40 | 41 | --------------------------------------------------------------------------------