SQLite 프론트엔드 내부: 토크나이저, 파서, 그리고 코드 생성기

발행: (2026년 1월 11일 오전 03:26 GMT+9)
10 min read
원문: Dev.to

Source: Dev.to

Hello, I’m Maneshwar. I’m working on FreeDevTools online – a free, open‑source hub that brings together all dev tools, cheat codes, and TLDRs in one place, so developers can find what they need without endless searching.

SQLite 프런트‑엔드 심층 탐구

이전 글에서는 SQLite의 전체 아키텍처와 SQL 컴파일을 실행과 깔끔하게 분리하는 방식을 살펴보았습니다. 그 고수준 개요에서는 프런트엔드를 SQL 텍스트를 실행 가능한 바이트코드로 변환하는 구성 요소로 소개했습니다.

오늘은 그 프런트엔드 파이프라인을 더 깊이 파고들어 SQL 문이 어떻게 분해되고, 이해되며, 최종적으로 SQLite 가상 머신이 실행할 수 있는 바이트코드 프로그램으로 변환되는지 살펴보겠습니다.

Image

토크나이저

애플리케이션이 SQL 문이나 SQLite 명령을 제출하면 첫 번째로 원시 입력을 보는 구성 요소는 토크나이저입니다.

그 역할은 간단하지만 필수적입니다:

  1. 입력 문자열을 스캔합니다.
  2. 문자열을 개별 토큰으로 분리합니다.
  3. 그 토큰들을 파서에 하나씩 전달합니다.

토큰에는 다음이 포함됩니다

  • SQL 키워드(SELECT, INSERT, WHERE)
  • 식별자(테이블 및 컬럼 이름)
  • 리터럴(숫자, 문자열)
  • 연산자 및 구두점

토크나이저 구현은 tokenize.c 소스 파일에 있습니다.

특이한 제어 흐름 선택

YACC나 BISON으로 만든 많은 컴파일러 툴체인에서는 파서가 토크나이저를 호출해 다음 토큰을 요청합니다. SQLite는 이 관계를 뒤집어 토크나이저가 파서를 호출하도록 합니다.

Richard Hipp는 두 접근 방식을 모두 실험해 본 결과, 토크나이저가 파서를 주도하도록 하면 코드가 더 깔끔하고 유지보수가 쉬워진다는 것을 발견했습니다. 이 역전은 상태 처리를 단순화하고 SQLite 실행 모델과 더 잘 통합됩니다.

파서

토큰이 생성되면 파서는 문맥에 따라 토큰에 의미를 부여합니다. SQLite의 파서는 Lemon이라는 LALR(1) 파서 생성기를 사용해 생성됩니다. Lemon은 SQLite 전용으로 만들어졌습니다.

왜 Lemon인가?

  • 재진입 가능한 파서.
  • 스레드‑안전 파서.
  • 생성된 코드는 메모리 누수에 강함.

이러한 특성은 SQLite의 임베디드 및 다중 스레드 사용 사례와 완벽히 맞아떱니다.

문법 정의 및 생성된 코드

SQLite 문법은 parse.y 파일에 정의되어 있으며, 여기에는:

  • SQL 구문 규칙.
  • SQLite‑특화 명령 및 확장.

이 문법을 기반으로 Lemon은 다음 파일들을 생성합니다:

파일목적
parse.c파서 구현
parse.h토큰 타입에 대한 숫자 코드

파서는:

  • SQL 구문을 검증합니다.
  • 파스 트리를 구축합니다.
  • 표현식, 절, 문장과 같은 의미 구조를 식별합니다.

Lemon 사용 가능성에 대한 참고

YACC나 BISON과 달리 Lemon은 일반 개발 시스템에 기본적으로 설치되지 않습니다. SQLite는 tool 디렉터리 안에 Lemon 전체 소스(lemon.c)와 문서를 포함시켜, 외부 의존성 없이 언제든지 파서를 재생성할 수 있게 합니다. 이는 SQLite의 자체 포함 철학을 또다시 보여줍니다.

