-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathformat.ts
More file actions
150 lines (135 loc) · 3.86 KB
/
format.ts
File metadata and controls
150 lines (135 loc) · 3.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*
* This implementation follows the bitcask paper: https://riak.com/assets/bitcask-intro.pdf
* and https://github.com/avinassh/go-caskdb
*
* The header consists of the following:
* timestamp - 4 bytes
* keySize - 4 bytes
* valueSize - 4 bytes
*
* After the header, is the record:
* key - serialized string
* value - serialized string
*
* the keySize and valueSize in the header will provide the offset information
* necessary to know where a key and value starts or ends
*
* | HEADER |
* | timestamp (4)| keySize (4)| valueSize (4)| key | value |
*
* The KeyDir is an in memory hashmap that maps the key to the file offset that the
* value is at on disk
* The format of a KeyDir entry is:
*
* timestamp - 4 bytes
* valueSize - 4 bytes
* valueOffset - 4 bytes
*
* */
export const HEADER_SIZE: number = 12;
export const KEY_DIR_ENTRY_SIZE: number = 12;
export interface KeyDirEntry {
valueSize: number;
valueOffset: number;
timestamp: number;
}
export interface Header {
timestamp: number;
keySize: number;
valueSize: number;
}
// maybe not necessary to store the key if there will be no restore
export interface KV {
key: string;
value: string;
}
export interface TmpdbRecord {
header: Header;
kv: KV;
}
export const encodeKeyDirEntry = (
timestamp: number,
valueSize: number,
// this is the offset relative to the start of the database file
valueOffset: number,
): Buffer => {
const buf = Buffer.alloc(KEY_DIR_ENTRY_SIZE);
buf.writeUInt32LE(timestamp, 0);
buf.writeUInt32LE(valueSize, 4);
buf.writeUInt32LE(valueOffset, 8);
return buf;
};
export const decodeKeyDirEntry = (entry: Buffer): KeyDirEntry => {
const timestamp = entry.readUInt32LE(0);
const valueSize = entry.readUInt32LE(4);
const valueOffset = entry.readUInt32LE(8);
return {
timestamp: timestamp,
valueSize: valueSize,
valueOffset: valueOffset,
};
};
export const encodeHeader = (
timestamp: number,
keySize: number,
valueSize: number,
): Buffer => {
const buf = Buffer.alloc(HEADER_SIZE);
// each slot in the buffer array is 1 byte
// each item of the header is 4 bytes
// therefore, each write of a UInt32 (which is 4 bytes)
// needs to be done at previousOffset + 4
buf.writeUint32LE(timestamp, 0);
buf.writeUint32LE(keySize, 4);
buf.writeUint32LE(valueSize, 8);
return buf;
};
export const decodeHeader = (header: Buffer): Header => {
const timestamp = header.readUInt32LE(0);
const keySize = header.readUInt32LE(4);
const valueSize = header.readUInt32LE(8);
return {
timestamp: timestamp,
keySize: keySize,
valueSize: valueSize,
};
};
export const encodeRecord = (
timestamp: number,
key: string,
value: string,
): {
record: Buffer;
// the valueOffset is needed to put into the KeyDir entry
// this is relative to this individual entry, not the start of the database file
// it is the headersize + keysize
// this is where the value starts
valueOffset: number;
// valueSize is needed in the keyDir entry to know how much to read after the
// valueOffset
valueSize: number;
} => {
const keyBuffer = Buffer.from(key);
const valueBuffer = Buffer.from(value);
const header = encodeHeader(timestamp, keyBuffer.length, valueBuffer.length);
const kv = Buffer.concat([Buffer.from(key), Buffer.from(value)]);
const record = Buffer.concat([header, kv]);
return {
record: record,
valueOffset: HEADER_SIZE + keyBuffer.length,
valueSize: valueBuffer.length,
};
};
export const decodeRecord = (buf: Buffer): TmpdbRecord => {
const header = decodeHeader(buf);
const key = buf
.subarray(HEADER_SIZE, HEADER_SIZE + header.keySize)
.toString();
const value = buf
.subarray(
HEADER_SIZE + header.keySize,
HEADER_SIZE + header.keySize + header.valueSize,
)
.toString();
return { header: header, kv: { key: key, value: value } };
};