Postgres/Postgres Internal

Flex와 Bison을 이용한 쿼리문 파싱 예제 만들기

moxie2ks 2025. 3. 6. 18:01
728x90
반응형

개요

Postgres에는 쿼리 파싱을 위해 사용되는 두 가지 주요 툴체인이 존재한다: flexbison이다. 이들은 각각 문자열 파싱을 위한 문법을 제공하며, 그에 맞게 특정 문자열을 파싱 하는 데 사용된다. 본 문서에서는 flexbison을 활용하여 SQL의 SELECT 문을 파싱하는 예제를 제시함으로써 PG 내부 쿼리 파서를 보다 쉽게 이해할 수 있도록 한다.

gram.y 파일 생성

쿼리 파싱을 위한 토큰을 정의하고, 각 토큰에 올 수 있는 다른 토큰들을 규정한다.

// C code block between '%{' and '%}'
%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// prototype of creating token function
int yylex();

// prototype of printing error message function
void yyerror(const char* s);

%}

// Defines the different data types that can be used in parsing.
%union {
    int ival;
    char* sval;
}

// %token defines the tokens that can be used in gram.
%token INTEGER IDENTIFIER // token for integer and identifier
%token SELECT FROM WHERE EQ SEMICOLON // token for query keyword
%token STAR  // token for '*'

// %% is start of gram rule
%% 

// query defines grammar rule. This rule parses SELECT statement.
query:
    SELECT select_list FROM table_name where_clause SEMICOLON
    {
        // print success message if this rule is parsed successfully.
        printf("Parsed query successfully.\n");
    }
    ;
// select_list defines a list of columns to select.
select_list:
    IDENTIFIER
    | select_list ',' IDENTIFIER // handles selecting multiple columns.
    | STAR  // Processes when all columns(*) are selected.
    ;

// table_name defines the table name, consisting of a single IDENTIFIER.
table_name:
    IDENTIFIER
    ;

// where_clause defines a conditional clause.
where_clause:
    WHERE condition // if there is a condition.
    |  /* empty */ //handles the absence of a condition.
    ;

// condition defines the content of the conditional clause.
condition:
    IDENTIFIER EQ INTEGER // A condition that indicates that a particular column is equal to a particular integer.
    ;
// %% is end of gram rule
%% 

// The yyerror function is called when an error occurs during parsing.
void yyerror(const char* s) {
    fprintf(stderr, "Error: %s\n", s);
}

scan.l 파일 생성

정의된 토큰들이 발견되면 어떤 동작을 수행할 것인지에 대한 규칙을 설정한다.

%{
#include <string.h>
#include "gram.tab.h"
%}

%% /* start of flex rule. */

SELECT           { return SELECT; }
FROM             { return FROM; }
WHERE            { return WHERE; }
[0-9]+           { yylval.ival = atoi(yytext); return INTEGER; }
[a-zA-Z][a-zA-Z0-9_]* { yylval.sval = strdup(yytext); return IDENTIFIER; }
\*               { return STAR; }
=                { return EQ; }
;                { return SEMICOLON; }
[ \t\n]+         /* ignore whitespace */
.                { return yytext[0]; }
%% /* end of flex rule. */

int yywrap() {
    return 1;
}

main.c 생성

위 두 코드를 실행할 main 함수를 간략히 작성한다.

#include <stdio.h>
#include "gram.tab.h"

int main() {
    printf("Kisoon SQL Parser.\n");
    printf("Enter SQL query : ");
    yyparse();
    return 0;
}

Makefile 생성

위 코드를 컴파일하고 실행 바이너리를 만드는 파일

CC = gcc
YACC = bison
LEX = flex

all: parser

parser: gram scan
 $(CC) -o parser gram.tab.c lex.yy.c main.c

gram: gram.y
 $(YACC) -d gram.y

scan: scan.l
 $(LEX) scan.l

clean:
 rm -rf parser gram.tab.c gram.tab.h lex.yy.c parser.dSYM

빌드 및 실행

$ make all  
bison -d gram.y
flex scan.l
gcc -o parser gram.tab.c lex.yy.c main.c
$ ./parser
Kisoon SQL Parser.
Enter SQL query : SELECT a, b FROM table WHERE a = 10;
Parsed query successfully.

결론

위 예제를 통해 flexbison을 활용한 SQL SELECT 문 파싱의 기초를 이해할 수 있다. 이 예제는 PG의 쿼리 파서 구조를 이해하는 데 큰 도움이 될 것이다.

참고문헌

SQL Query Parser

728x90
반응형