코드 생성기

파서가 모든 토큰을 소비하고 완전한 파스 트리를 조립하면 제어는 코드 생성기로 넘어갑니다.

그 책임은 다음과 같습니다:

  • 파스 트리를 순회합니다.
  • 동등한 SQLite 바이트코드 프로그램을 내보냅니다.
  • 프로그램이 SQL 문이 설명하는 효과를 정확히 구현하도록 보장합니다.

실제 로직이 위치한 곳

SQLite의 코드 생성 로직은 여러 소스 파일에 분산되어 있으며, 각각 특정 종류의 SQL 문이나 구문을 담당합니다:

파일책임
expr.c표현식 처리 및 최적화
select.cSELECT 문 컴파일
insert.cINSERT 문 컴파일
update.cUPDATE 문 컴파일
delete.cDELETE 문 컴파일
trigger.c트리거 정의와 실행
sqlite3.c전체 바이트코드 프로그램 조립

각 파일은 파스 트리의 해당 부분을 방문하면서 적절한 바이트코드 명령을 Vdbe(Virtual DataBase Engine) 구조에 기록합니다. 최종적으로 생성된 바이트코드 프로그램은 SQLite 가상 머신에 의해 순차적으로 실행되어, 원본 SQL 문의 의도를 실제 데이터베이스 작업으로 구현합니다.

| 표현식 및 연산 | 설명 | | where.c | SELECT, UPDATE, DELETE에 대한 WHERE 절 로직 | | select.c | SELECT 문 | | insert.c
delete.c
update.c | 데이터‑수정 문 | | trigger.c | 트리거 실행 로직 | | attach.c | 데이터베이스 첨부 처리 | | vacuum.c | 데이터베이스 재구성 | | pragma.c | PRAGMA 명령 | | build.c | 스키마 및 기타 문 | | auth.c | sqlite3_set_authorizer를 통한 권한 부여 |

문장별 파일은 표현식 처리나 술어 평가와 같은 공통 로직을 expr.cwhere.c 같은 공유 모듈에 위임합니다. 이러한 모듈화는 코드베이스를 체계적으로 유지하고 SQLite의 아키텍처 명확성을 강화합니다.

SQL 텍스트에서 바이트코드로

프론트엔드 파이프라인의 마지막 단계:

  1. SQL 텍스트가 토큰화되었습니다.
  2. 구문이 검증되었습니다.
  3. 의미가 해석되었습니다.
  4. 최적화된 바이트코드 프로그램이 생성되었습니다.

이 모든 작업은 sqlite3_prepare API 호출 내부에서 수행되며, 애플리케이션에는 숨겨져 있습니다. 애플리케이션이 받는 것은 준비된 문 핸들이지만, 그 핸들 뒤에는 실행 준비가 된 완전 컴파일된 프로그램이 존재합니다.

마무리 생각

오늘 배운 내용은 SQLite의 API 수준에서의 단순함이 정교하게 설계된 컴파일 파이프라인에 의해 뒷받침된다는 것을 보여줍니다. 구체적으로는:

  • 토크나이저가 파서를 주도하도록 하고,
  • 맞춤형이며 안전한 파서 생성기(Lemon)를 사용하며,
  • 코드 생성을 명확하고 모듈화된 구성 요소로 조직함으로써,

SQLite는 강력하면서도 유지보수가 용이한 프런트엔드를 구현합니다.

SQLite와 관련된 저의 실험 및 직접 실행 예시는 여기에서 확인할 수 있습니다: lovestaco/sqlite

참고 자료

피드백이나 기여자는 언제든 환영합니다! 온라인에 공개된 오픈소스로, 누구든지 바로 사용할 수 있습니다.

GitHub에서 별표를 눌러 주세요: freedevtools

Back to Blog

관련 글

더 보기 »