-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdb.c
289 lines (263 loc) · 9.99 KB
/
db.c
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include <errno.h>
#include <stdint.h>
#define sizeOfColumn(Struct,Column) sizeof(((Struct*)0)->Column)
const uint32_t NAME_MAX_SIZE = 32;
const uint32_t PASSWD_MAX_SIZE = 255;
typedef enum { SELECT, INSERT }SQLType; //CURD first :) //UPDATE, DELETE
typedef enum {
EXECUTE_SUCCESS,
EXECUTE_FAILURE,
EXECUTE_PARAMETER_ERROR,
EXECUTE_TABEL_FULL_ERROR,
EXECUTE_UNSUPPORT_CMD,
EXECUTE_TABLE_EMPTY
}ExecuteResult;
typedef struct {
uint32_t id;
char usrname[NAME_MAX_SIZE];
char passwd[PASSWD_MAX_SIZE];
}Row;
typedef struct {
SQLType type;
Row row;
}Statement;
// define Input wrapper/stream
typedef struct {
char *buffer;
size_t bufferLength;
ssize_t inputLength;
}InputBuffer;
//calc the size
const uint32_t ID_SIZE = sizeOfColumn(Row,id);
const uint32_t USRNAME_SIZE = sizeOfColumn(Row,usrname);
const uint32_t PASSWD_SIZE = sizeOfColumn(Row,passwd);
const uint32_t ROW_SIZE = ID_SIZE + USRNAME_SIZE + PASSWD_SIZE;
//column's position
const uint32_t ID_OFFSET = 0;
const uint32_t USRNAME_OFFSET = ID_OFFSET + ID_SIZE;
const uint32_t PASSWD_OFFSET = USRNAME_OFFSET + USRNAME_SIZE;
//page & row size
const uint32_t PAGE_SIZE = 4096; //注意,这里取4k是考虑到与OS的page统一,不代表DB一定需要这样设置,具体大小需要根据使用场景来调整,参考之前page讲解
const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE;
const uint32_t MAX_tPAGES = 10; //如果实际使用树去存储,那么这里的表最大page数就只会限制一次在内存里保存多少page,而不能限制db的大小上限了.
const uint32_t MAX_tROWS = MAX_tPAGES * ROWS_PER_PAGE;
typedef struct {
void* pages[MAX_tPAGES]; //在内存中每个page地址不一定相邻,所以我们还需要确定如何在内存中读/写一行
uint32_t rowNum;
}Table;
//How to read a row if it shouldn't cross the page?
void* rowPosition(Table *table, uint32_t rowNum){
uint32_t pageNum = rowNum / ROWS_PER_PAGE;
printf("rowNum1:%d\n",rowNum);
void *page = table->pages[pageNum];
printf("pageAddr1:%p\n",page);
if (page == NULL) { //!page
//malloc memory to page only when we try to access it?
page = table->pages[pageNum] = malloc(PAGE_SIZE);
printf("pageAddr2:%p\n",page);
}
uint32_t rowOffset = rowNum % ROWS_PER_PAGE; //得到是最后一个page的行数,比如有100行,每页14行 --> 100%14=2
uint32_t byteOffset = rowOffset * ROW_SIZE; //算出最后N行的地址
//TODO: printf("rowPosition:%p\n",page + byteOffset);
return page + byteOffset; //返回n行的最后地址
}
void printRow(Row *row){printf("User:[%d, %s, %s]\n", row->id, row->usrname, row->passwd);}
//convert data into memory (use void* first)
void serializeRow(Row *source ,void *destination){
//printf("%d ,%d, %d ", ID_OFFSET, USRNAME_OFFSET, PASSWD_OFFSET);
printf("%p ,%p, %p ", &(source->id), &(source->usrname), &(source->passwd));
memcpy(destination + ID_OFFSET, &(source->id), ID_SIZE);
memcpy(destination + USRNAME_OFFSET, &(source->usrname), USRNAME_SIZE);
memcpy(destination + PASSWD_OFFSET, &(source->passwd), PASSWD_SIZE);
printRow(source);
printf("destinationAddr:%p", destination);
printRow(destination);
}
//get data from memory(reverse)
void deserializeRow(void *source,Row *destination){
printRow(source);
memcpy(&(destination->id), source + ID_OFFSET, ID_SIZE);
memcpy(&(destination->usrname), source + USRNAME_OFFSET, USRNAME_SIZE);
memcpy(&(destination->passwd), source + PASSWD_OFFSET, PASSWD_SIZE);
printf("deserializeRow ");
printRow(destination);
}
ExecuteResult executeInsert(Statement *statement,Table *table){
if (table->rowNum < MAX_tROWS){
Row *insertRow = &(statement->row);
printf("rowAddr1:%p\n",&(statement->row));
serializeRow(insertRow, rowPosition(table,table->rowNum));
table->rowNum ++;
printf("rowNum:%d\n",table->rowNum);
return EXECUTE_SUCCESS;
}
return EXECUTE_TABEL_FULL_ERROR;
}
ExecuteResult executeSelect(Statement *statement,Table *table){
if(table->rowNum==0){
return EXECUTE_TABLE_EMPTY;
}
Row row;
for(uint32_t i = 0; i < table->rowNum; ++i ){
deserializeRow(rowPosition(table,i), &row);
printRow(&row);
}
return EXECUTE_SUCCESS;
}
void printFormat() { printf("db -> "); }
/*Only need when use windows...On Unix u need delete the getline() below.
* Refference POSIX-getline() & StackOverflow.
* BTW ,add "#define _GUN_SOURCE" before "stdio.h" is useless on win(clang/gcc)
*/
ssize_t getline(char **lineptr, size_t *n, FILE *stream) {
size_t pos;
int c;
if (lineptr == NULL || stream == NULL || n == NULL) {
errno = EINVAL;
return -1;
}
c = fgetc(stream);
if (c == EOF) {
return -1;
}
if (*lineptr == NULL) {
*lineptr = malloc(128); //auto malloc space
if (*lineptr == NULL) {
return -1;
}
*n = 128;
}
pos = 0;
while(c != EOF) {
if (pos + 1 >= *n) {
size_t new_size = *n + (*n >> 2);
if (new_size < 128) {
new_size = 128;
}
char *new_ptr = realloc(*lineptr, new_size);
if (new_ptr == NULL) {
return -1;
}
*n = new_size;
*lineptr = new_ptr;
}
((unsigned char *)(*lineptr))[pos ++] = c;
if (c == '\n') { //end when meet '\n',but doesn't drop it.
break;
}
c = fgetc(stream);
}
(*lineptr)[pos] = '\0';
return pos;
}
void readInput(InputBuffer* inputBuffer){
// Note: On windows,u have to impl it or use fgets() instead...(unsafe)
ssize_t bytesRead = getline(&(inputBuffer->buffer),&(inputBuffer->bufferLength),stdin);
//printf("size = %llu, buffer = %s ,bufferLen = %lld, inputLen = %llu\n",bytesRead,inputBuffer->buffer,inputBuffer->bufferLength,inputBuffer->inputLength); //if '%zd' is supported.. use it instead.
if (bytesRead <= 0) {
printf("Invalid input\n");
exit(EXIT_FAILURE);
}
//ignore '\n' in end of line (or \r\n? Refference:https://segmentfault.com/a/1190000004367243)
inputBuffer->inputLength = bytesRead - 1;
inputBuffer->buffer[bytesRead - 1] = 0 ;
}
ExecuteResult doSysCMD(InputBuffer *inputBuffer) {
if (strcmp(inputBuffer -> buffer,".q") == 0){ //support exit first~ ...这里用.q代替.exit
exit(EXIT_SUCCESS);
}else {
return EXECUTE_FAILURE;
}
}
//然后CURD里肯定是优先实现读&写,也就是select & insert
ExecuteResult prepareStatement(InputBuffer *inputBuffer, Statement *statement){
//strncmp()相比strcmp()只比较前n个字符,但注意遇到'\0'会提前结束. (why use strcmp() & why not use else-if?)
if (strncmp(inputBuffer -> buffer, "select", 6) == 0){
statement -> type = SELECT;
return EXECUTE_SUCCESS;
}
if (strncmp(inputBuffer -> buffer, "insert", 6) == 0){
statement -> type = INSERT;
//先实现写入:读取一整行字符串数据, 然后存哪? 想想之前的逻辑,这就应该存到Statement里了.可以丰富它的结构了.(如下)
int propsNum = sscanf(inputBuffer->buffer, "insert %d %s %s", &(statement->row.id), statement->row.usrname, statement->row.passwd);
printf("%d,%s\n",propsNum,inputBuffer->buffer);
if (propsNum != 3){
return EXECUTE_PARAMETER_ERROR;
}
return EXECUTE_SUCCESS;
}
return EXECUTE_UNSUPPORT_CMD;
}
//修改之前的执行方法入参和返回值,error code用起来
ExecuteResult executeStatement(Statement *statement, Table *table){
switch (statement->type){
case SELECT:
return executeSelect(statement, table);
case INSERT:
return executeInsert(statement, table);
}
return EXECUTE_FAILURE;
}
//init method
InputBuffer* newInputBuffer() {
InputBuffer *inputBuffer = malloc(sizeof(InputBuffer));
inputBuffer->buffer = NULL;
inputBuffer->bufferLength = 0;
inputBuffer->inputLength = 0;
return inputBuffer;
}
Table* initTable(){
Table *table = malloc(sizeof(Table));
table->rowNum = 0;
return table;
}
/*V0.3版:
*1.db-shell的入口是一个死循环的读取输入-->执行-->显示输出的过程
*2.vm+sql-parser
*3.in-momory backend
*/
int main ( int argc, char const *argv[]) {
InputBuffer *inputBuffer = newInputBuffer();
Table *table = initTable();
printf("Sqlite starts...\n");
while (1) {
printFormat();
readInput(inputBuffer);
if (inputBuffer -> buffer[0] == '.') { //sqlite定义系统命令都以'.'开头...
if(doSysCMD(inputBuffer) == EXECUTE_SUCCESS) {
continue;
}else {
printf("Command '%s' is unsupported .. \n", inputBuffer->buffer);
}
}
Statement statement;
/*进入SQL编译器
**这里你会发现各种输入会有不同的错误可能,再用之前的if-else后续就非常臃肿了,也不能只有0/1
**应该在之前的枚举里多定义几个错误类型 ,然后改用switch-case去判断. (不过目前有很多枚举警告...后面处理)
*/
switch (prepareStatement(inputBuffer,&statement)){
case EXECUTE_SUCCESS:
break;
case EXECUTE_PARAMETER_ERROR:
printf("Syntax error,plz check ur input again(params' type & amount)\n");
continue;
case EXECUTE_UNSUPPORT_CMD:
printf("Keyword '%s' is unsupported .. \n", inputBuffer->buffer);
continue;
}
//VM执行
switch (executeStatement(&statement, table)) {
case EXECUTE_SUCCESS:
break;
case EXECUTE_TABEL_FULL_ERROR:
printf("Table is full, del first..\n");
break;
case EXECUTE_TABLE_EMPTY:
printf("Table is empty, add data..\n");
break;
}
}
}