SQL과 AI의 만남 - AI 기반 SQL 검증 솔루션의 규칙 엔진 내부 살펴보기
요약
AI 기반 SQL 검증 에이전트의 내부 동작 원리와 규칙 엔진을 다룹니다. SQL을 실행하기 전 정적 분석을 통해 오류를 사전에 방지하고, 데이터베이스 정책을 강제하는 에이전트 구축 방법을 설명합니다.
핵심 포인트
- Text-to-SQL을 넘어 검증, 최적화, 거버넌스 영역의 AI 활용법 제시
- Node.js와 TypeScript 기반의 AI SQL 검증 에이전트 구현 사례
- 데이터베이스 연결 없이 정적 분석을 통한 보안 및 정책 준수 강화
- CI 파이프라인 및 LLM 도구 호출 워크플로와의 통합 방안
AI 데이터베이스 솔루션은 보통 하나의 단일한 개념으로 제시됩니다: 영어로 질문을 입력하면, SQL 쿼리가 결과로 돌아온다. Text-to-SQL (텍스트-to-SQL)은 인상적이지만, 이는 훨씬 더 큰 영역의 한 귀퉁이일 뿐입니다. 실제 팀 환경에서 AI는 최소한 네 가지 다른 방식으로 데이터베이스를 도울 수 있습니다:
- 생성 (Generation) — 자연어를 SQL로 변환 (text-to-SQL).
- 검증 (Validation) — SQL이 데이터베이스에 닿기 _전(before)_에 검토.
- 최적화 (Optimization) — 인덱스(index) 제안, 느린 쿼리 재작성, 실행 계획(execution plans) 설명.
- 거버넌스 (Governance) — 읽기 전용(read-only) 정책 강제, 위험한 문장 차단, 액세스 감사(auditing).
이 글은 실제 실행 가능한 프로젝트를 사례 연구로 사용하여 두 번째와 네 번째 카테고리에 집중합니다: Node.js와 TypeScript로 구축된 **AI SQL 검증 에이전트 (AI SQL Validation Agent)**입니다.
전체 소스 코드는 여기에서 확인할 수 있습니다:
👉 github.com/cs2026086510-a11y/sql-ai-validator-agent
제 동료인 Cristian Soto가 이미 REST API 계층과 Codex Skill에 대한 훌륭한 가이드를 작성했습니다 (여기서 읽어보세요). 따라서 이를 반복하는 대신, 이 글은 **엔진 내부(inside the engine)**를 다룹니다: 검증 규칙이 실제로 어떻게 작동하는지, 왜 다른 무엇보다 문자열 리터럴(string literals)을 먼저 제거해야 하는지, SQL 방언(dialect)의 차이가 어떻게 감지되는지, 그리고 이러한 종류의 에이전트가 CI 파이프라인 및 LLM 도구 호출(tool-calling)과 같은 실제 워크플로에 어떻게 부합하는지를 살펴봅니다.
단순히 쿼리를 실행하는 대신 왜 "AI 검증"인가?
데이터베이스는 쿼리가 잘못되었을 때 항상 알려줍니다 — 하지만 그것은 당신이 쿼리를 실행한 _후(after)_에 일어나는 일입니다. 이는 실제 프로젝트에서 세 가지 문제를 야기합니다:
- 오류가 너무 늦게 도착합니다. CI 파이프라인(CI pipeline)이나 코드 리뷰(code review)에서는 머지(merge)하기 전에 피드백을 받고 싶지, 프로덕션 로그(production logs)에서 확인하고 싶지 않습니다.
- 오류 메시지가 모호합니다.
syntax error at or near "FORM"은 정확하긴 하지만 도움이 되지 않습니다. 주니어 개발자는 여전히 무엇이 잘못되었는지 파악해야 합니다. - 데이터베이스에는 정책에 대한 의견이 없습니다. 권한이 허용된다면 데이터베이스는
DROP TABLE users;를 기꺼이 실행할 것입니다.
AI 스타일의 검증 에이전트(validation agent)는 이 모델을 뒤집습니다. SQL을 **신뢰할 수 없는 입력(untrusted input)**으로 취급하여 정적으로 분석하고, 시니어 개발자가 풀 리퀘스트(pull request)에 코멘트를 남기는 것과 동일한 방식으로 구조화된 인간 친화적 리뷰를 반환합니다.
Developer / CI / LLM
|
v
...
에이전트는 데이터베이스에 절대 연결하지 않습니다. 이는 한계가 아니라 기능입니다. 자격 증명(credentials)과 리스크 없이 어디에서나(CI 러너, 에디터, 서버리스 함수) 실행할 수 있습니다.
0단계: 원시 SQL 텍스트를 절대 신뢰하지 마세요
이것이 전체 엔진에서 가장 과소평가된 부분입니다. 어떤 규칙이 실행되기 전에, SQL 문자열은 정규화(normalized)되며 문자열 리터럴(string literals)이 제거됩니다:
const stringLiteralPattern = /'(?:''|[^'])*'|"(?:\"|[^ "])*"/g;
export function normalizeSql(sql: string): string {
...
이것이 왜 중요할까요? 다음과 같이 완벽하게 유효한 쿼리를 생각해 보십시오:
SELECT 'FORM is mentioned inside a string' AS note;
FORM이라는 오타를 스캔하는 단순한 검증기(naive validator)는 이를 오류로 표시할 것이며, 이는 **오탐(false positive)**이 됩니다. 모든 리터럴을 먼저 빈 값인 ''로 교체함으로써, 키워드 체크는 사용자 데이터가 아닌 오직 SQL의 _구조(structural)_만을 보게 됩니다. 정규 표현식(regex)은 심지어 이스케이프된 따옴표(작은따옴표로 둘러싸인 문자열 내부의 '')까지 처리하는데, 이는 직접 만든 SQL 스캐너를 망가뜨리는 전형적인 예외 케이스(edge case)입니다.
이는 SQL 인젝션(SQL injection) 방어의 원리와 동일합니다: 데이터와 구조는 절대 혼동되어서는 안 됩니다. LLM 기반 도구를 포함한 모든 AI 데이터베이스 도구에는 이러한 분리가 필요하며, 그렇지 않으면 악의적인 문자열 리터럴이 분석을 조작할 수 있습니다(프롬프트 인젝션(prompt injection)의 한 형태).
규칙 엔진: 결정론적 체크, 구조화된 출력
src/validation/rules.ts에 있는 모든 규칙은 동일한 계약(contract)을 따릅니다. 즉, 아무런 반응을 하지 않거나 구조화된 에러 객체(structured error object)를 방출합니다.
if (/\bFORM\b/i.test(literalSafeSql)) {
errors.push({
code: "TYPO_FORM",
...
흥미로운 규칙들은 단순한 정규 표현식(regex)이 아니라 실제 파싱(parsing) 로직이 필요한 것들입니다. 두 가지 예시는 다음과 같습니다.
상태 머신(state machine)을 이용한 닫히지 않은 따옴표 감지
단일 정규 표현식으로는 닫히지 않은 따옴표를 감지할 수 없습니다. 왜냐하면 작은따옴표, 큰따옴표, 이스케이프된 따옴표(escaped quotes), 그리고 중복된 따옴표('')가 모두 서로 상호작용하기 때문입니다. 엔진은 문자열을 한 글자씩 순회하며 따옴표의 상태를 추적합니다.
export function hasClosedQuotes(sql: string): boolean {
let singleQuoteOpen = false;
let doubleQuoteOpen = false;
...
이것은 아주 작은 상태 머신(state machine)입니다. 실제 SQL 렉서(lexer)들이 사용하는 것과 동일한 기술이지만, 단 하나의 작업에만 범위를 좁혀 적용한 것입니다.
전체 파서 없이 누락된 쉼표 추측하기
SELECT id name email FROM users;는 초보자들이 가장 흔히 하는 실수 중 하나입니다. 엔진은 SELECT 리스트를 추출하고 휴리스틱(heuristic)을 적용합니다. 즉, 여러 개의 식별자(identifier)가 나열되어 있고, 쉼표가 없으며, 해당 패턴을 정당화할 수 있는 AS나 DISTINCT 같은 별칭(alias) 키워드가 없는 경우를 찾아냅니다.
function looksLikeMissingComma(sql: string): boolean {
const match = /^\s*SELECT\s+(.+?)\s+FROM\s+/i.exec(sql);
if (!match) return false;
...
여기서 설계 철학을 주목하십시오. 이 규칙은 소음을 내는 것보다 침묵하는 것을 선호합니다. SELECT id AS user_id FROM users는 아무런 간섭 없이 통과됩니다. 개발자 도구에서는 재현율(recall)보다 정밀도(precision)가 더 중요합니다. 양치기 소년처럼 잘못된 경고를 남발하는 검증기는 일주일 안에 비활성화되기 마련입니다.
방언(Dialects): 동일한 SQL이 동시에 유효할 수도, 유효하지 않을 수도 있음
AI 데이터베이스 솔루션이 가르쳐줄 수 있는(또는 강제할 수 있는) 가장 가치 있는 사실 중 하나는 SQL은 단 하나의 언어가 아니라는 점입니다. 동일한 페이지네이션(pagination) 의도가 네 가지 다른 방식으로 작성될 수 있습니다.
| 엔진 | 구문 |
|---|---|
| PostgreSQL / MySQL | SELECT * FROM users LIMIT 5; |
| ... |
엔진은 이러한 차이점들을 엔진별 특정 규칙(engine-specific rules)으로 인코딩합니다. 예를 들어:
엔진은 이러한 차이점들을 엔진별 특정 규칙(engine-specific rules)으로 인코딩합니다. 예를 들어:
if (engine === "postgresql" && /TOP\s+\d+\b/i.test(sql)) {
errors.push({
code: "ENGINE_SPECIFIC",
...
ANSI 모드에서는 흥미로운 세부 사항이 있습니다. 방언별 구문(dialect-specific syntax)은 error가 아닌 **warning**으로 보고됩니다. 쿼리는 사용자의 엔진에서 정상적으로 실행될 수 있지만, 에이전트는 단지 이 코드가 이식성이 떨어진다고 알려주는 것일 뿐입니다. 이러한 심각도 수준(Severity levels)은 단순한 이진 검증기(binary validator)를 미묘하게 차별화된 리뷰어로 만듭니다.
거버넌스: 읽기 전용 방화벽 (read-only firewall)
이 에이전트는 또한 정책을 강제합니다. 토크나이저가 (리터럴 제거된) SQL을 분할하고, 모든 토큰을 블랙리스트(denylist)와 비교합니다:
const forbiddenStatements = [
"ALTER", "CREATE", "DELETE", "DROP", "EXEC",
"EXECUTE", "INSERT", "MERGE", "TRUNCATE", "UPDATE"
...
다중 문장 검사(multiple-statement check)와 결합하여, 이는 고전적인 '게으름 피우기 공격 패턴(piggyback attack pattern)'이 발생하기도 전에 차단합니다:
SELECT * FROM users; DROP TABLE users; -- 거부됨: MULTIPLE_STATEMENTS + FORBIDDEN_STATEMENT
이것은 SQL을 생성하는 LLM 앞단에 필요한 바로 그 종류의 레이어입니다. 모델이 파괴적인 구문을 환각(hallucinate)하더라도, 결정론적 검증기(deterministic validator)가 실행 전에 이를 제거합니다.
실제 통합 예시 (Real-world integration examples)
1. CI/CD 품질 게이트 (GitHub Actions)
저장소의 모든 .sql 파일에 차단되는 오류가 있으면 빌드를 실패시킵니다:
- name: Validate SQL files
run: |
for f in $(git ls-files '*.sql'); do
...
2. 풀 리퀘스트 리뷰 봇 (Pull request review bot)
API가 구조화된 JSON(code, message, suggestion, explanation)을 반환하기 때문에, 봇은 추가적인 파싱 없이도 읽기 쉬운 리뷰 코멘트를 게시할 수 있습니다:
🤖 SQL Review — migration_2026_07.sql
Issue: TOP은 유효한 PostgreSQL SELECT 구문이 아닙니다.
Suggestion: PostgreSQL에서는 LIMIT을 사용하세요.
3. LLM 에이전트용 도구 (Tool for an LLM agent)
여기서
{
"name": "validate_sql",
"description": "실행 전 SQL 문을 검증합니다. 구조화된 에러와 가능한 경우 수정된 버전을 반환합니다.",
...
워크플로우는 다음과 같습니다: LLM이 SQL을 생성함 → 결정론적 검증기(deterministic validator)가 이를 검토함 → 검증된 SQL만이 실행 대상으로 고려됨. 확률론적(probabilistic) 계층과 결정론적(deterministic) 계층이 서로를 점검하며, 이는 우리가 현재 알고 있는 AI + 데이터베이스를 위한 가장 안전한 아키텍처입니다.
다음에 개선하고 싶은 점
- 실제 파서 (AST 기반). 정규 표현식(Regex) 휴리스틱은 교육용이나 1차 방어선으로는 완벽하지만, 중첩된 서브쿼리(nested subqueries), CTE(Common Table Expressions), 윈도우 함수(window functions)를 처리하려면 진정한 문법(grammar)이 필요합니다.
node-sql-parser와 같은 라이브러리가 자연스러운 다음 단계가 될 것입니다. - 행/열 위치 정보. IDE 진단 및 PR(Pull Request) 코멘트는 정확한 문자를 가리킬 때 10배 더 유용해집니다.
- 설정 가능한 정책.
INSERT문은 대시보드 컨텍스트에서는 차단되어야 하지만,migrations/폴더 내에서는 허용되어야 합니다. - 선택적인 LLM 설명 계층 — 단, 항상 결정론적 규칙(deterministic rules)이 적용된 _이후_에 위치해야 하며, 새로운 내용을 지어내는 것이 아니라 구조화된 결과물을 설명하는 역할을 해야 합니다.
결론
AI 데이터베이스 담론은 생성(generation)에 의해 주도되고 있지만, 검증(validation)과 거버넌스(governance)야말로 AI 스타일의 툴링이 즉각적이고 저위험의 가치를 제공하는 영역입니다: 자격 증명(credentials)이 필요 없고, 실행되지 않으며, 결정론적이고 테스트 가능한 동작을 보장하고, 인간, CI 시스템, 그리고 LLM이 모두 소비할 수 있을 만큼 구조화된 출력을 제공하기 때문입니다.
Text-to-SQL이 가속기라면, 검증 에이전트(validation agent)는 브레이크이자 안전벨트입니다. 진지한 AI 데이터베이스 솔루션이라면 이 두 가지가 모두 필요합니다.
Repository: github.com/cs2026086510-a11y/sql-ai-validator-agent
Companion article (API + Codex Skill): Building an AI SQL Validation Agent with Codex and a REST API by Cristian Soto
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기