원본 글
https://cstack.github.io/db_tutorial/parts/part2.html
SQLite 의 앞단 (front-end)
- SQLite 의 앞단은 문자열을 구문 분석(parsing) 하고 바이트 코드라는 내부 표현을 뱉어내는 SQL 컴파일러이다.
- 바이트 코드는 가상 머신(virtual machine) 으로 넘어가 가상 머신에 의해 실행된다.
다시 아키텍처 살펴보기
- 문자열을 먼저 바이트 코드로 만든 뒤에 바이트 코드를 가상머신에서 실행하는 2가지 절차를 따르면 무슨 장점이 있을까?
- 2가지 파트를 각각 구현하기 때문에 각 파트에 대한 복잡도를 낮출 수 있다.
- 가상 머신은 SQL 문법 에러에 대해 걱정하지 않아도 된다.
- 일반 쿼리를 한번 컴파일하고 바이트 코드를 캐싱해놓으면 성능을 개선할 수 있다.
- 2가지 파트를 각각 구현하기 때문에 각 파트에 대한 복잡도를 낮출 수 있다.
우리가 이번 시간에 해야 할 일 정리해보기
meta-commands 처리하기
.exit
과 같이 SQL 문이 아닌 명령어를 "meta-commands" 라고 부른다.- "meta-commands" 는 모두
.
으로 시작한다. - 우리는 명령어가 "meta-commands" 인지 체크하고 분리된 함수 내에서 따로 처리할 것이다.
입력을 내부 표현문으로 변환하는 단계 추가하기
- 우리가 받은 입력을 내부 표현문 (internal representation of a statement) 으로 변환할 것이다.
prepared-statement 를 execute_statement
로 넘기기
- prepared statement 를
execute_statement
로 넘길 것이다. - 이 함수가 결국 우리의 가상머신이 될 것이다.
main 함수 구성하기
meta-commands 처리하기
meta-commands 가 사용할 enum
추가하기
- meta commands 가 성공했는지 실패했는지 구분할 때 반환할 enum 이다.
MetaCommandResult
라는 이름을 사용한다.
typedef enum
{
META_COMMAND_SUCCESS,
META_COMMAND_UNRECOGNIZED_COMMAND
} MetaCommandResult;
- 원 글의 저자는 예외(Exception)를 사용하는 것을 선호하지 않는다.
- 물론, C 에서 예외를 지원하지도 않는다.
- 대신, enum 결과 코드를 사용할 것이다.
META_COMMAND_UNRECOGNIZED
는 meta commands 를 인식하지 못했을 때 반환한다.
meta-commands 처리 함수 추가하기
MetaCommandResult do_meta_command(InputBuffer *input_buffer)
{
if (strcmp(input_buffer->buffer, ".exit") == 0)
{
exit(EXIT_SUCCESS);
}
else
{
return META_COMMAND_UNRECOGNIZED_COMMAND;
}
}
do_meta_command()
는 기존 기능의 래퍼일 뿐이다.- 더 많은 명령을 위한 공간이 남아있다.
prepared-statement 처리하기
prepared-statement 가 사용할 enum
과 struct
추가하기
typedef enum
{
STATEMENT_INSERT,
STATEMENT_SELECT
} StatementType;
typedef struct
{
StatementType type;
} Statement;
- 일단 가능한 2가지 경우만 추가했다.
insert
와select
명령어만 먼저 추가했다.
- statement 에 매개변수를 포함하면 더 많은 데이터를 포함할 수 있다.
prepared-statement 처리 함수 추가하기
PrepareResult prepare_statement(InputBuffer *input_buffer, Statement *statement)
{
if (strncmp(input_buffer->buffer, "insert", 6) == 0)
{
statement->type = STATEMENT_INSERT;
return PREPARE_SUCCESS;
}
if (strcmp(input_buffer->buffer, "select") == 0)
{
statement->type = STATEMENT_SELECT;
return PREPARE_SUCCESS;
}
return PREPARE_UNRECOGNIZED_STATEMENT;
}
prepare_statement
는 SQL 을 당장 이해하진 못한다.- 아직은
insert
와select
오직 두 단어만 이해하고 있다.
- 아직은
insert
를 위해strncmp
를 사용한다.insert
명령은insert 1 stack foo@bar.com
과 같이 뒤에 데이터가 따라올 것이다.
execute_statement()
처리하기
void execute_statement(Statement *statement)
{
switch (statement->type)
{
case (STATEMENT_INSERT):
printf("This is where we would do an insert.\n");
break;
case (STATEMENT_SELECT):
printf("This is where we would do a select.\n");
break;
}
}
- 여기까지 에러가 전달될 수 없으므로, 이 부분에선 어떠한 에러도 반환하지 않는다.
main()
구성하기
int main(int argc, char *argv[])
{
InputBuffer *input_buffer = new_input_buffer();
while (true)
{
print_prompt();
read_input(input_buffer);
if (input_buffer->buffer[0] == '.')
{
switch (do_meta_command(input_buffer))
{
case (META_COMMAND_SUCCESS):
continue;
case (META_COMMAND_UNRECOGNIZED_COMMAND):
printf("Unrecognized command '%s'\n", input_buffer->buffer);
continue;
}
}
Statement statement;
switch (prepare_statement(input_buffer, &statement))
{
case (PREPARE_SUCCESS):
break;
case (PREPARE_UNRECOGNIZED_STATEMENT):
printf("Unrecognized keyword at start of '%s'.\n", input_buffer->buffer);
continue;
}
execute_statement(&statement);
printf("Executed.\n");
}
}
테스트
db > insert foo bar
This is where we would do an insert.
Executed.
db > delete foo
Unrecognized keyword at start of 'delete foo'.
db > select
This is where we would do a select.
Executed.
db > .tables
Unrecognized command '.tables'
db > .exit
- 슬슬 모양을 갖춰가고 있다.
- 다음 시간엔 세계 최악의 데이터 저장소를 만들어보자.
반응형
'데이터베이스 > SQLite 직접 만들어보기' 카테고리의 다른 글
SQLite 직접 만들어보기 Step 3 - 메모리에서만 동작하는 단일 테이블 DB 만들어보기 (0) | 2023.06.09 |
---|---|
SQLite 직접 만들어보기 Step 1 - 매우 간단한 REPL 만들어보기 (0) | 2023.06.08 |
SQLite 직접 만들어보기 Step 0 - SQLite 아키텍처 살펴보기 (0) | 2023.05.31 |