Go에서 PostgreSQL 파서 구축: 오픈소스 공개 후 발생한 문제

발행: (2026년 2월 18일 오후 03:18 GMT+9)
7 분 소요
원문: Dev.to

Source: Dev.to

Cover image for Building a PostgreSQL Parser in Go: What Broke After We Open-Sourced It

postgresparser 은 순수 Go 로 구현된 PostgreSQL SQL 파서입니다. SQL 텍스트를 실행하지 않고도 구조화된 메타데이터(테이블, 컬럼, 조인, 필터, DDL 액션, 파라미터)로 변환합니다.

우리는 이 파서가 견고하다고 생각했습니다. 오픈소스를 공개하고 보니 그 생각이 틀렸다는 것을 알게 되었습니다.
우리가 배운 점을 정리합니다.

가장 큰 변화: 사용 사례 확장

우리는 자체 워크플로우를 위해 파서를 만들었습니다. 공개 사용자들은 매우 다른 작업량을 가지고 나타났습니다. 출시 후 첫 주에 대부분의 피드백은 결정론적 배치 파싱에 초점이 맞춰졌습니다.

내부 가정이 즉시 깨졌다

단일 팀 내부에서는 모두가 규칙을 “알고” 있기 때문에 모호한 동작이 살아남습니다. 공개 사용자들은 그 맥락을 가지고 있지 않습니다.

첫 번째 압력점은 다중 문장 SQL이었습니다. 우리는 ParseSQL(단일 문장)을 가지고 있었고 배치 파싱이 “충분히 가깝다”고 가정했습니다. 그렇지 않았습니다.

사람들은 파서를 다음과 같이 사용하고 있었습니다:

  • CI linting pipelines
  • Production tools
  • LLM wrappers

우리가 명확히 답변하지 못한 실용적인 질문들:

  • 정확히 어떤 문장이 실패했나요?
  • 이것이 경고인지 심각한 오류인지?
  • 진단 정보를 원본 SQL 텍스트에 신뢰성 있게 매핑할 수 있나요?

그러한 질문들은 암시된 동작에 의존하는 대신 엄격한 계약을 정의하도록 강요했습니다. 도구가 SQL을 대량으로 소비한다면, 배치 연관성은 모든 것입니다.

깨진 동작 예시

SELECT 1;
SELECT FROM;
SELECT 2;

초기 배치 동작은 결과가 압축되고 진단이 문장 우선이 아니었기 때문에 상관관계를 파악하기 어려웠습니다. CI 검사나 마이그레이션 도구에서는 “배치에서 무언가가 실패했습니다”라는 메시지가 실질적인 조치를 취하기 어렵게 만들었습니다.

이제 각 문장은 (Index, RawSQL, Query, Warnings)와 같은 결정적인 상관관계를 갖게 되어, 하위 코드가 정확한 원본 문장을 가리킬 수 있습니다.

전후 API 차이

- type ParseBatchResult struct {
-   Queries          []*ParsedQuery
-   Warnings         []ParseWarning
-   TotalStatements  int
-   ParsedStatements int
- }
+ type StatementParseResult struct {
+   Index    int
+   RawSQL   string
+   Query    *ParsedQuery   // nil => IR conversion failure
+   Warnings []ParseWarning // statement‑scoped warnings
+ }
+
+ type ParseBatchResult struct {
+   Statements       []StatementParseResult
+   TotalStatements  int
+   ParsedStatements int
+   HasFailures      bool
+ }

그 형태는 빠른 데모에는 덜 편리하지만 실제 통합에는 훨씬 더 좋습니다.

실제 환경에서의 Real SQL은 테스트 픽스처보다 더 난잡합니다

오픈소스 사용으로 내부 테스트에서는 보지 못했던 SQL 형태가 등장했습니다:

  • 뒤에 붙는 세미콜론과 이상한 공백
  • 전체적으로는 유효하지만 중간에 있는 잘못된 구문
  • DDL과 DML 스크립트가 혼합된 경우
  • DDL 경로에서의 ONLY 변형

파서는 모호해지지 않으면서 회복력을 가져야 했습니다. 이를 위해 다음이 필요했습니다:

  • 더 나은 문장 수준 경고 할당
  • 명시적인 실패 의미(Query == nil)
  • DDL 관계 추출 경로 전반에 걸친 더 엄격한 처리

Concrete snippet (current behavior)

batch, err := postgresparser.ParseSQLAll(sql)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("total=%d parsed=%d has_failures=%t\n",
    batch.TotalStatements, batch.ParsedStatements, batch.HasFailures)

for _, stmt := range batch.Statements {
    fmt.Printf("idx=%d failed=%t warnings=%d raw=%q\n",
        stmt.Index, stmt.Query == nil, len(stmt.Warnings), stmt.RawSQL)
}

그것이 사람들이 요구한 통합 모델이다: 결정적이고, 검사 가능하며, 가장 좋은 의미에서 지루한 모델이다.

왜 이것이 중요한가

오픈소스는 우리가 모서리 사례를 대충 넘길 수 있는 능력을 없앴습니다. 개발 루프는 다음과 같이 바뀌었습니다:

  1. 구현
  2. 도전을 받음
  3. 단순화
  4. 테스트로 동작을 고정
  5. 계약을 문서화

그 루프 덕분에 postgresparser는 내부 전용 도구였을 때보다 더 나아졌습니다. 내부 도구는 모호함을 견딜 수 있지만, 공개 라이브러리는 그렇지 못합니다.

postgresparser 위에 뭔가를 구축하고 있다면 이슈를 열어 주세요. 실제 SQL은 계약을 지속적으로 개선합니다.

0 조회
Back to Blog

관련 글

더 보기 »